Pandas gruppieren sich nach kumulierter Summe

85

Ich möchte meinem Pandas-Datenrahmen eine kumulative Summenspalte hinzufügen, damit:

name | day       | no
-----|-----------|----
Jack | Monday    | 10
Jack | Tuesday   | 20
Jack | Tuesday   | 10
Jack | Wednesday | 50
Jill | Monday    | 40
Jill | Wednesday | 110

wird:

Jack | Monday     | 10  | 10
Jack | Tuesday    | 30  | 40
Jack | Wednesday  | 50  | 90
Jill | Monday     | 40  | 40
Jill | Wednesday  | 110 | 150

Ich habe verschiedene Kombinationen ausprobiert df.groupbyund df.agg(lambda x: cumsum(x))ohne Erfolg.

kc2819
quelle
Sind Sie wirklich sicher, dass Sie eine Aggregation über Wochentage wünschen? Das verliert den Index und auch die kumulierte Summe ist weniger sinnvoll, wenn es mehrere Wochen gibt. Die Antworten von dmitry-andreev und @vjayky berechnen stattdessen die Cumsum über die Folge von Tagen für jeden Namen. Überlegen Sie, wie dies erweitert werden könnte, wenn es auch eine Datumsspalte gäbe, nach der die Einträge sortiert werden könnten, bevor Sie sie gruppieren und aggregieren.
Elias Hasle

Antworten:

85

Dies sollte es tun, groupby()zweimal brauchen :

df.groupby(['name', 'day']).sum() \
  .groupby(level=0).cumsum().reset_index()

Erläuterung:

print(df)
   name        day   no
0  Jack     Monday   10
1  Jack    Tuesday   20
2  Jack    Tuesday   10
3  Jack  Wednesday   50
4  Jill     Monday   40
5  Jill  Wednesday  110

# sum per name/day
print( df.groupby(['name', 'day']).sum() )
                 no
name day           
Jack Monday      10
     Tuesday     30
     Wednesday   50
Jill Monday      40
      Wednesday  110

# cumulative sum per name/day
print( df.groupby(['name', 'day']).sum() \
         .groupby(level=0).cumsum() )
                 no
name day           
Jack Monday      10
     Tuesday     40
     Wednesday   90
Jill Monday      40
     Wednesday  150

Der aus der ersten Summe resultierende Datenrahmen wird nach 'name'und nach indiziert 'day'. Sie können es durch Drucken sehen

df.groupby(['name', 'day']).sum().index 

Wenn Sie die kumulative Summe berechnen, möchten Sie dies 'name'entsprechend dem ersten Index (Ebene 0) tun .

Verwenden Sie reset_indexzum Schluss, um die Namen wiederholen zu lassen.

df.groupby(['name', 'day']).sum().groupby(level=0).cumsum().reset_index()

   name        day   no
0  Jack     Monday   10
1  Jack    Tuesday   40
2  Jack  Wednesday   90
3  Jill     Monday   40
4  Jill  Wednesday  150
CT Zhu
quelle
3
Danke für die Antwort. Ich hatte jedoch einige Fragen: 1. Können Sie bitte erklären, was 'level = [0]' bedeutet? 2. Wie Sie sehen, hatten Sie zuvor Zeilennummern in Ihrem Datenrahmen, und diese Zeilennummern verschwinden, sobald Sie die kumulative Summe erstellt haben. Gibt es eine Möglichkeit, sie zurück zu haben?
user3694373
5
1), Die Indexnummer muss gehen, da die Cumsums aus mehreren Zeilen stammen, wie die 2. Nummer, 40, ist 10 + 20 + 10, welchen Indexwert sollte sie erhalten? 1, 2 oder 3? Verwenden wir also weiterhin nameund dayals multiIndex, was sinnvoller ist ( reset_index()um den intIndex zu erhalten , falls gewünscht). 2) ist das level=[0]Mittel, groupbyum durch die 1. Ebene der MultiIndexSpalte zu arbeiten name.
CT Zhu
Danke CT. Ich habe das später verstanden und versucht, mit reset_index () mein Problem zu lösen. Danke für die ausführliche Erklärung!
user3694373
4
Es gibt einen subtilen Fehler: Der erste groupby()Standard ist das Sortieren der Schlüssel. Wenn Sie also eine Jack-Thursday-Zeile am unteren Rand des Eingabedatensatzes hinzufügen, erhalten Sie unerwartete Ergebnisse. Und da groupby()ich mit df.groupby(['name', 'day'], sort=False).sum().groupby(by='name').cumsum().reset_index()Levelnamen arbeiten kann finde ich weniger kryptisch.
Nickolay
Wie benennt man die Spalte um?
Jonathan Lam
45

Dies funktioniert in Pandas 0.16.2

In[23]: print df
        name          day   no
