¿Cómo creo una nueva columna a partir de la salida de pandas groupby (). Sum ()?

82

Intentando crear una nueva columna a partir del groupbycálculo. En el siguiente código, obtengo los valores calculados correctos para cada fecha (vea el grupo a continuación) pero cuando intento crear una nueva columna ( df['Data4']) con ella, obtengo NaN. Así que estoy tratando de crear una nueva columna en el marco de datos con la suma de Data3todas las fechas y aplicarla a cada fila de fechas. Por ejemplo, 2015-05-08 está en 2 filas (el total es 50 + 5 = 55) y en esta nueva columna me gustaría tener 55 en ambas filas.

import pandas as pd
import numpy as np
from pandas import DataFrame

df = pd.DataFrame({
    'Date' : ['2015-05-08', '2015-05-07', '2015-05-06', '2015-05-05', '2015-05-08', '2015-05-07', '2015-05-06', '2015-05-05'], 
    'Sym'  : ['aapl', 'aapl', 'aapl', 'aapl', 'aaww', 'aaww', 'aaww', 'aaww'], 
    'Data2': [11, 8, 10, 15, 110, 60, 100, 40],
    'Data3': [5, 8, 6, 1, 50, 100, 60, 120]
})

group = df['Data3'].groupby(df['Date']).sum()

df['Data4'] = group
fe ner
fuente

Respuestas:

189

Si desea usar transformesto, devolverá una Serie con el índice alineado con el df para que luego pueda agregarlo como una nueva columna:

In [74]:

df = pd.DataFrame({'Date': ['2015-05-08', '2015-05-07', '2015-05-06', '2015-05-05', '2015-05-08', '2015-05-07', '2015-05-06', '2015-05-05'], 'Sym': ['aapl', 'aapl', 'aapl', 'aapl', 'aaww', 'aaww', 'aaww', 'aaww'], 'Data2': [11, 8, 10, 15, 110, 60, 100, 40],'Data3': [5, 8, 6, 1, 50, 100, 60, 120]})
​
df['Data4'] = df['Data3'].groupby(df['Date']).transform('sum')
df
Out[74]:
   Data2  Data3        Date   Sym  Data4
0     11      5  2015-05-08  aapl     55
1      8      8  2015-05-07  aapl    108
2     10      6  2015-05-06  aapl     66
3     15      1  2015-05-05  aapl    121
4    110     50  2015-05-08  aaww     55
5     60    100  2015-05-07  aaww    108
6    100     60  2015-05-06  aaww     66
7     40    120  2015-05-05  aaww    121
EdChum
fuente
¿Qué sucede si tenemos un segundo groupby como aquí: stackoverflow.com/a/40067099/281545
Mr_and_Mrs_D
@Mr_and_Mrs_D tendría que restablecer el índice y realizar una combinación izquierda en las columnas comunes en ese caso para volver a agregar la columna
EdChum
10
Alternativamente, uno puede usar df.groupby('Date')['Data3'].transform('sum')(que encuentro un poco más fácil de recordar).
Cleb
39

¿Cómo creo una nueva columna con Groupby (). Sum ()?

Hay dos formas: una sencilla y la otra un poco más interesante.


El favorito de todos: GroupBy.transform()con'sum'

La respuesta de @Ed Chum se puede simplificar un poco. Llame en DataFrame.groupbylugar de Series.groupby. Esto da como resultado una sintaxis más simple.

# The setup.
df[['Date', 'Data3']]

         Date  Data3
0  2015-05-08      5
1  2015-05-07      8
2  2015-05-06      6
3  2015-05-05      1
4  2015-05-08     50
5  2015-05-07    100
6  2015-05-06     60
7  2015-05-05    120

df.groupby('Date')['Data3'].transform('sum')

0     55
1    108
2     66
3    121
4     55
5    108
6     66
7    121
Name: Data3, dtype: int64 

Es un poco más rápido

df2 = pd.concat([df] * 12345)

%timeit df2['Data3'].groupby(df['Date']).transform('sum')
%timeit df2.groupby('Date')['Data3'].transform('sum')

10.4 ms ± 367 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
8.58 ms ± 559 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

Poco convencional, pero vale la pena considerarlo: GroupBy.sum()+Series.map()

Me encontré con una idiosincrasia interesante en la API. Por lo que digo, puede reproducir esto en cualquier versión principal superior a 0.20 (probé esto en 0.23 y 0.24). Parece que puedes reducir constantemente algunos milisegundos del tiempo que tardas transformsi, en cambio, usas una función directa GroupByy la transmites usando map:

df.Date.map(df.groupby('Date')['Data3'].sum())

0     55
1    108
2     66
3    121
4     55
5    108
6     66
7    121
Name: Date, dtype: int64

Comparar con

df.groupby('Date')['Data3'].transform('sum')

0     55
1    108
2     66
3    121
4     55
5    108
6     66
7    121
Name: Data3, dtype: int64

Mis pruebas muestran que mapes un poco más rápido si usted puede permitirse el lujo de usar la directa GroupByfunción (como mean, min, max, first, etc). Es más o menos más rápido para la mayoría de situaciones generales hasta alrededor de ~ 200 mil registros. Después de eso, el rendimiento realmente depende de los datos.

(Izquierda: v0.23, Derecha: v0.24)

Buena alternativa para conocer, y mejor si tiene marcos más pequeños con un menor número de grupos. . . pero lo recomendaría transformcomo primera opción. Pensé que valía la pena compartir esto de todos modos.

Código de evaluación comparativa, como referencia:

import perfplot

perfplot.show(
    setup=lambda n: pd.DataFrame({'A': np.random.choice(n//10, n), 'B': np.ones(n)}),
    kernels=[
        lambda df: df.groupby('A')['B'].transform('sum'),
        lambda df:  df.A.map(df.groupby('A')['B'].sum()),
    ],
    labels=['GroupBy.transform', 'GroupBy.sum + map'],
    n_range=[2**k for k in range(5, 20)],
    xlabel='N',
    logy=True,
    logx=True
)
cs95
fuente
1
¡Esto es bueno saberlo! ¿Le importaría incluir (al menos en perfplots futuros) números de versión? La diferencia de rendimiento es interesante, pero estos son, después de todo, detalles de implementación que pueden resolverse en el futuro. Especialmente si los desarrolladores toman nota de sus publicaciones.
jpp
@jpp ¡sí, eso es justo! Tener versiones agregadas. Esto se probó en 0.23 pero creo que la diferencia se ve siempre que tenga una versión superior a 0.20.
cs95