Pandas GroupBy y seleccione filas con el valor mínimo en una columna específica

Resuelto Wendy asked hace 5 años • 7 respuestas

Tengo un DataFrame con las columnas A, B y C. Para cada valor de A, me gustaría seleccionar la fila con el valor mínimo en la columna B.

Es decir, de esto:

df = pd.DataFrame({'A': [1, 1, 1, 2, 2, 2],
                   'B': [4, 5, 2, 7, 4, 6],
                   'C': [3, 4, 10, 2, 4, 6]})      
    A   B   C
0   1   4   3
1   1   5   4
2   1   2   10
3   2   7   2
4   2   4   4
5   2   6   6  

Me gustaría conseguir:

    A   B   C
0   1   2   10
1   2   4   4

Por el momento estoy agrupando por columna A, luego creando un valor que me indica las filas que mantendré:

a = data.groupby('A').min()
a['A'] = a.index
to_keep = [str(x[0]) + str(x[1]) for x in a[['A', 'B']].values]
data['id'] = data['A'].astype(str) + data['B'].astype('str')
data[data['id'].isin(to_keep)]

Estoy seguro de que existe una forma mucho más sencilla de hacerlo. He visto muchas respuestas aquí que usan MultiIndex, que preferiría evitar.

Gracias por su ayuda.

Wendy avatar Feb 01 '19 06:02 Wendy
Aceptado

Siento que estás pensando demasiado en esto. Solo usa groupbyy idxmin:

df.loc[df.groupby('A').B.idxmin()]

   A  B   C
2  1  2  10
4  2  4   4

df.loc[df.groupby('A').B.idxmin()].reset_index(drop=True)

   A  B   C
0  1  2  10
1  2  4   4
cs95 avatar Feb 01 '2019 00:02 cs95

Tuve una situación similar pero con un encabezado de columna más complejo (por ejemplo, "B val"), en cuyo caso esto es necesario:

df.loc[df.groupby('A')['B val'].idxmin()]
Juho avatar Dec 15 '2019 10:12 Juho

Puedes sort_valuesy drop_duplicates:

df.sort_values('B').drop_duplicates('A')

Producción:

   A  B   C
2  1  2  10
4  2  4   4
Mykola Zotko avatar Oct 03 '2022 20:10 Mykola Zotko

La respuesta aceptada (que sugiere idxmin) no se puede utilizar con el patrón de tubería. Una alternativa compatible con las tuberías es ordenar primero los valores y luego usarlos groupbycon DataFrame.head:

data.sort_values('B').groupby('A').apply(DataFrame.head, n=1)

Esto es posible porque de forma predeterminada groupby se conserva el orden de las filas dentro de cada grupo , lo cual es un comportamiento estable y documentado (ver pandas.DataFrame.groupby).

Este enfoque tiene beneficios adicionales:

  • se puede expandir fácilmente para seleccionar n filas con valores más pequeños en una columna específica
  • puede romper vínculos proporcionando otra columna (como una lista) .sort_values(), por ejemplo:
    data.sort_values(['final_score', 'midterm_score']).groupby('year').apply(DataFrame.head, n=1)
    

Al igual que con otras respuestas, para que coincida exactamente con el resultado deseado en la pregunta .reset_index(drop=True)es necesario realizar el fragmento final:

df.sort_values('B').groupby('A').apply(DataFrame.head, n=1).reset_index(drop=True)
krassowski avatar Feb 15 '2022 16:02 krassowski

Encontré una respuesta un poco más extensa, pero mucho más eficiente :

Este es el conjunto de datos de ejemplo:

data = pd.DataFrame({'A': [1,1,1,2,2,2], 'B':[4,5,2,7,4,6], 'C':[3,4,10,2,4,6]})
data

Out:
   A  B   C
0  1  4   3
1  1  5   4
2  1  2  10
3  2  7   2
4  2  4   4
5  2  6   6 

Primero obtendremos los valores mínimos de una Serie a partir de una operación de grupo:

min_value = data.groupby('A').B.min()
min_value

Out:
A
1    2
2    4
Name: B, dtype: int64

Luego, fusionamos el resultado de esta serie en el marco de datos original.

data = data.merge(min_value, on='A',suffixes=('', '_min'))
data

Out:
   A  B   C  B_min
0  1  4   3      2
1  1  5   4      2
2  1  2  10      2
3  2  7   2      4
4  2  4   4      4
5  2  6   6      4

Finalmente, obtenemos solo las líneas donde B es igual a B_min y eliminamos B_min ya que ya no lo necesitamos.

data = data[data.B==data.B_min].drop('B_min', axis=1)
data

Out:
   A  B   C
2  1  2  10
4  2  4   4

Lo probé en conjuntos de datos muy grandes y esta fue la única manera de hacerlo funcionar en un tiempo razonable.

Sergio Polimante avatar Sep 23 '2021 23:09 Sergio Polimante