Pandas: agrupación y agregación con múltiples funciones.

Situación

Tengo un dataframe de pandas definido de la siguiente manera:

import pandas as pd headers = ['Group', 'Element', 'Case', 'Score', 'Evaluation'] data = [ ['A', 1, 'x', 1.40, 0.59], ['A', 1, 'y', 9.19, 0.52], ['A', 2, 'x', 8.82, 0.80], ['A', 2, 'y', 7.18, 0.41], ['B', 1, 'x', 1.38, 0.22], ['B', 1, 'y', 7.14, 0.10], ['B', 2, 'x', 9.12, 0.28], ['B', 2, 'y', 4.11, 0.97], ] df = pd.DataFrame(data, columns=headers) 

que se ve así en la salida de la consola:

  Group Element Case Score Evaluation 0 A 1 x 1.40 0.59 1 A 1 y 9.19 0.52 2 A 2 x 8.82 0.80 3 A 2 y 7.18 0.41 4 B 1 x 1.38 0.22 5 B 1 y 7.14 0.10 6 B 2 x 9.12 0.28 7 B 2 y 4.11 0.97 

Problema

Me gustaría realizar una operación de agrupación y agregación en df que me proporcione el siguiente dataframe de resultados:

  Group Max_score_value Max_score_element Max_score_case Min_evaluation 0 A 9.19 1 y 0.41 1 B 9.12 2 x 0.10 

Para aclarar con más detalle: me gustaría agrupar por la columna Group , y luego aplicar la agregación para obtener las siguientes columnas de resultados:

  • Max_score_value : el valor máximo de grupo de la columna Score .
  • Max_score_element : el valor de la columna Element que corresponde al valor de Score máximo del grupo.
  • Max_score_case : el valor de la columna Case que corresponde al valor de Score máximo del grupo.
  • Min_evaluation : el valor mínimo de grupo de la columna Evaluation .

Intentado hasta ahora

He creado el siguiente código para la agrupación y agregación:

 result = ( df.set_index(['Element', 'Case']) .groupby('Group') .agg({'Score': ['max', 'idxmax'], 'Evaluation': 'min'}) .reset_index() ) print(result) 

que da como salida:

  Group Score Evaluation max idxmax min 0 A 9.19 (1, y) 0.41 1 B 9.12 (2, x) 0.10 

Como puede ver, los datos básicos están ahí, pero todavía no están en el formato que necesito. Es este último paso con el que estoy luchando. ¿Alguien aquí tiene algunas buenas ideas para generar un dataframe de resultados en el formato que estoy buscando?

A partir del dataframe de result , puede transformarse en dos pasos de la siguiente manera al formato que necesita:

 # collapse multi index column to single level column result.columns = [y + '_' + x if y != '' else x for x, y in result.columns]​ # split the idxmax column into two columns result = result.assign( max_score_element = result.idxmax_Score.str[0], max_score_case = result.idxmax_Score.str[1] ).drop('idxmax_Score', 1) result #Group max_Score min_Evaluation max_score_case max_score_element #0 A 9.19 0.41 y 1 #1 B 9.12 0.10 x 2 

Una alternativa a partir del df original que usa join , que puede no ser tan eficiente pero menos verbosa similar a la idea de @tarashypka:

 (df.groupby('Group') .agg({'Score': 'idxmax', 'Evaluation': 'min'}) .set_index('Score') .join(df.drop('Evaluation',1)) .reset_index(drop=True)) #Evaluation Group Element Case Score #0 0.41 A 1 y 9.19 #1 0.10 B 2 x 9.12 

Temporización ingenua con el conjunto de datos de ejemplo:

 %%timeit (df.groupby('Group') .agg({'Score': 'idxmax', 'Evaluation': 'min'}) .set_index('Score') .join(df.drop('Evaluation',1)) .reset_index(drop=True)) # 100 loops, best of 3: 3.47 ms per loop %%timeit result = ( df.set_index(['Element', 'Case']) .groupby('Group') .agg({'Score': ['max', 'idxmax'], 'Evaluation': 'min'}) .reset_index() )​ result.columns = [y + '_' + x if y != '' else x for x, y in result.columns]​ result = result.assign( max_score_element = result.idxmax_Score.str[0], max_score_case = result.idxmax_Score.str[1] ).drop('idxmax_Score', 1) # 100 loops, best of 3: 7.61 ms per loop 

Aquí es posible solución con pd.merge

 >> r = df.groupby('Group') \ >> .agg({'Score': 'idxmax', 'Evaluation': 'min'}) \ >> .rename(columns={'Score': 'idx'}) >> for c in ['Score', 'Element', 'Case']: >> r = pd.merge(r, df[[c]], how='left', left_on='idx', right_index=True) >> r.drop('Score_idx', axis=1).rename(columns={'Score': 'Max_score_value', >> 'Element': 'Max_score_element', >> 'Case': 'Max_score_case'}) Evaluation Max_score_value Max_score_element Max_score_case Group A 0.41 9.19 1 y B 0.10 9.12 2 x 

A pesar de que proporciona la salida deseada, no estoy seguro de si no es menos eficiente que el enfoque tuyo.

Puede usar aplicar en lugar de agg para construir todas las columnas de una sola vez.

 result = ( df.groupby('Group').apply(lambda x: [np.max(x.Score), df.loc[x.Score.idxmax(),'Element'], df.loc[x.Score.idxmax(),'Case'], np.min(x.Evaluation)]) .apply(pd.Series) .rename(columns={0:'Max_score_value', 1:'Max_score_element', 2:'Max_score_case', 3:'Min_evaluation'}) .reset_index() ) result Out[9]: Group Max_score_value Max_score_element Max_score_case Min_evaluation 0 A 9.19 1 y 0.41 1 B 9.12 2 x 0.10 

Mi toma

 g = df.set_index('Group').groupby(level='Group', group_keys=False) result = g.apply( pd.DataFrame.nlargest, n=1, columns='Score' ) def f(x): x = 'value' if x == 'Score' else x return 'Max_score_' + x.lower() result.drop('Evaluation', 1).rename(columns=f).assign( Min_evaluation=g.Evaluation.min().values).reset_index() Group Max_score_element Max_score_case Max_score_value Min_evaluation 0 A 1 y 9.19 0.41 1 B 2 x 9.12 0.10