421 Stimmen

Wenden Sie mehrere Funktionen auf mehrere Groupby-Spalten an

Die Docs zeigen, wie man mehrere Funktionen gleichzeitig auf ein Groupby-Objekt anwendet, indem man ein Dict mit den Ausgabespaltennamen als Schlüssel verwendet:

In [563]: grouped['D'].agg({'result1' : np.sum,
   .....:                   'result2' : np.mean})
   .....:
Out[563]: 
      result2   result1
A                      
bar -0.579846 -1.739537
foo -0.280588 -1.402938

Dies funktioniert jedoch nur bei einem Series-Groupby-Objekt. Und wenn ein Dict in ähnlicher Weise an ein Groupby-DataFrame übergeben wird, erwartet es, dass die Schlüssel die Spaltennamen sind, auf die die Funktion angewendet wird.

Was ich tun möchte, ist, mehrere Funktionen auf mehrere Spalten anzuwenden (aber bestimmte Spalten werden mehrmals bearbeitet). Außerdem werden einige Funktionen von anderen Spalten im Groupby-Objekt abhängen (wie SUMMEWENN-Funktionen). Meine aktuelle Lösung besteht darin, Spalte für Spalte vorzugehen und etwas Ähnliches wie den obigen Code zu tun, wobei Lambdas für Funktionen verwendet werden, die von anderen Zeilen abhängen. Aber das dauert lange (ich glaube, es dauert lange, ein Groupby-Objekt zu durchlaufen). Ich werde es so ändern müssen, dass ich das ganze Groupby-Objekt in einem Durchlauf durchlaufe, aber ich frage mich, ob es in pandas einen eingebauten Weg gibt, dies irgendwie sauber zu tun.

Zum Beispiel habe ich etwas Ähnliches versucht wie

grouped.agg({'C_sum' : lambda x: x['C'].sum(),
             'C_std': lambda x: x['C'].std(),
             'D_sum' : lambda x: x['D'].sum()},
             'D_sumifC3': lambda x: x['D'][x['C'] == 3].sum(), ...)

aber wie erwartet erhalte ich eine KeyError (da die Schlüssel eine Spalte sein müssen, wenn agg von einem DataFrame aufgerufen wird).

Gibt es einen eingebauten Weg, um das zu tun, was ich tun möchte, oder die Möglichkeit, dass diese Funktionalität hinzugefügt wird, oder muss ich das Groupby manuell durchlaufen?

6 Stimmen

Wenn Sie diese Frage ab 2017 besuchen, sehen Sie bitte die Antwort untenum den idiomatischen Weg zu sehen, um mehrere Spalten zusammenzufassen. Die derzeit ausgewählte Antwort hat mehrere Veraltungen darin, nämlich dass Sie nicht mehr ein Wörterbuch von Wörterbüchern verwenden können, um Spalten im Ergebnis eines groupby umzubenennen.

611voto

Ted Petrou Punkte 56706

Die zweite Hälfte der derzeit akzeptierten Antwort ist veraltet und weist zwei Veraltungen auf. Erstens und am wichtigsten ist es nicht mehr möglich, ein Wörterbuch von Wörterbüchern an die agg groupby-Methode zu übergeben. Zweitens, verwenden Sie niemals .ix.

Wenn Sie mit zwei separaten Spalten gleichzeitig arbeiten möchten, würde ich vorschlagen, die apply-Methode zu verwenden, die implizit ein DataFrame an die angewendete Funktion übergibt. Verwenden wir ein ähnliches DataFrame wie das oben gezeigte

df = pd.DataFrame(np.random.rand(4,4), columns=list('abcd'))
df['group'] = [0, 0, 1, 1]
df

          a         b         c         d  group
0  0.418500  0.030955  0.874869  0.145641      0
1  0.446069  0.901153  0.095052  0.487040      0
2  0.843026  0.936169  0.926090  0.041722      1
3  0.635846  0.439175  0.828787  0.714123      1

