python - multiple - pandas groupby count
Aplicar funciones mĂșltiples a varias columnas groupby (2)
Los docs muestran cómo aplicar múltiples funciones en un grupo por objeto a la vez usando un dict con los nombres de las columnas de salida como las claves:
In [563]: grouped[''D''].agg({''result1'' : np.sum,
.....: ''result2'' : np.mean})
.....:
Out[563]:
result2 result1
A
bar -0.579846 -1.739537
foo -0.280588 -1.402938
Sin embargo, esto solo funciona en un objeto agrupado por grupo. Y cuando un dict se pasa de manera similar a un grupo por DataFrame, espera que las claves sean los nombres de columna a los que se aplicará la función.
Lo que quiero hacer es aplicar múltiples funciones a varias columnas (pero ciertas columnas serán operadas varias veces). Además, algunas funciones dependerán de otras columnas en el grupo por objeto (como funciones de suma). Mi solución actual es ir columna por columna y hacer algo como el código anterior, usando lambdas para funciones que dependen de otras filas. Pero esto está tomando mucho tiempo, (creo que lleva mucho tiempo iterar a través de un grupo por objeto). Tendré que cambiarlo para que recorra todo el grupo por objeto en una sola ejecución, pero me pregunto si hay una forma integrada en pandas para hacerlo de una manera un tanto limpia.
Por ejemplo, he intentado algo como
grouped.agg({''C_sum'' : lambda x: x[''C''].sum(),
''C_std'': lambda x: x[''C''].std(),
''D_sum'' : lambda x: x[''D''].sum()},
''D_sumifC3'': lambda x: x[''D''][x[''C''] == 3].sum(), ...)
pero como esperaba recibo un KeyError (ya que las claves tienen que ser una columna si se llama a agg
desde un DataFrame).
¿Existe alguna forma de hacer lo que me gustaría hacer, o la posibilidad de que se agregue esta funcionalidad, o solo tendré que repetir el grupo manualmente?
Gracias
La segunda mitad de la respuesta actualmente aceptada está desactualizada y tiene dos inconvenientes. Primero y más importante, ya no puede pasar un diccionario de diccionarios al método agg
groupby. Segundo, nunca uses .ix
.
Si desea trabajar con dos columnas separadas al mismo tiempo, le sugiero que utilice el método de apply
, cuya implicidad pasa un DataFrame a la función aplicada. Usemos un marco de datos similar al de arriba
df = pd.DataFrame(np.random.rand(4,4), columns=list(''abcd''))
df[''group''] = [0, 0, 1, 1]
df
a b c d group
0 0.418500 0.030955 0.874869 0.145641 0
1 0.446069 0.901153 0.095052 0.487040 0
2 0.843026 0.936169 0.926090 0.041722 1
3 0.635846 0.439175 0.828787 0.714123 1
Un diccionario asignado de nombres de columna a funciones de agregación sigue siendo una forma perfectamente buena de realizar una agregación.
df.groupby(''group'').agg({''a'':[''sum'', ''max''],
''b'':''mean'',
''c'':''sum'',
''d'': lambda x: x.max() - x.min()})
a b c d
sum max mean sum <lambda>
group
0 0.560541 0.507058 0.418546 1.707651 0.129667
1 0.187757 0.157958 0.887315 0.533531 0.652427
Si no le gusta el nombre feo de la columna lambda, puede usar una función normal y proporcionar un nombre personalizado al atributo especial __name__
como este:
def max_min(x):
return x.max() - x.min()
max_min.__name__ = ''Max minus Min''
df.groupby(''group'').agg({''a'':[''sum'', ''max''],
''b'':''mean'',
''c'':''sum'',
''d'': max_min})
a b c d
sum max mean sum Max minus Min
group
0 0.560541 0.507058 0.418546 1.707651 0.129667
1 0.187757 0.157958 0.887315 0.533531 0.652427
Usar apply
y devolver una serie
Ahora, si tenía varias columnas que necesitaban interactuar juntas, entonces no puede usar agg
, que implícitamente pasa una serie a la función de agregación. Al usar, apply
el grupo completo como un DataFrame pasa a la función.
Recomiendo hacer una única función personalizada que devuelva una serie de todas las agregaciones. Use el índice de la serie como etiquetas para las nuevas columnas:
def f(x):
d = {}
d[''a_sum''] = x[''a''].sum()
d[''a_max''] = x[''a''].max()
d[''b_mean''] = x[''b''].mean()
d[''c_d_prodsum''] = (x[''c''] * x[''d'']).sum()
return pd.Series(d, index=[''a_sum'', ''a_max'', ''b_mean'', ''c_d_prodsum''])
df.groupby(''group'').apply(f)
a_sum a_max b_mean c_d_prodsum
group
0 0.560541 0.507058 0.418546 0.118106
1 0.187757 0.157958 0.887315 0.276808
Si está enamorado de MultiIndexes, aún puede devolver una Serie con una como esta:
def f_mi(x):
d = []
d.append(x[''a''].sum())
d.append(x[''a''].max())
d.append(x[''b''].mean())
d.append((x[''c''] * x[''d'']).sum())
return pd.Series(d, index=[[''a'', ''a'', ''b'', ''c_d''],
[''sum'', ''max'', ''mean'', ''prodsum'']])
df.groupby(''group'').apply(f_mi)
a b c_d
sum max mean prodsum
group
0 0.560541 0.507058 0.418546 0.118106
1 0.187757 0.157958 0.887315 0.276808
Para la primera parte puede pasar un dict de nombres de columna para claves y una lista de funciones para los valores:
In [28]: df
Out[28]:
A B C D E GRP
0 0.395670 0.219560 0.600644 0.613445 0.242893 0
1 0.323911 0.464584 0.107215 0.204072 0.927325 0
2 0.321358 0.076037 0.166946 0.439661 0.914612 1
3 0.133466 0.447946 0.014815 0.130781 0.268290 1
In [26]: f = {''A'':[''sum'',''mean''], ''B'':[''prod'']}
In [27]: df.groupby(''GRP'').agg(f)
Out[27]:
A B
sum mean prod
GRP
0 0.719580 0.359790 0.102004
1 0.454824 0.227412 0.034060
ACTUALIZACIÓN 1:
Debido a que la función de agregado funciona en Series, las referencias a los otros nombres de columna se pierden. Para evitar esto, puede hacer referencia al marco de datos completo e indexarlo usando los índices de grupo dentro de la función lambda.
Aquí hay una solución hacky:
In [67]: f = {''A'':[''sum'',''mean''], ''B'':[''prod''], ''D'': lambda g: df.loc[g.index].E.sum()}
In [69]: df.groupby(''GRP'').agg(f)
Out[69]:
A B D
sum mean prod <lambda>
GRP
0 0.719580 0.359790 0.102004 1.170219
1 0.454824 0.227412 0.034060 1.182901
Aquí, la columna resultante ''D'' se compone de los valores ''E'' sumados.
ACTUALIZACIÓN 2:
Este es un método que creo que hará todo lo que pidas. Primero crea una función lambda personalizada. A continuación, g hace referencia al grupo. Cuando se agrega, g será una serie. Al pasar g.index
a df.ix[]
selecciona el grupo actual de df. Luego pruebo si la columna C es menor que 0.5. La serie booleana devuelta se pasa a g[]
que selecciona solo aquellas filas que cumplen los criterios.
In [95]: cust = lambda g: g[df.loc[g.index][''C''] < 0.5].sum()
In [96]: f = {''A'':[''sum'',''mean''], ''B'':[''prod''], ''D'': {''my name'': cust}}
In [97]: df.groupby(''GRP'').agg(f)
Out[97]:
A B D
sum mean prod my name
GRP
0 0.719580 0.359790 0.102004 0.204072
1 0.454824 0.227412 0.034060 0.570441