¿Cómo aplicar diferentes funciones a un objeto groupby?

Tengo un dataframe como este:

import pandas as pd df = pd.DataFrame({'id': [1, 2, 1, 1, 2, 1, 2, 2], 'min_max': ['max_val', 'max_val', 'min_val', 'min_val', 'max_val', 'max_val', 'min_val', 'min_val'], 'value': [1, 20, 20, 10, 12, 3, -10, -5 ]}) id min_max value 0 1 max_val 1 1 2 max_val 20 2 1 min_val 20 3 1 min_val 10 4 2 max_val 12 5 1 max_val 3 6 2 min_val -10 7 2 min_val -5 

Cada id tiene varios valores máximos y mínimos asociados. Mi salida deseada se ve así:

  max min id 1 3 10 2 20 -10 

Contiene el max_val máximo y el min_val mínimo para cada id .

Actualmente lo implemento de la siguiente manera:

 gdf = df.groupby(by=['id', 'min_max'])['value'] max_max = gdf.max().loc[:, 'max_val'] min_min = gdf.min().loc[:, 'min_val'] final_df = pd.concat([max_max, min_min], axis=1) final_df.columns = ['max', 'min'] 

Lo que no me gusta es que tengo que llamar a .max() y .min() en el gdf dataframe agrupado, por separado donde descarto el 50% de la información (ya que no estoy interesado en el min_val mínimo de min_val y el mínimo min_val ).

¿Hay una manera de hacerlo de una manera más directa, por ejemplo, pasando la función que debe aplicarse a un grupo directamente a la llamada groupby ?

EDITAR:

 df.groupby('id')['value'].agg(['max','min']) 

no es suficiente, ya que puede darse el caso de que un grupo tenga un min_val que sea más alto que todos los max_val para ese grupo o un max_val que sea más bajo que todos los min_val . Por lo tanto, uno también tiene que agrupar basado en la columna min_max .

Resultado para

 df.groupby('id')['value'].agg(['max','min']) max min id 1 20 1 2 20 -10 

Resultado del código de arriba:

  max min id 1 3 10 2 20 -10 

Aquí hay una solución ligeramente irónica:

 >>> df.groupby(['id', 'min_max'])['value'].apply(lambda g: getattr(g, g.name[1][:3])()).unstack() min_max max_val min_val id 1 3 10 2 20 -10 

Esto aplica una función que toma el nombre de la función real para aplicar desde la tecla de grupo.

Obviamente, esto no funcionaría tan simplemente si no existiera una relación tan simple entre la cadena “max_val” y el nombre de la función “max”. Se podría generalizar teniendo una columna que asigne valores de columna a funciones para aplicar, algo como esto:

 func_map = {'min_val': min, 'max_val': max} df.groupby(['id', 'min_max'])['value'].apply(lambda g: func_map[g.name[1]](g)).unstack() 

Tenga en cuenta que esto es un poco menos eficiente que la versión anterior, ya que llama a Python max / min en lugar de a las versiones de pandas optimizadas. Pero si quieres una solución más generalizable, eso es lo que tienes que hacer, porque no hay versiones pandas optimizadas de todo. (Esto también es más o menos un motivo por el cual no hay una forma integrada de hacer esto: para la mayoría de los datos, no puede asumir a priori que sus valores pueden asignarse a funciones significativas, por lo que no tiene sentido tratar de determinar la función a aplicar en función de los propios valores.)

Una opción es hacer la agregación personalizada con groupby.apply , ya que no encaja bien con el escenario de agregación incorporado:

 (df.groupby('id') .apply(lambda g: pd.Series({'max': g.value[g.min_max == "max_val"].max(), 'min': g.value[g.min_max == "min_val"].min()}))) # max min #id # 1 3 10 # 2 20 -10 

Solución con pivot_table :

 df1 = df.pivot_table(index='id', columns='min_max', values='value', aggfunc=[np.min,np.max]) df1 = df1.loc[:, [('amin','min_val'), ('amax','max_val')]] df1.columns = df1.columns.droplevel(1) print (df1) amin amax id 1 10 3 2 -10 20