Ein von Spaltennamen auf Aggregationsfunktionen abgebildetes Wörterbuch ist immer noch ein vollkommen gültiger Weg, um eine Aggregation durchzuführen.

df.groupby('group').agg({'a':['sum', 'max'], 
                         'b':'mean', 
                         'c':'sum', 
                         'd': lambda x: x.max() - x.min()})

              a                   b         c         d
            sum       max      mean       sum  
group                                                  
0      0.864569  0.446069  0.466054  0.969921  0.341399
1      1.478872  0.843026  0.687672  1.754877  0.672401

Wenn Ihnen dieser unansehnliche Lambda-Spaltenname nicht gefällt, können Sie eine normale Funktion verwenden und einen benutzerdefinierten Namen für das spezielle __name__-Attribut wie folgt bereitstellen:

def max_min(x):
    return x.max() - x.min()

max_min.__name__ = 'Max minus Min'

df.groupby('group').agg({'a':['sum', 'max'], 
                         'b':'mean', 
                         'c':'sum', 
                         'd': max_min})

              a                   b         c             d
            sum       max      mean       sum Max minus Min
group                                                      
0      0.864569  0.446069  0.466054  0.969921      0.341399
1      1.478872  0.843026  0.687672  1.754877      0.672401

Die Verwendung von apply und die Rückgabe eines Series

Wenn Sie nun mehrere Spalten haben, die zusammenwirken müssen, dann können Sie nicht agg verwenden, das implizit eine Series an die Aggregationsfunktion übergibt. Bei Verwendung von apply wird die gesamte Gruppe als DataFrame an die Funktion übergeben.

Ich empfehle, eine einzelne benutzerdefinierte Funktion zu erstellen, die eine Series aller Aggregationen zurückgibt. Verwenden Sie den Index der Series als Bezeichnungen für die neuen Spalten:

def f(x):
    d = {}
    d['a_sum'] = x['a'].sum()
    d['a_max'] = x['a'].max()
    d['b_mean'] = x['b'].mean()
    d['c_d_prodsum'] = (x['c'] * x['d']).sum()
    return pd.Series(d, index=['a_sum', 'a_max', 'b_mean', 'c_d_prodsum'])

df.groupby('group').apply(f)

         a_sum     a_max    b_mean  c_d_prodsum
group                                           
0      0.864569  0.446069  0.466054     0.173711
1      1.478872  0.843026  0.687672     0.630494

Wenn Sie MultiIndexes lieben, können Sie immer noch eine Series mit einem wie folgt zurückgeben:

    def f_mi(x):
        d = []
        d.append(x['a'].sum())
        d.append(x['a'].max())
        d.append(x['b'].mean())
        d.append((x['c'] * x['d']).sum())
        return pd.Series(d, index=[['a', 'a', 'b', 'c_d'], 
                                   ['sum', 'max', 'mean', 'prodsum']])

df.groupby('group').apply(f_mi)

              a                   b       c_d
            sum       max      mean   prodsum
group                                        
0      0.864569  0.446069  0.466054  0.173711
1      1.478872  0.843026  0.687672  0.630494

7 Stimmen

Dies ist die einzige Möglichkeit, die ich gefunden habe, um ein DataFrame über mehrere Spalteninputs gleichzeitig zu aggregieren (das obige Beispiel c_d)

3 Stimmen

Auf großen Datensätzen ist dies sehr langsam. Welche Ideen gibt es für effizientere Lösungen?

0 Stimmen

Tatsächlich ist der sauberste Weg, in Python, im R data.table-Paket schlägt es leicht in Bezug auf kürzere Syntax und schnellere Geschwindigkeit alles was Sie tun müssen ist einfach $$df[,.(sum(a), min(a), max(a), sum(c*d)), keyby = .(group)]$$

188voto

Zelazny7 Punkte 38896

Für den ersten Teil können Sie ein Dictionary von Spaltennamen für Schlüssel und eine Liste von Funktionen für die Werte übergeben:

