Python pandas: elimine duplicados por columnas A, manteniendo la fila con el valor más alto en la columna B

161

Tengo un marco de datos con valores de repetición en la columna A. Quiero eliminar duplicados, manteniendo la fila con el valor más alto en la columna B.

Así que esto:

A B
1 10
1 20
2 30
2 40
3 10

Debería convertirse en esto:

A B
1 20
2 40
3 10

Wes ha agregado una buena funcionalidad para eliminar duplicados: http://wesmckinney.com/blog/?p=340 . Pero AFAICT, está diseñado para duplicados exactos, por lo que no se mencionan los criterios para seleccionar qué filas se mantienen.

Supongo que probablemente haya una manera fácil de hacer esto, tal vez tan fácil como ordenar el marco de datos antes de soltar duplicados, pero no conozco la lógica interna de groupby lo suficientemente bien como para descubrirlo. ¿Alguna sugerencia?

Abe
fuente
1
Tenga en cuenta que la URL en la pregunta aparece EOL.
DaveL17
Para una forma idiomática y performante, vea esta solución a continuación .
Ted Petrou

Respuestas:

194

Esto lleva lo último. Sin embargo, no es el máximo:

In [10]: df.drop_duplicates(subset='A', keep="last")
Out[10]: 
   A   B
1  1  20
3  2  40
4  3  10

También puedes hacer algo como:

In [12]: df.groupby('A', group_keys=False).apply(lambda x: x.loc[x.B.idxmax()])
Out[12]: 
   A   B
A       
1  1  20
2  2  40
3  3  10
Wes McKinney
fuente
12
Pequeña nota: Los parámetros colsy take_lastse deprecian y han sido reemplazados por los parámetros subsety keep. pandas.pydata.org/pandas-docs/version/0.17.1/generated/…
Jezzamon
como dice @Jezzamon,FutureWarning: the take_last=True keyword is deprecated, use keep='last' instead
tumultous_rooster
1
¿Hay alguna razón para no usar df.sort_values(by=['B']).drop_duplicates(subset=['A'], keep='last')? Quiero decir que este sort_values ​​me parece seguro, pero no tengo idea de si realmente lo es.
Little Bobby Tables
44
Esta respuesta ahora es obsoleta. Vea la respuesta de @Ted Petrou a continuación.
cxrodgers
Si desea usar este código pero con el caso de más de una columna en el group_by, puede agregar .reset_index(drop=True) df.groupby(['A','C'], group_keys=False).apply(lambda x: x.ix[x.B.idxmax()]).reset_index(drop=True)Esto restablecerá el índice ya que su valor predeterminado sería un índice múltiple compilado desde 'A'y'C'
Dijo Hamri
79

La respuesta principal es hacer demasiado trabajo y parece ser muy lenta para conjuntos de datos más grandes. applyes lento y debe evitarse si es posible. ixestá en desuso y también debe evitarse.

df.sort_values('B', ascending=False).drop_duplicates('A').sort_index()

   A   B
1  1  20
3  2  40
4  3  10

O simplemente agrupe por todas las otras columnas y tome el máximo de la columna que necesita. df.groupby('A', as_index=False).max()

Ted Petrou
fuente
1
Este es realmente un enfoque inteligente. Me preguntaba si se puede generalizar mediante el uso de alguna lambafunción al soltar. Por ejemplo, ¿cómo puedo soltar solo valores menores que el promedio de esos valores duplicados?
Dexter
15

La solución más simple:

Para soltar duplicados basados ​​en una columna:

df = df.drop_duplicates('column_name', keep='last')

Para soltar duplicados basados ​​en múltiples columnas:

df = df.drop_duplicates(['col_name1','col_name2','col_name3'], keep='last')
Gil Baggio
fuente
1
Mejor solución. Gracias.
Flavio
Encantado de ayudar. @Flavio
Gil Baggio
Mi marco de datos tiene 10 columnas, y usé este código para eliminar duplicados de tres columnas. Sin embargo, eliminó las filas del resto de las columnas. ¿Hay alguna forma de eliminar los duplicados solo para las 4 últimas columnas?
Sofía
2
Pero OP quiere mantener el valor más alto en la columna B. Esto podría funcionar si ordena primero. Pero entonces es básicamente la respuesta de Ted Petrou.
Teepeemm
7

Prueba esto:

