Pandas GroupBy y seleccione filas con el valor mínimo en una columna específica
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.
Siento que estás pensando demasiado en esto. Solo usa groupby
y 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
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()]
Puedes sort_values
y drop_duplicates
:
df.sort_values('B').drop_duplicates('A')
Producción:
A B C
2 1 2 10
4 2 4 4
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 groupby
con 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)
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.