In [28]: df
Out[28]:
          A         B         C         D         E  GRP
0  0.395670  0.219560  0.600644  0.613445  0.242893    0
1  0.323911  0.464584  0.107215  0.204072  0.927325    0
2  0.321358  0.076037  0.166946  0.439661  0.914612    1
3  0.133466  0.447946  0.014815  0.130781  0.268290    1

In [26]: f = {'A':['sum','mean'], 'B':['prod']}

In [27]: df.groupby('GRP').agg(f)
Out[27]:
            A                   B
          sum      mean      prod
GRP
0    0.719580  0.359790  0.102004
1    0.454824  0.227412  0.034060

UPDATE 1:

Weil die Aggregatfunktion auf Series arbeitet, gehen Verweise auf die andere Spaltennamen verloren. Um dies zu umgehen, können Sie auf das vollständige DataFrame verweisen und es mit den Gruppenindizes innerhalb der Lambda-Funktion indizieren.

Hier ist ein Workaround:

In [67]: f = {'A':['sum','mean'], 'B':['prod'], 'D': lambda g: df.loc[g.index].E.sum()}

In [69]: df.groupby('GRP').agg(f)
Out[69]:
            A                   B         D
          sum      mean      prod  
GRP
0    0.719580  0.359790  0.102004  1.170219
1    0.454824  0.227412  0.034060  1.182901

Hier besteht die resultierende 'D'-Spalte aus den summierten 'E'-Werten.

UPDATE 2:

Hier ist eine Methode, die ich denke, alles tut, was Sie verlangen. Erstellen Sie zuerst eine benutzerdefinierte Lambda-Funktion. Nachfolgend bezieht sich g auf die Gruppe. Beim Aggregieren wird g eine Series sein. Das Übergeben von g.index an df.ix[] wählt die aktuelle Gruppe aus df aus. Dann prüfe ich, ob die Spalte C kleiner als 0,5 ist. Die zurückgegebene boolesche Series wird an g[] übergeben, das nur die Zeilen auswählt, die den Kriterien entsprechen.

In [95]: cust = lambda g: g[df.loc[g.index]['C'] < 0.5].sum()

In [96]: f = {'A':['sum','mean'], 'B':['prod'], 'D': {'my name': cust}}

In [97]: df.groupby('GRP').agg(f)
Out[97]:
            A                   B         D
          sum      mean      prod   my name
GRP
0    0.719580  0.359790  0.102004  0.204072
1    0.454824  0.227412  0.034060  0.570441

0 Stimmen

Interessant, ich kann auch ein Dict von {funcname: func} als Werte übergeben anstatt von Listen, um meine benutzerdefinierten Namen beizubehalten. Aber in beiden Fällen kann ich keine lambda übergeben, die andere Spalten verwendet (wie lambda x: x['D'][x['C'] < 3].sum() oben: "KeyError: 'D'"). Irgendwelche Ideen, ob das möglich ist?

0 Stimmen

Ich habe versucht, genau das zu tun, und ich bekomme den Fehler KeyError: 'D'

0 Stimmen

Cool, Ich habe es mit df['A'].ix[g.index][df['C'] < 0].sum() zum Laufen gebracht. Das wird langsam ziemlich unübersichtlich, aber ich denke für die Lesbarkeit ist möglicherweise manuelles Schleifen bevorzugt, und ich bin mir nicht sicher, ob es eine Möglichkeit gibt, ihm meinen bevorzugten Namen im agg-Argument zu geben (anstelle von ). Ich habe die Hoffnung, dass jemand vielleicht einen einfacheren Weg kennt...

83voto

Erfan Punkte 36580

Pandas >= 0.25.0, benannte Aggregationen

Seit der pandas Version 0.25.0 oder höher entfernen wir uns von der auf Wörterbüchern basierenden Aggregation und Umbenennung, und bewegen uns hin zu benannten Aggregationen, die ein tuple akzeptieren. Jetzt können wir gleichzeitig aggregieren + umbenennen zu einem aussagekräftigeren Spaltennamen:

