Pandas groupby soma cumulativa

97

Eu gostaria de adicionar uma coluna de soma cumulativa ao meu dataframe do Pandas para que:

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

torna-se:

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

Tentei vários combos de df.groupbye df.agg(lambda x: cumsum(x))sem sucesso.

kc2819
fonte
Tem certeza de que deseja a agregação durante os dias da semana? Isso perde o índice e também a soma cumulativa faz menos sentido se houver várias semanas. As respostas de dmitry-andreev e @vjayky calculam cumsum sobre a sequência de dias para cada nome. Pense em como isso poderia ser estendido se também houvesse uma coluna de data, pela qual as entradas pudessem ser classificadas antes do agrupamento e agregação.
Elias Hasle

Respostas:

93

Isso deve bastar, preciso groupby()duas vezes:

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

Explicação:

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

O dataframe resultante da primeira soma é indexado por 'name'e por 'day'. Você pode ver isso imprimindo

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

Ao calcular a soma cumulativa, você deseja fazer isso por 'name', correspondendo ao primeiro índice (nível 0).

Finalmente, use reset_indexpara repetir os nomes.

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
fonte
3
Obrigado pela resposta. Eu tive algumas dúvidas: 1. Você pode explicar o que 'level = [0]' significa? 2. Além disso, como você pode ver, você tinha números de linha em seu quadro de dados antes e esses números de linha desaparecem assim que você faz a soma cumulativa. Existe uma maneira de tê-los de volta?
user3694373
5
1), O número do índice tem que ir, já que os cumsums são de várias linhas, como o segundo número, 40, é 10 + 20 + 10, qual valor de índice ele deve obter? 1, 2 ou 3? Então, vamos continuar usando namee dayas multiIndex, o que faz mais sentido ( reset_index()para obter o intíndice, se desejar). 2), o level=[0]meio groupbyé operar pelo 1º nível de MultiIndex, nomeadamente coluna name.
CT Zhu,
Obrigado CT. Eu entendi isso mais tarde e tentei reset_index () para resolver meu problema. Obrigado pela explicação detalhada!
user3694373
4
Há um bug sutil: o primeiro groupby()padrão é classificar as chaves, portanto, se você adicionar uma linha Jack-Thursday na parte inferior do conjunto de dados de entrada, obterá resultados inesperados. E como groupby()posso trabalhar com nomes de níveis, acho df.groupby(['name', 'day'], sort=False).sum().groupby(by='name').cumsum().reset_index()menos enigmático.
Nickolay
Como você renomeia a coluna?
Jonathan Lam
48

Isso funciona no 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
fonte
Mostrar como adicioná-lo de volta ao df é muito útil. Tentei usar uma transformação, mas não funcionou bem com cumsum ().
zerovector
2
Observe que esta resposta (parece equivalente à solução mais simples de @vjayky ) não agrega por namee dayantes de calcular a soma cumulativa por name(nota: há 2 linhas para Jack + terça-feira no resultado). Isso é o que o torna mais simples do que a resposta de CT Zhu .
Nickolay
44

Modificação da resposta de @Dmitry. Isso é mais simples e funciona no 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
fonte
2
Esta parece ser a solução mais simples se você não precisar da agregação em duas etapas , conforme solicitado na pergunta.
Nickolay
A única parte que particularmente não gosto é que ele converteu meu int dtype em um flutuador.
Chris Farr
Esta deve ser a resposta aceita para o cumsum na parte do grupo. @ChrisFarr Não parece mais converter para float para mim a partir do pandas 1.0.3.
Louis Yang
8

você deveria usar

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

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

Outra maneira de fazer isso

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

insira a descrição da imagem aqui

sushmit
fonte
3
Isso calcula um total geral global, em vez de uma soma separada para cada grupo separadamente. Portanto, Jill-Monday recebe um valor de 130 ( 90como a soma de todos os valores de Jack, + 40, o valor de Jill-Monday).
Nickolay
@Nickolay acabou de adicionar outra resposta, diga-me se funciona
sushmit
Não tenho certeza se ele calcula o total global em execução conforme meu exemplo, a linha 3 obtém um valor de 4
sushmit
Por que uso lambda x: x.cumsum () aqui, em vez de pandas.series.cumsum ()?
Jinhua Wang
7

Em vez de df.groupby(by=['name','day']).sum().groupby(level=[0]).cumsum() (veja acima), você também pode fazer umdf.set_index(['name', 'day']).groupby(level=0, as_index=False).cumsum()

  • df.groupby(by=['name','day']).sum() está na verdade apenas movendo ambas as colunas para um MultiIndex
  • as_index=False significa que você não precisa chamar reset_index depois
Christoph
fonte
Obrigado por postar isso, me ajudou a entender o que está acontecendo aqui! Observe que groupby().sum()não se trata apenas de mover ambas as colunas para MultiIndex - também soma os dois valores de Jack + Tuesday. E as_index=Falsenão parece ter nenhum efeito neste caso, uma vez que o índice já foi definido antes de groupby. E já que groupby().cumsum()nukes o nome / dia das colunas do quadro de dados, você deve adicionar a coluna numérica resultante ao quadro de dados original (como vjayky e Dmitry sugeriram) ou mover nome / dia para o índice e reset_index depois.
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

Código:

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)

Resultado:

   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
fonte