Wenden Sie mehrere Funktionen auf mehrere Groupby-Spalten an

221

Die Dokumente zeigen, wie Sie mehrere Funktionen gleichzeitig auf ein Groupby-Objekt anwenden, indem Sie ein Diktat mit den Namen der Ausgabespalten als Schlüssel verwenden:

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 Diktat auf ähnliche Weise an eine Gruppe von DataFrame übergeben wird, erwartet es, dass die Schlüssel die Spaltennamen sind, auf die die Funktion angewendet wird.

Ich möchte mehrere Funktionen auf mehrere Spalten anwenden (bestimmte Spalten werden jedoch mehrmals bearbeitet). Außerdem hängen einige Funktionen auf anderen Spalten in dem groupby Objekt (wie SUMIF Funktionen). Meine derzeitige Lösung besteht darin, Spalte für Spalte zu gehen und so etwas wie den obigen Code auszuführen, wobei Lambdas für Funktionen verwendet werden, die von anderen Zeilen abhängen. Dies dauert jedoch lange (ich denke, es dauert lange, bis ein Groupby-Objekt durchlaufen ist). Ich muss es so ändern, dass ich das gesamte groupby-Objekt in einem einzigen Lauf durchlaufe, aber ich frage mich, ob es in Pandas eine eingebaute Methode gibt, um dies etwas sauber zu machen.

Zum Beispiel habe ich so etwas versucht

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 bekomme ich einen KeyError (da die Schlüssel eine Spalte sein müssen, wenn aggsie von einem DataFrame aufgerufen werden).

Gibt es eine eingebaute Möglichkeit, das zu tun, was ich tun möchte, oder die Möglichkeit, dass diese Funktionalität hinzugefügt wird, oder muss ich die Gruppe nur manuell durchlaufen?

Vielen Dank

Bart
quelle
2
Wenn Sie 2017+ zu dieser Frage kommen, lesen Sie bitte die Antwort unten, um zu sehen, wie Sie mehrere Spalten auf idiomatische Weise zusammenfassen können. Die aktuell ausgewählte Antwort enthält mehrere Verwerfungen, nämlich, dass Sie kein Wörterbuch mit Wörterbüchern mehr verwenden können, um Spalten im Ergebnis eines Groupby umzubenennen.
Ted Petrou

Antworten:

282

Die zweite Hälfte der derzeit akzeptierten Antwort ist veraltet und weist zwei Abwertungen auf. Erstens und vor allem können Sie kein Wörterbuch mit Wörterbüchern mehr an die agggroupby-Methode übergeben. Zweitens nie verwenden .ix.

Wenn Sie mit zwei separaten Spalten gleichzeitig arbeiten möchten, würde ich die Verwendung der applyMethode vorschlagen, die implizit einen DataFrame an die angewendete Funktion übergibt. Verwenden wir einen ähnlichen Datenrahmen wie den von oben

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 Wörterbuch, das von Spaltennamen auf Aggregationsfunktionen abgebildet wird, ist immer noch eine perfekte Möglichkeit, 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  <lambda>
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 hässliche Lambda-Spaltenname nicht gefällt, können Sie eine normale Funktion verwenden und dem speziellen __name__Attribut wie folgt einen benutzerdefinierten Namen geben :

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

Verwenden applyund Zurückgeben einer Serie

Wenn Sie mehrere Spalten hatten, die miteinander interagieren mussten, können Sie diese nicht verwenden agg, wodurch implizit eine Reihe an die Aggregationsfunktion übergeben wird. Bei Verwendung applyder gesamten Gruppe als DataFrame wird an die Funktion übergeben.

Ich empfehle, eine einzelne benutzerdefinierte Funktion zu erstellen, die eine Reihe aller Aggregationen zurückgibt. Verwenden Sie den Serienindex als Beschriftung 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 in MultiIndexes verliebt sind, können Sie dennoch eine Serie mit einer solchen 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
Ted Petrou
quelle
3
Ich mag das Muster, eine Funktion zu verwenden, die eine Reihe zurückgibt. Sehr gepflegt.
Stephen McAteer
2
Dies ist die einzige Möglichkeit, einen Datenrahmen über mehrere Spalteneingaben gleichzeitig zu aggregieren (das obige c_d-Beispiel)
Blake
2
Ich bin verwirrt von den Ergebnissen, sollte die Summe ainnerhalb der Gruppe 0nicht sein 0.418500 + 0.446069 = 0.864569? Gleiches gilt für andere Zellen, die Zahlen scheinen sich nicht zu summieren. Könnte es sein, dass in den folgenden Beispielen ein etwas anderer zugrunde liegender Datenrahmen verwendet wurde?
Slackline
Ich verwende häufig .size () mit einem groupby, um die Anzahl der Datensätze anzuzeigen. Gibt es eine Möglichkeit, dies mit der agg: dict-Methode zu tun? Ich verstehe, dass ich ein bestimmtes Feld zählen könnte, aber ich würde es vorziehen, wenn die Zählung feldunabhängig ist.
Chris Decker
1
@ Slackline ja. Ich habe es gerade getestet und es funktioniert gut. Ted muss den Frame nur ein paar Mal erstellt haben und da er über die Zufallszahlengenerierung erstellt wurde, waren die df-Daten zur tatsächlichen Generierung der Daten anders als die letztendlich in den Berechnungen verwendeten
Lucas H
166

