Los documentos muestran cómo aplicar múltiples funciones en un objeto groupby a la vez utilizando un dict con los nombres de las columnas de salida como 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 Group by Series. Y cuando un dict se pasa de manera similar a un grupo por DataFrame, se espera que las claves sean los nombres de columna a los que se aplicará la función.
Lo que quiero hacer es aplicar varias funciones a varias columnas (pero ciertas columnas se operarán varias veces). Además, algunas funciones dependerán de otras columnas en el objeto groupby (como las funciones sumif). 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 lleva mucho tiempo (creo que toma mucho tiempo recorrer un objeto groupby). 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 hacer esto de manera 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 se esperaba, obtengo un KeyError (ya que las claves deben ser una columna si se llama a agg
desde un DataFrame).
¿Hay alguna forma integrada de hacer lo que me gustaría hacer, o la posibilidad de que se pueda agregar esta funcionalidad, o simplemente tendré que recorrer el grupo manualmente?
Gracias
La segunda mitad de la respuesta actualmente aceptada está desactualizada y tiene dos desaprobaciones. Lo primero y lo más importante es que ya no puede pasar un diccionario de diccionarios al método colectivo de grupo. En segundo lugar, nunca utilice .ix
.
Si desea trabajar con dos columnas separadas al mismo tiempo, sugeriría usar el método de apply
que implícitamente pasa un DataFrame a la función aplicada. Usemos un dataframe similar al de arriba
df = pd.DataFrame(np.random.rand(4,4), columns=list('abcd')) df['group'] = [0, 0, 1, 1] df abcd 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 manera 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()}) abcd sum max mean sum group 0 0.864569 0.446069 0.466054 0.969921 0.341399 1 1.478872 0.843026 0.687672 1.754877 0.672401
Si no te gusta ese nombre de columna feo lambda, puedes usar una función normal y proporcionar un nombre personalizado al atributo __name__
especial 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}) abcd sum max mean sum Max minus Min group 0 0.864569 0.446069 0.466054 0.969921 0.341399 1 1.478872 0.843026 0.687672 1.754877 0.672401
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. Cuando se usa, se apply
todo el grupo, ya que un DataFrame se pasa a la función.
Recomiendo hacer una única función personalizada que devuelva una serie de todas las agregaciones. Utilice el índice de Series como tags 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.864569 0.446069 0.466054 0.173711 1 1.478872 0.843026 0.687672 0.630494
Si está enamorado de los 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) ab c_d sum max mean prodsum group 0 0.864569 0.446069 0.466054 0.173711 1 1.478872 0.843026 0.687672 0.630494
Para la primera parte, puede pasar un dictado de nombres de columna para las claves y una lista de funciones para los valores:
In [28]: df Out[28]: ABCDE 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]: AB 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 agregada funciona en Series, las referencias a los otros nombres de columna se pierden. Para solucionar esto, puede hacer referencia al dataframe completo e indexarlo utilizando 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]: ABD sum mean prod GRP 0 0.719580 0.359790 0.102004 1.170219 1 0.454824 0.227412 0.034060 1.182901
Aquí, la columna ‘D’ resultante se compone de los valores ‘E’ sumdos.
ACTUALIZACIÓN 2:
Aquí hay un método que creo que hará todo lo que pidas. Primero haz una función lambda personalizada. A continuación, g hace referencia al grupo. Al agregar, g será una serie. Al pasar g.index
a df.ix[]
selecciona el grupo actual de df. Entonces 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 con 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]: ABD sum mean prod my name GRP 0 0.719580 0.359790 0.102004 0.204072 1 0.454824 0.227412 0.034060 0.570441
Como alternativa (sobre todo en estética) a la respuesta de Ted Petrou, descubrí que prefería una lista algo más compacta. Por favor, no consideres aceptarlo, es solo un comentario mucho más detallado sobre la respuesta de Ted, más el código / datos. Python / pandas no es mi primera / mejor, pero encontré esto para leer bien:
df.groupby('group') \ .apply(lambda x: pd.Series({ 'a_sum' : x['a'].sum(), 'a_max' : x['a'].max(), 'b_mean' : x['b'].mean(), 'c_d_prodsum' : (x['c'] * x['d']).sum() }) ) a_sum a_max b_mean c_d_prodsum group 0 0.530559 0.374540 0.553354 0.488525 1 1.433558 0.832443 0.460206 0.053313
Me parece más evocador de dplyr
tuberías data.table
y los comandos encadenados data.table
. No quiere decir que sean mejores, solo que me son más familiares. (Ciertamente reconozco el poder y, para muchos, la preferencia de usar funciones de def
más formalizadas para este tipo de operaciones. Esta es solo una alternativa, no necesariamente mejor).
Generé datos de la misma manera que Ted, agregaré una semilla para reproducibilidad.
import numpy as np np.random.seed(42) df = pd.DataFrame(np.random.rand(4,4), columns=list('abcd')) df['group'] = [0, 0, 1, 1] df abcd group 0 0.374540 0.950714 0.731994 0.598658 0 1 0.156019 0.155995 0.058084 0.866176 0 2 0.601115 0.708073 0.020584 0.969910 1 3 0.832443 0.212339 0.181825 0.183405 1
La respuesta de Ted es asombrosa. Terminé usando una versión más pequeña de eso en caso de que alguien esté interesado. Es útil cuando busca una agregación que depende de los valores de varias columnas:
df=pd.DataFrame({'a': [1,2,3,4,5,6], 'b': [1,1,0,1,1,0], 'c': ['x','x','y','y','z','z']}) abc 0 1 1 x 1 2 1 x 2 3 0 y 3 4 1 y 4 5 1 z 5 6 0 z
df.groupby('c').apply(lambda x: x['a'][(x['a']>1) & (x['b']==1)].mean()) c x 2.0 y 4.0 z 5.0
Me gusta este enfoque ya que todavía puedo usar el agregado. Quizás la gente me permita saber por qué es necesario aplicar para acceder a varias columnas al hacer agregaciones en grupos.
Parece obvio ahora, pero mientras no seleccione la columna de interés directamente después del grupo , tendrá acceso a todas las columnas del dataframe desde su función de agregación.
df.groupby('c')['a'].aggregate(lambda x: x[x>1].mean())
df.groupby('c').aggregate(lambda x: x[(x['a']>1) & (x['b']==1)].mean())['a']
df.groupby('c').aggregate(lambda x: x['a'][(x['a']>1) & (x['b']==1)].mean())
Espero que esto ayude.