df.groupby(['A']).max()
eumiro
fuente
1
¿Conoces el mejor idioma para reindexar esto para que parezca el DataFrame original? Estaba tratando de resolver eso cuando me ninja. : ^)
DSM
44
Ordenado. ¿Qué sucede si el marco de datos contiene más columnas (por ejemplo, C, D, E)? Max no parece funcionar en ese caso, porque necesitamos especificar que B es la única columna que necesita ser maximizada.
Abe
1
@DSM Verifique el enlace en la pregunta original. Hay algún código para reindexar el marco de datos agrupado.
Abe el
5

Primero ordenaría el marco de datos con la columna B descendente, luego soltaría los duplicados para la columna A y me quedaría primero

df = df.sort_values(by='B', ascending=False)
df = df.drop_duplicates(subset='A', keep="first")

sin ningún grupo

Nobel
fuente
1

Creo que en tu caso realmente no necesitas un grupo. Ordenaría por orden descendente su columna B, luego soltaría duplicados en la columna A y si lo desea, también puede tener un nuevo índice agradable y limpio como ese:

df.sort_values('B', ascending=False).drop_duplicates('A').sort_index().reset_index(drop=True)
lo que sea
fuente
¿En qué se diferencia esto de otras publicaciones?
DJK
1

Aquí hay una variación que tuve que resolver que vale la pena compartir: para cada cadena única en la columnAque quería encontrar la cadena asociada más común columnB.

df.groupby('columnA').agg({'columnB': lambda x: x.mode().any()}).reset_index()

La .any()recoge uno si hay un empate para el modo. (Tenga en cuenta que el uso .any()en una serie de ints devuelve un valor booleano en lugar de elegir uno de ellos).

Para la pregunta original, el enfoque correspondiente se simplifica a

df.groupby('columnA').columnB.agg('max').reset_index().

mistaben
fuente
0

Cuando las publicaciones ya respondieron la pregunta, hice un pequeño cambio agregando el nombre de la columna en la que se aplica la función max () para una mejor legibilidad del código.

df.groupby('A', as_index=False)['B'].max()
Bhagabat Behera
fuente
Dé un poco más de contexto a sus respuestas, explicando cómo funcionan y por qué son superiores o complementarias a las respuestas ya disponibles para una pregunta. Si no proporcionan un valor agregado, evite publicar respuestas adicionales sobre preguntas anteriores. Finalmente, formatee su código como un bloque de código sangrándolo.
WhoIsJack
0

La forma más fácil de hacer esto:

# First you need to sort this DF as Column A as ascending and column B as descending 
# Then you can drop the duplicate values in A column 
# Optional - you can reset the index and get the nice data frame again
# I'm going to show you all in one step. 

d = {'A': [1,1,2,3,1,2,3,1], 'B': [30, 40,50,42,38,30,25,32]}
df = pd.DataFrame(data=d)
df

    A   B
0   1   30
1   1   40
2   2   50
3   3   42
4   1   38
5   2   30
6   3   25
7   1   32


df = df.sort_values(['A','B'], ascending =[True,False]).drop_duplicates(['A']).reset_index(drop=True)

df

    A   B
0   1   40
1   2   50
2   3   42
rra
fuente
-1

esto también funciona:

a=pd.DataFrame({'A':a.groupby('A')['B'].max().index,'B':a.groupby('A')       ['B'].max().values})
Mahesh
fuente
Si bien este fragmento de código puede resolver la pregunta, incluir una explicación realmente ayuda a mejorar la calidad de su publicación. Recuerde que está respondiendo la pregunta para los lectores en el futuro, y que esas personas podrían no conocer los motivos de su sugerencia de código. Por favor, también trate de no saturar su código con comentarios explicativos, ¡esto reduce la legibilidad tanto del código como de las explicaciones!
Martin Tournoij
-8

No voy a darle la respuesta completa (no creo que esté buscando el análisis y la escritura en la parte del archivo de todos modos), pero una sugerencia fundamental debería ser suficiente: use la set()función de Python y luego sorted()o .sort()junto con .reverse():

>>> a=sorted(set([10,60,30,10,50,20,60,50,60,10,30]))
>>> a
[10, 20, 30, 50, 60]
>>> a.reverse()
>>> a
[60, 50, 30, 20, 10]
Abhranil Das
fuente
8
Tal vez me equivoque en esto, pero reestructurar un DataFrame de pandas como un conjunto, luego convertirlo nuevamente parece una forma muy ineficiente de resolver este problema. Estoy haciendo un análisis de registro, así que lo aplicaré a algunos conjuntos de datos muy grandes.
Abe
Lo siento, no sé demasiado sobre este escenario en particular, por lo que es posible que mi respuesta genérica no resulte demasiado eficiente para su problema.
Abhranil Das