0      Jack       Monday    10
1      Jack      Tuesday    20
2      Jack      Tuesday    10
3      Jack    Wednesday    50
4      Jill       Monday    40
5      Jill    Wednesday   110
In[24]: df['no_cumulative'] = df.groupby(['name'])['no'].apply(lambda x: x.cumsum())
In[25]: print df
        name          day   no  no_cumulative
0      Jack       Monday    10             10
1      Jack      Tuesday    20             30
2      Jack      Tuesday    10             40
3      Jack    Wednesday    50             90
4      Jill       Monday    40             40
5      Jill    Wednesday   110            150
Dmitry Andreev
quelle
Es ist sehr hilfreich zu zeigen, wie man es wieder zum df hinzufügt. Ich habe versucht, eine Transformation zu verwenden, aber das hat mit cumsum () nicht gut gespielt.
Zerovector
1
Beachten Sie, dass diese Antwort (scheint der einfacheren Lösung von @vjayky zu entsprechen ) nicht nach nameund dayvor der Berechnung der kumulierten Summe nach aggregiert wird name(Hinweis: Das Ergebnis enthält 2 Zeilen für Jack + Tuesday). Dies macht es einfacher als die Antwort von CT Zhu .
Nickolay
35

Änderung der Antwort von @ Dmitry. Dies ist einfacher und funktioniert in Pandas 0.19.0:

print(df) 

 name        day   no
0  Jack     Monday   10
1  Jack    Tuesday   20
2  Jack    Tuesday   10
3  Jack  Wednesday   50
4  Jill     Monday   40
5  Jill  Wednesday  110

df['no_csum'] = df.groupby(['name'])['no'].cumsum()

print(df)
   name        day   no  no_csum
0  Jack     Monday   10       10
1  Jack    Tuesday   20       30
2  Jack    Tuesday   10       40
3  Jack  Wednesday   50       90
4  Jill     Monday   40       40
5  Jill  Wednesday  110      150
vjayky
quelle
2
Dies scheint die einfachste Lösung zu sein, wenn Sie die in der Frage angeforderte zweistufige Aggregation nicht benötigen .
Nickolay
Der einzige Teil, den ich nicht besonders mag, ist, dass er meinen int-Typ in einen Float konvertiert hat.
Chris Farr
8

du solltest benutzen

df['cum_no'] = df.no.cumsum()

http://pandas.pydata.org/pandas-docs/version/0.19.2/generated/pandas.DataFrame.cumsum.html

Ein anderer Weg, es zu tun

import pandas as pd
df = pd.DataFrame({'C1' : ['a','a','a','b','b'],
           'C2' : [1,2,3,4,5]})
df['cumsum'] = df.groupby(by=['C1'])['C2'].transform(lambda x: x.cumsum())
df

Geben Sie hier die Bildbeschreibung ein

Sushmit
quelle
2
Dies berechnet eine globale laufende Summe anstelle einer separaten Summe für jede Gruppe separat. Jill-Monday erhält also einen Wert von 130 ( 90als Summe aller Jack-Werte + 40den Wert für Jill-Monday).
Nickolay
@ Nickolay hat gerade eine weitere Antwort hinzugefügt. Lassen Sie mich wissen, ob es funktioniert
Sushmit
Ich bin nicht sicher, ob es die globale laufende Summe gemäß meinem Beispiel berechnet. Zeile 3 erhält einen Wert von 4
Sushmit
Warum verwende ich hier lambda x: x.cumsum () anstelle von pandas.series.cumsum ()?
Jinhua Wang
7

Anstelle von df.groupby(by=['name','day']).sum().groupby(level=[0]).cumsum() (siehe oben) können Sie auch einedf.set_index(['name', 'day']).groupby(level=0, as_index=False).cumsum()

  • df.groupby(by=['name','day']).sum() verschiebt eigentlich nur beide Spalten in einen MultiIndex
  • as_index=False bedeutet, dass Sie reset_index danach nicht mehr aufrufen müssen
Christoph
quelle
Vielen Dank für die Veröffentlichung, es hat mir geholfen zu verstehen, was hier los ist! Beachten Sie, dass groupby().sum()nicht nur beide Spalten in MultiIndex verschoben werden, sondern auch die beiden Werte für Jack + Tuesday zusammengefasst werden. Und as_index=Falsescheint in diesem Fall keine Wirkung zu haben, da der Index bereits vor dem gesetzt wurde groupby. Und da groupby().cumsum()der Name / Tag aus den Spalten des Datenrahmens entfernt wird, müssen Sie entweder die resultierende numerische Spalte zum ursprünglichen Datenrahmen hinzufügen (wie von vjayky und Dmitry vorgeschlagen) oder den Namen / Tag in den Index verschieben und anschließend den Index zurücksetzen.
Nickolay