Grupo de pandas por suma acumulativa

93

Me gustaría agregar una columna de suma acumulativa a mi marco de datos Pandas para que:

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

se convierte en:

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

Probé varios combos de df.groupbyy df.agg(lambda x: cumsum(x))sin éxito.

kc2819
fuente
¿Está realmente seguro de que desea la agregación durante los días de la semana? Eso pierde el índice, y también la suma acumulada tiene menos sentido si hay varias semanas. Las respuestas de dmitry-andreev y @vjayky calculan cumsum durante la secuencia de días para cada nombre. Piense en cómo podría extenderse esto si también hubiera una columna de fecha, por la que las entradas podrían ordenarse antes de agrupar y agregar.
Elias Hasle

Respuestas:

89

Esto debería hacerlo, necesita groupby()dos veces:

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

Explicación:

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

El marco de datos resultante de la primera suma se indexa por 'name'y por 'day'. Puedes verlo imprimiendo

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

Al calcular la suma acumulada, desea hacerlo por 'name', correspondiente al primer índice (nivel 0).

Por último, utilice reset_indexpara que se repitan los nombres.

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
fuente
3
Gracias por la respuesta. Sin embargo, tuve algunas consultas: 1. ¿Puede explicar qué significa "nivel = [0]"? 2. Además, como puede ver, antes tenía números de fila en su marco de datos y estos números de fila desaparecen una vez que hace la suma acumulativa. ¿Hay alguna forma de recuperarlos?
user3694373
5
1), el número de índice tiene que ir, ya que los cumsums son de varias filas, como el segundo número, 40, es 10 + 20 + 10, ¿qué valor de índice debería obtener? 1, 2 o 3? Entonces, sigamos usando namey daycomo multiIndex, que tiene más sentido ( reset_index()para obtener un intíndice, si lo desea). 2), el level=[0]medio groupbyes operar por el 1er nivel de MultiIndex, a saber, la columna name.
CT Zhu
Gracias CT. Entendí eso más tarde e intenté reset_index () para resolver mi problema. ¡Gracias por la explicación detallada!
user3694373
4
Hay un error sutil: el primer valor groupby()predeterminado para ordenar las claves, por lo que si agrega una fila Jack-Thursday en la parte inferior del conjunto de datos de entrada, obtendrá resultados inesperados. Y como groupby()puedo trabajar con nombres de nivel, me parece df.groupby(['name', 'day'], sort=False).sum().groupby(by='name').cumsum().reset_index()menos críptico.
Nickolay
¿Cómo se cambia el nombre de la columna?
Jonathan Lam
47

Esto funciona en 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
fuente
Mostrar cómo volver a agregarlo al df es realmente útil. Intenté usar una transformación, pero eso no funcionó bien con cumsum ().
zerovector
2
Tenga en cuenta que esta respuesta (parece equivalente a la solución más simple de @vjayky ) no se agrega por namey dayantes de calcular la suma acumulada por name(nota: hay 2 filas para Jack + Tuesday en el resultado). Esto es lo que lo hace más simple que la respuesta de CT Zhu .
Nickolay
39

Modificación a la respuesta de @ Dmitry. Esto es más simple y funciona en 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
fuente
2
Esta parece ser la solución más simple si no necesita la agregación de dos pasos , como se solicita en la pregunta.
Nickolay
La única parte que no me gusta particularmente es que convirtió mi int dtype en un flotador.
Chris Farr
Esta debería ser la respuesta aceptada para el cumsum en la parte grupal. @ChrisFarr Parece que ya no se convierte para flotar para mí a partir de pandas 1.0.3.
Louis Yang
8

Deberías usar

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

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

Otra forma de hacerlo

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

ingrese la descripción de la imagen aquí

sushmit
fuente
3
Esto calcula un total acumulado global, en lugar de una suma separada para cada grupo por separado. Así que a Jill-Monday se le asigna un valor de 130 ( 90como la suma de todos los valores de Jack, + 40, el valor de Jill-Monday).
Nickolay
@Nickolay acaba de agregar otra respuesta, avíseme si funciona
sushmit
No estoy seguro de si calcula el total
acumulado
¿Por qué uso lambda x: x.cumsum () aquí, en lugar de pandas.series.cumsum ()?
Jinhua Wang
7

En lugar de df.groupby(by=['name','day']).sum().groupby(level=[0]).cumsum() (ver arriba) también puede hacer undf.set_index(['name', 'day']).groupby(level=0, as_index=False).cumsum()

  • df.groupby(by=['name','day']).sum() en realidad, solo mueve ambas columnas a un MultiIndex
  • as_index=False significa que no necesita llamar a reset_index después
Christoph
fuente
Gracias por publicar esto, ¡me ayudó a entender lo que está pasando aquí! Tenga en cuenta que groupby().sum()no solo está moviendo ambas columnas a MultiIndex, sino que también resume los dos valores para Jack + Tuesday. Y as_index=Falseno parece tener ningún efecto en este caso, ya que el índice ya se estableció antes de groupby. Y dado que groupby().cumsum()extrae el nombre / día de las columnas del marco de datos, debe agregar la columna numérica resultante al marco de datos original (como sugirieron vjayky y Dmitry), o mover el nombre / día al índice, y luego reiniciar_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

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)

Salida:

   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
fuente