Groupe de pandas par somme cumulée

93

Je voudrais ajouter une colonne de somme cumulée à mon dataframe Pandas afin que:

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

devient:

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

J'ai essayé divers combos df.groupbyet df.agg(lambda x: cumsum(x))en vain.

kc2819
la source
Êtes-vous vraiment sûr de vouloir une agrégation sur les jours de la semaine? Cela perd l'indice, et la somme cumulée a également moins de sens s'il y a plusieurs semaines. Les réponses de dmitry-andreev et @vjayky calcule le sperme sur la séquence de jours pour chaque nom à la place. Pensez à la façon dont cela pourrait être étendu s'il y avait aussi une colonne de date, par laquelle les entrées pourraient être triées avant le regroupement et l'agrégation.
Elias Hasle

Réponses:

89

Cela devrait le faire, besoin de groupby()deux fois:

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

Explication:

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

Le dataframe résultant de la première somme est indexé par 'name'et par 'day'. Vous pouvez le voir en imprimant

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

Lors du calcul de la somme cumulée, vous voulez le faire par 'name', correspondant au premier index (niveau 0).

Enfin, utilisez reset_indexpour répéter les noms.

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
la source
3
Merci d'avoir répondu. J'ai cependant quelques questions: 1. Pouvez-vous expliquer ce que signifie «niveau = [0]»? 2. De plus, comme vous pouvez le voir, vous aviez auparavant des numéros de ligne dans votre bloc de données et ces numéros de ligne disparaissent une fois que vous faites la somme cumulative. Y a-t-il un moyen de les récupérer?
user3694373
5
1), Le numéro d'index doit disparaître, car les cumuls proviennent de plusieurs lignes, comme le 2ème nombre, 40, est 10 + 20 + 10, quelle valeur d'index doit-il obtenir? 1, 2 ou 3? Alors, continuons à utiliser nameet dayas multiIndex, ce qui est plus logique ( reset_index()pour obtenir l' intindex, si vous le souhaitez). 2), le level=[0]moyen groupbyest de fonctionner par le 1er niveau de MultiIndex, à savoir la colonne name.
CT Zhu
Merci CT. J'ai compris cela plus tard et j'ai essayé reset_index () pour résoudre mon problème. Merci pour l'explication détaillée!
user3694373
4
Il y a un bug subtil: le premier par groupby()défaut consiste à trier les clés, donc si vous ajoutez une ligne Jack-Thursday au bas de l'ensemble de données d'entrée, vous obtiendrez des résultats inattendus. Et comme groupby()je peux travailler avec des noms de niveau, je trouve df.groupby(['name', 'day'], sort=False).sum().groupby(by='name').cumsum().reset_index()moins cryptique.
Nickolay
Comment renommer la colonne?
Jonathan Lam le
47

Cela fonctionne dans les 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
la source
Montrer comment l'ajouter au df est vraiment utile. J'ai essayé d'utiliser une transformation, mais cela ne jouait pas bien avec cumsum ().
zerovector
2
Notez que cette réponse (semble équivalente à la solution plus simple de @vjayky ) ne s'agrège pas avant nameet dayavant de calculer la somme cumulée par name(note: il y a 2 lignes pour Jack + mardi dans le résultat). C'est ce qui le rend plus simple que la réponse de CT Zhu .
Nickolay
39

Modification de la réponse de @ Dmitry. C'est plus simple et fonctionne dans les 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
la source
2
Cela semble être la solution la plus simple si vous n'avez pas besoin de l'agrégation en deux étapes , comme demandé dans la question.
Nickolay
La seule partie que je n'aime pas particulièrement, c'est qu'il a converti mon dtype int en un float.
Chris Farr le
Cela devrait être la réponse acceptée pour le sperme en groupe. @ChrisFarr Il ne semble plus se convertir en float pour moi depuis pandas 1.0.3.
Louis Yang
8

Tu devrais utiliser

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

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

Une autre façon de faire

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

entrez la description de l'image ici

sushmit
la source
3
Cela calcule un total cumulé global, au lieu d'une somme distincte pour chaque groupe séparément. Ainsi, Jill-Monday reçoit une valeur de 130 ( 90, en tant que somme de toutes les valeurs de Jack, + 40, la valeur de Jill-Monday).
Nickolay
@Nickolay vient d'ajouter une autre réponse, laissez-moi savoir si cela fonctionne
sushmit
Je ne suis pas sûr qu'il calcule le total cumulé global selon mon exemple, la ligne 3 obtient une valeur de 4
sushmit
Pourquoi utiliser lambda x: x.cumsum () ici, au lieu de pandas.series.cumsum ()?
Jinhua Wang
7

Au lieu de df.groupby(by=['name','day']).sum().groupby(level=[0]).cumsum() (voir ci-dessus), vous pouvez également faire undf.set_index(['name', 'day']).groupby(level=0, as_index=False).cumsum()

  • df.groupby(by=['name','day']).sum() déplace simplement les deux colonnes vers un MultiIndex
  • as_index=False signifie que vous n'avez pas besoin d'appeler reset_index par la suite
Christoph
la source
Merci d'avoir publié ceci, cela m'a aidé à comprendre ce qui se passe ici! Notez qu'il groupby().sum()ne s'agit pas simplement de déplacer les deux colonnes vers MultiIndex - cela résume également les deux valeurs pour Jack + Tuesday. Et as_index=Falsene semble pas avoir d'effet dans ce cas, puisque l'index était déjà défini avant le groupby. Et comme groupby().cumsum()nukes le nom / jour des colonnes de la trame de données, vous devez soit ajouter la colonne numérique résultante à la trame de données d'origine (comme vjayky et Dmitry suggéré), ou déplacer nom / jour vers index, et reset_index par la suite.
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

Code:

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)

Production:

   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
la source