Beispiel:

df = pd.DataFrame(np.random.rand(4,4), columns=list('abcd'))
df['group'] = [0, 0, 1, 1]

          a         b         c         d  group
0  0.521279  0.914988  0.054057  0.125668      0
1  0.426058  0.828890  0.784093  0.446211      0
2  0.363136  0.843751  0.184967  0.467351      1
3  0.241012  0.470053  0.358018  0.525032      1

Wenden Sie GroupBy.agg mit benannten Aggregationen an:

df.groupby('group').agg(
             a_sum=('a', 'sum'),
             a_mean=('a', 'mean'),
             b_mean=('b', 'mean'),
             c_sum=('c', 'sum'),
             d_range=('d', lambda x: x.max() - x.min())
)

          a_sum    a_mean    b_mean     c_sum   d_range
group                                                  
0      0.947337  0.473668  0.871939  0.838150  0.320543
1      0.604149  0.302074  0.656902  0.542985  0.057681

6 Stimmen

Ich mag diese benannten Aggregationen, aber ich konnte nicht sehen, wie wir sie mit mehreren Spalten verwenden sollen.

0 Stimmen

Gute Frage, konnte das nicht herausfinden, bezweifle, dass dies möglich ist (noch). Ich habe ein Ticket dazu geöffnet. Ich werde meine Frage und dich auf dem Laufenden halten. Vielen Dank an @SimonWoodhead, dass du darauf hingewiesen hast.

1 Stimmen

Hast du Fortschritte gemacht, dies mit mehreren Spalten zu tun?? z.B. (['a', 'b'], 'sum')

56voto

r2evans Punkte 119331

Als Alternative (meistens im ästhetischen Sinne) zu der Antwort von Ted Petrou fand ich, dass ich eine etwas kompaktere Liste bevorzuge. Bitte ziehen Sie es nicht in Betracht, es zu akzeptieren, es ist nur ein sehr viel detaillierter Kommentar zu Teds Antwort, plus Code/Daten. Python/pandas ist nicht mein erstes/bestes Fachgebiet, aber ich finde dies lesbar:

df.groupby('group') \
  .apply(lambda x: pd.Series({
      'a_sum'       : x['a'].sum(),
      'a_max'       : x['a'].max(),
      'b_mean'      : x['b'].mean(),
      'c_d_prodsum' : (x['c'] * x['d']).sum()
  })
)

          a_sum     a_max    b_mean  c_d_prodsum
group                                           
0      0.530559  0.374540  0.553354     0.488525
1      1.433558  0.832443  0.460206     0.053313

Ich empfinde es eher wie dplyr pipes und data.table chain commands. Nicht, dass sie besser wären, sondern eher vertrauter für mich. (Ich erkenne zweifellos die Kraft und, für viele, die Präferenz von einer formalisierten Nutzung von def-Funktionen für solche Operationen. Dies ist nur eine Alternative, nicht unbedingt besser.)


Ich habe die Daten genauso generiert wie Ted, ich werde einen Seed für die Reproduzierbarkeit hinzufügen.

import numpy as np
np.random.seed(42)
df = pd.DataFrame(np.random.rand(4,4), columns=list('abcd'))
df['group'] = [0, 0, 1, 1]
df

          a         b         c         d  group
0  0.374540  0.950714  0.731994  0.598658      0
1  0.156019  0.155995  0.058084  0.866176      0
2  0.601115  0.708073  0.020584  0.969910      1
3  0.832443  0.212339  0.181825  0.183405      1

7 Stimmen

Ich mag diese Antwort am meisten. Das ist ähnlich wie dplyr Pipes in R.

2 Stimmen

Um dies vollständig zu machen, wie in Teds Antwort: Wenn Sie mehrere Indizes möchten, können Sie Tupel als Schlüssel für das Wörterbuch angeben, das Sie an pd.Series übergeben. Zum Beispiel ('a', 'sum') : x['a'].sum() anstelle von 'a_sum' : x['a'].sum()

