Grupa pandy według sumy łącznej

93

Chciałbym dodać skumulowaną kolumnę sumy do mojej ramki danych Pandas, aby:

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

staje się:

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

Próbowałem różnych kombinacji df.groupbyi df.agg(lambda x: cumsum(x))bezskutecznie.

kc2819
źródło
Czy na pewno chcesz agregować dane w dni robocze? To powoduje utratę wskaźnika, a także skumulowana suma ma mniej sensu, jeśli jest wiele tygodni. Odpowiedzi udzielone przez dmitry-andreev i @vjayky zamiast tego obliczają sumę w ciągu dni dla każdego nazwiska. Pomyśl, jak można by to rozszerzyć, gdyby istniała również kolumna z datami, według której wpisy mogłyby być sortowane przed grupowaniem i agregacją.
Elias Hasle

Odpowiedzi:

89

Powinno to zrobić, potrzebujesz groupby()dwa razy:

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

Wyjaśnienie:

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

Ramka danych wynikająca z pierwszej sumy jest indeksowana przez 'name'i przez 'day'. Możesz to zobaczyć, drukując

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

Obliczając skumulowaną sumę, chcesz to zrobić 'name', odpowiadając pierwszemu indeksowi (poziom 0).

Na koniec użyj, reset_indexaby powtórzyć nazwy.

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
źródło
3
Dziękuję za odpowiedź. Miałem jednak kilka pytań: 1. Czy możesz wyjaśnić, co oznacza „level = [0]”? 2. Ponadto, jak widać, numery wierszy w ramce danych znajdowały się wcześniej i znikają po wykonaniu sumy skumulowanej. Czy jest sposób, aby je odzyskać?
user3694373
5
1), numer indeksu musi iść, ponieważ sumy pochodzą z wielu wierszy, np. Druga liczba, 40, to 10 + 20 + 10, jaką wartość indeksu powinien otrzymać? 1, 2 czy 3? Więc używajmy dalej namei dayas multiIndex, które mają większy sens ( reset_index()aby uzyskać intindeks, jeśli chcesz). 2), level=[0]oznacza groupbyto działanie na poziomie 1 MultiIndex, czyli kolumny name.
CT Zhu,
Dzięki CT. Zrozumiałem to później i spróbowałem reset_index () rozwiązać mój problem. Dzięki za szczegółowe wyjaśnienie!
user3694373
4
Występuje subtelny błąd: pierwszy groupby()domyślnie służy do sortowania kluczy, więc jeśli dodasz wiersz Jack-czwartek na dole zestawu danych wejściowych, otrzymasz nieoczekiwane wyniki. A ponieważ groupby()mogę pracować z nazwami poziomów, uważam, że jest df.groupby(['name', 'day'], sort=False).sum().groupby(by='name').cumsum().reset_index()mniej tajemniczy.
Nickolay
Jak zmienić nazwę kolumny?
Jonathan Lam
47

Działa to w pandach 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
źródło
Bardzo pomocne jest pokazanie, jak dodać go z powrotem do pliku df. Próbowałem użyć transformacji, ale nie grało to dobrze z cumsum ().
zerovector
2
Zwróć uwagę, że ta odpowiedź (wydaje się równoważna prostszemu rozwiązaniu @vjayky ) nie agreguje się przed obliczeniem skumulowanej sumy przez namei dayprzed obliczeniem name(uwaga: w wyniku są 2 wiersze dla Jacka + wtorek). To sprawia, że ​​jest to prostsze niż odpowiedź CT Zhu .
Nickolay
39

Modyfikacja odpowiedzi @ Dmitry'ego. Jest to prostsze i działa w pandach 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
źródło
2
Wydaje się, że jest to najprostsze rozwiązanie, jeśli nie potrzebujesz dwuetapowej agregacji , zgodnie z pytaniem.
Nickolay
Jedyną częścią, która mi się szczególnie nie podoba, jest to, że przekształcił mój typ int w zmiennoprzecinkowy.
Chris Farr
Powinna to być akceptowana odpowiedź dla sumy w części grupowej. @ChrisFarr Wydaje mi się, że od wersji pandy 1.0.3 nie konwertuje się już na float.
Louis Yang
8

powinieneś użyć

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

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

Inny sposób na zrobienie tego

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

wprowadź opis obrazu tutaj

sushmit
źródło
3
To oblicza globalną sumę bieżącą zamiast oddzielnej sumy dla każdej grupy oddzielnie. Zatem Jill-Monday otrzymuje wartość 130 ( 90jako suma wszystkich wartości Jacka, + 40, wartość Jill-Monday).
Nickolay
@Nickolay właśnie dodał kolejną odpowiedź, daj mi znać, czy działa
sushmit
Nie jestem pewien, czy oblicza globalną sumę bieżącą, jak w moim przykładowym wierszu 3 otrzymuje wartość 4
sushmit
Dlaczego używam tutaj lambda x: x.cumsum () zamiast pandas.series.cumsum ()?
Jinhua Wang
7

Zamiast df.groupby(by=['name','day']).sum().groupby(level=[0]).cumsum() (patrz powyżej) możesz również wykonać plikdf.set_index(['name', 'day']).groupby(level=0, as_index=False).cumsum()

  • df.groupby(by=['name','day']).sum() w rzeczywistości po prostu przenosi obie kolumny do MultiIndex
  • as_index=False oznacza, że ​​nie musisz później wywoływać reset_index
Christoph
źródło
Dzięki za opublikowanie tego, pomogło mi to zrozumieć, co się tutaj dzieje! Zwróć uwagę, że groupby().sum()nie tylko przenosi obie kolumny do MultiIndex - to także sumuje dwie wartości dla Jack + Tuesday. I as_index=Falsewydaje się, że nie ma to żadnego wpływu w tym przypadku, ponieważ indeks został już ustawiony przed groupby. A ponieważ groupby().cumsum()nuking nazwa / dzień z kolumn ramki danych, musisz albo dodać wynikową kolumnę liczbową do oryginalnej ramki danych (jak sugerowali vjayky i Dmitry), albo przenieść nazwę / dzień do indeksu, a następnie zresetować_index.
Nickolay
0

data.csv:

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

Kod:

import numpy as np
import pandas as pd

df = pd.read_csv('data.csv')
print(df)
df = df.groupby(['name', 'day'])['no'].sum().reset_index()
print(df)
df['cumsum'] = df.groupby(['name'])['no'].apply(lambda x: x.cumsum())
print(df)

Wynik:

   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
   name        day   no
0  Jack     Monday   10
1  Jack    Tuesday   30
2  Jack  Wednesday   50
3  Jill     Monday   40
4  Jill  Wednesday  110
   name        day   no  cumsum
0  Jack     Monday   10      10
1  Jack    Tuesday   30      40
2  Jack  Wednesday   50      90
3  Jill     Monday   40      40
4  Jill  Wednesday  110     150
Aaj Kaal
źródło