Im ersten Teil können Sie ein Diktat mit Spaltennamen für Schlüssel und eine Liste mit 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:

Da die Aggregatfunktion für Serien funktioniert, gehen Verweise auf die anderen Spaltennamen verloren. Um dies zu umgehen, können Sie auf den vollständigen Datenrahmen verweisen und ihn mithilfe der Gruppenindizes innerhalb der Lambda-Funktion indizieren.

Hier ist eine hackige Problemumgehung:

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  <lambda>
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, von der ich denke, dass sie alles tut, was Sie verlangen. Erstellen Sie zuerst eine benutzerdefinierte Lambda-Funktion. Unten verweist g auf die Gruppe. Bei der Aggregation ist g eine Reihe. Übergeben g.indexan df.ix[]wählt die aktuelle Gruppe aus df aus. Ich teste dann, ob Spalte C kleiner als 0,5 ist. Die zurückgegebene boolesche Reihe wird übergeben, an g[]die nur die Zeilen ausgewählt werden, die die Kriterien erfüllen.

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
Zelazny7
quelle
Interessanterweise kann ich {funcname: func}anstelle von Listen auch ein Diktat als Werte übergeben, um meine benutzerdefinierten Namen beizubehalten. In beiden Fällen kann ich jedoch keine übergeben lambda, die andere Spalten verwendet (wie lambda x: x['D'][x['C'] < 3].sum()oben: "KeyError: 'D'"). Irgendeine Idee, ob das möglich ist?
Bart
Ich habe versucht, genau das zu tun, und ich bekomme den FehlerKeyError: 'D'
Zelazny7
Cool, ich habe es zum Arbeiten df['A'].ix[g.index][df['C'] < 0].sum(). Dies wird jedoch langsam ziemlich chaotisch - ich denke, aus Gründen der Lesbarkeit ist eine manuelle Schleife vorzuziehen, und ich bin mir nicht sicher, ob es eine Möglichkeit gibt, dem aggArgument meinen bevorzugten Namen zu geben (anstelle von <lambda>). Ich hoffe, dass jemand einen einfacheren Weg kennt ...
Bart
3
Sie können ein Diktat für den Spaltenwert übergeben, wodurch {'D': {'my name':lambda function}}der innere Diktatschlüssel zum Spaltennamen wird.
Zelazny7
1
Ich glaube, dass Pandas jetzt mehrere Funktionen unterstützt, die auf einen gruppierten Datenrahmen angewendet werden: pandas.pydata.org/pandas-docs/stable/…
IanS
22

Als Alternative (hauptsächlich in Bezug auf Ästhetik) zu Ted Petrous Antwort fand ich, dass ich eine etwas kompaktere Auflistung bevorzugte. Bitte akzeptieren Sie es nicht, es ist nur ein viel detaillierterer Kommentar zu Teds Antwort sowie Code / Daten. Python / Pandas ist nicht mein erstes / bestes, aber ich fand das gut zu lesen:

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 finde es eher wie dplyrPfeifen und data.tableverkettete Befehle. Um nicht zu sagen, dass sie besser sind, mir nur vertrauter. (Ich erkenne zweifellos die Macht und für viele die Präferenz, formalisierte defFunktionen für diese Art von Operationen zu verwenden. Dies ist nur eine Alternative, nicht unbedingt besser.)


Ich habe Daten auf die gleiche Weise wie Ted generiert. Ich werde einen Startwert 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
r2evans
quelle
2
Diese Antwort gefällt mir am besten. Dies ist ähnlich zu dplyr Rohren in R.
Renhuai
18

Pandas >= 0.25.0, benannte Aggregationen

Seit der Pandas-Version 0.25.0oder höher entfernen wir uns von der wörterbuchbasierten Aggregation und Umbenennung und gehen zu benannten Aggregationen über, die a akzeptieren tuple. Jetzt können wir gleichzeitig einen informativeren Spaltennamen zusammenfassen und umbenennen:

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

Bewerben Sie sich GroupBy.aggmit benannter Aggregation:

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
Erfan
quelle
Ich mag diese benannten Aggregationen, aber ich konnte nicht sehen, wie wir sie mit mehreren Spalten verwenden sollen.
Simon Woodhead
Gute Frage, konnte das nicht herausfinden, bezweifle, dass dies (noch) möglich ist. Ich habe ein Ticket dafür geöffnet . Ich werde meine Frage auf dem Laufenden halten. Vielen Dank für den Hinweis auf @SimonWoodhead
Erfan
4