0 Stimmen

Seit pandas 0.25.0 kann die benannte Aggregation aggregieren und umbenennen, ohne die Lambda-Funktion zu verwenden. Andere Antworten decken dies ab.

19voto

exan Punkte 2610

Neu in Version 0.25.0.

Um die aggregierte Spalte mit Kontrolle über die Ausgabespaltennamen zu unterstützen, akzeptiert pandas die spezielle Syntax in GroupBy.agg(), bekannt als “benannte Aggregation”, wobei

  • Die Schlüsselwörter die Ausgabespaltennamen sind
  • Die Werte sind Tupel, deren erstes Element die auszuwählende Spalte und das zweite Element die anzuwendende Aggregation ist. Pandas bietet das pandas.NamedAgg namedtuple mit den Feldern ['Spalte', 'aggfunc'] an, um klarer zu machen, welche Argumente es sind. Wie üblich kann die Aggregation ein Funktionsaufruf oder ein Alias sein.

    animals = pd.DataFrame({ ... 'art': ['Katze', 'Hund', 'Katze', 'Hund'], ... 'Höhe': [9.1, 6.0, 9.5, 34.0], ... 'Gewicht': [7.9, 7.5, 9.9, 198.0] ... })

    print(animals) art Höhe Gewicht 0 Katze 9.1 7.9 1 Hund 6.0 7.5 2 Katze 9.5 9.9 3 Hund 34.0 198.0

    print( ... animals ... .groupby('art') ... .agg( ... min_Höhe=pd.NamedAgg(column='Höhe', aggfunc='min'), ... max_Höhe=pd.NamedAgg(column='Höhe', aggfunc='max'), ... durchschnittliches_Gewicht=pd.NamedAgg(column='Gewicht', aggfunc=np.mean), ... ) ... ) min_Höhe max_Höhe durchschnittliches_Gewicht art
    Katze 9.1 9.5 8.90 Hund 6.0 34.0 102.75

pandas.NamedAgg ist nur ein namedtuple. Einfache Tupel sind ebenfalls erlaubt.

>>> print(
...     animals
...     .groupby('art')
...     .agg(
...         min_Höhe=('Höhe', 'min'),
...         max_Höhe=('Höhe', 'max'),
...         durchschnittliches_Gewicht=('Gewicht', np.mean),
...     )
... )
      min_Höhe  max_Höhe  durchschnittliches_Gewicht
art                                        
Katze          9.1         9.5            8.90
Hund          6.0        34.0          102.75

Zusätzliche Stichwortargumente werden nicht an die Aggregationsfunktionen weitergegeben. Es sollten nur Paare von (Spalte, aggfunc) als **kwargs übergeben werden. Wenn Ihre Aggregationsfunktion zusätzliche Argumente erfordert, wenden Sie diese teilweise mit functools.partial() an.

Benannte Aggregation ist auch für Series-Groupby-Aggregationen gültig. In diesem Fall erfolgt keine Spaltenauswahl, daher sind die Werte nur die Funktionen.

>>> print(
...     animals
...     .groupby('art')
...     .Höhe
...     .agg(
...         min_Höhe='min',
...         max_Höhe='max',
...     )
... )
      min_Höhe  max_Höhe
art                        
Katze          9.1         9.5
Hund          6.0        34.0

0 Stimmen

Mein nächster Kommentar ist ein Tipp, wie man ein Wörterbuch benannter Aggregationen verwendet. Es scheint mir jedoch nicht möglich zu sein, den Code schön im Kommentar zu formatieren, daher habe ich unten auch eine Antwort erstellt.

1 Stimmen

agg_dict = { "min_height": pd.NamedAgg(column='height', aggfunc='min'), "max_height": pd.NamedAgg(column='height', aggfunc='max'), "average_weight": pd.NamedAgg(column='weight', aggfunc=np.mean) } animals.groupby("kind").agg(**agg_dict)

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X