Neu in Version 0.25.0.

Um die spaltenspezifische Aggregation mit Kontrolle über die Namen der Ausgabespalten zu unterstützen, akzeptiert pandas die spezielle Syntax in GroupBy.agg () , die als "benannte Aggregation" bezeichnet wird

  • Die Schlüsselwörter sind die Namen der Ausgabespalten
  • Die Werte sind Tupel, deren erstes Element die auszuwählende Spalte und das zweite Element die auf diese Spalte anzuwendende Aggregation ist. Pandas stellt dem pandas.NamedAgg namedtuple die Felder ['column', 'aggfunc'] zur Verfügung, um die Argumente klarer zu machen. Wie üblich kann die Aggregation ein aufrufbarer oder ein String-Alias ​​sein.
    In [79]: animals = pd.DataFrame({'kind': ['cat', 'dog', 'cat', 'dog'],
       ....:                         'height': [9.1, 6.0, 9.5, 34.0],
       ....:                         'weight': [7.9, 7.5, 9.9, 198.0]})
       ....: 

    In [80]: animals
    Out[80]: 
      kind  height  weight
    0  cat     9.1     7.9
    1  dog     6.0     7.5
    2  cat     9.5     9.9
    3  dog    34.0   198.0

    In [81]: animals.groupby("kind").agg(
       ....:     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),
       ....: )
       ....: 
    Out[81]: 
          min_height  max_height  average_weight
    kind                                        
    cat          9.1         9.5            8.90
    dog          6.0        34.0          102.75

pandas.NamedAgg ist nur ein benanntes Tupel. Auch einfache Tupel sind erlaubt.

    In [82]: animals.groupby("kind").agg(
       ....:     min_height=('height', 'min'),
       ....:     max_height=('height', 'max'),
       ....:     average_weight=('weight', np.mean),
       ....: )
       ....: 
    Out[82]: 
          min_height  max_height  average_weight
    kind                                        
    cat          9.1         9.5            8.90
    dog          6.0        34.0          102.75

Zusätzliche Schlüsselwortargumente werden nicht an die Aggregationsfunktionen übergeben. Nur Paare von (column, aggfunc) sollten als ** kwargs übergeben werden. Wenn Ihre Aggregationsfunktionen zusätzliche Argumente erfordern, wenden Sie diese teilweise mit functools.partial () an.

Die benannte Aggregation gilt auch für Seriengruppen-Aggregationen. In diesem Fall gibt es keine Spaltenauswahl, daher sind die Werte nur die Funktionen.

    In [84]: animals.groupby("kind").height.agg(
       ....:     min_height='min',
       ....:     max_height='max',
       ....: )
       ....: 
    Out[84]: 
          min_height  max_height
    kind                        
    cat          9.1         9.5
    dog          6.0        34.0
exan
quelle
3

Teds Antwort ist erstaunlich. Ich habe letztendlich eine kleinere Version davon verwendet, falls jemand interessiert ist. Nützlich, wenn Sie nach einer Aggregation suchen, die von Werten aus mehreren Spalten abhängt:

Erstellen Sie einen Datenrahmen

df=pd.DataFrame({'a': [1,2,3,4,5,6], 'b': [1,1,0,1,1,0], 'c': ['x','x','y','y','z','z']})


   a  b  c
0  1  1  x
1  2  1  x
2  3  0  y
3  4  1  y
4  5  1  z
5  6  0  z

Gruppieren und Aggregieren mit Apply (unter Verwendung mehrerer Spalten)

df.groupby('c').apply(lambda x: x['a'][(x['a']>1) & (x['b']==1)].mean())

c
x    2.0
y    4.0
z    5.0

Gruppieren und Aggregieren mit Aggregat (unter Verwendung mehrerer Spalten)

Ich mag diesen Ansatz, da ich immer noch Aggregat verwenden kann. Vielleicht lassen mich die Leute wissen, warum eine Anwendung erforderlich ist, um bei Aggregationen für Gruppen auf mehrere Spalten zuzugreifen.

Es scheint jetzt offensichtlich, aber solange Sie die interessierende Spalte nicht direkt nach dem Gruppieren auswählen , haben Sie innerhalb Ihrer Aggregationsfunktion Zugriff auf alle Spalten des Datenrahmens.

Nur Zugriff auf die ausgewählte Spalte

df.groupby('c')['a'].aggregate(lambda x: x[x>1].mean())

Zugriff auf alle Spalten, da die Auswahl immerhin die Magie ist

df.groupby('c').aggregate(lambda x: x[(x['a']>1) & (x['b']==1)].mean())['a']

oder ähnlich

df.groupby('c').aggregate(lambda x: x['a'][(x['a']>1) & (x['b']==1)].mean())

Ich hoffe das hilft.

Campo
quelle