Obtenga las filas que tienen el conteo máximo en grupos usando groupby

244

¿Cómo encuentro todas las filas en un marco de datos de pandas que tienen el valor máximo para countcolumna, después de agrupar por ['Sp','Mt']columnas?

Ejemplo 1: el siguiente dataFrame, que agrupo por ['Sp','Mt']:

   Sp   Mt Value   count
0  MM1  S1   a      **3**
1  MM1  S1   n      2
2  MM1  S3   cb     5
3  MM2  S3   mk      **8**
4  MM2  S4   bg     **10**
5  MM2  S4   dgd      1
6  MM4  S2  rd     2
7  MM4  S2   cb      2
8  MM4  S2   uyi      **7**

Salida esperada: obtenga las filas de resultados cuyo recuento es máximo entre los grupos, como:

0  MM1  S1   a      **3**
1 3  MM2  S3   mk      **8**
4  MM2  S4   bg     **10** 
8  MM4  S2   uyi      **7**

Ejemplo 2: este marco de datos, que agrupo por ['Sp','Mt']:

   Sp   Mt   Value  count
4  MM2  S4   bg     10
5  MM2  S4   dgd    1
6  MM4  S2   rd     2
7  MM4  S2   cb     8
8  MM4  S2   uyi    8

Para el ejemplo anterior, quiero obtener todas las filas donde countes igual a max, en cada grupo, por ejemplo:

MM2  S4   bg     10
MM4  S2   cb     8
MM4  S2   uyi    8
jojo12
fuente
¿En qué formato está su marco de datos?
David Robinson el
2
No lo entiendo ¿Qué es exactamente un grupo? ¿Por qué comienza la segunda línea en el resultado 1 3?
Jo So
stackoverflow.com/questions/18879782/… Podría ser útil
J_Arthur
1
Esta respuesta es la solución más rápida que pude encontrar: stackoverflow.com/a/21007047/778533
tommy.carstensen
Similar a esta pregunta, ¿alguien podría responder esto? Stackoverflow.com/questions/62069465/… Gracias.
ds_Abc

Respuestas:

325
In [1]: df
Out[1]:
    Sp  Mt Value  count
0  MM1  S1     a      3
1  MM1  S1     n      2
2  MM1  S3    cb      5
3  MM2  S3    mk      8
4  MM2  S4    bg     10
5  MM2  S4   dgd      1
6  MM4  S2    rd      2
7  MM4  S2    cb      2
8  MM4  S2   uyi      7

In [2]: df.groupby(['Mt'], sort=False)['count'].max()
Out[2]:
Mt
S1     3
S3     8
S4    10
S2     7
Name: count

Para obtener los índices del DF original, puede hacer:

In [3]: idx = df.groupby(['Mt'])['count'].transform(max) == df['count']

In [4]: df[idx]
Out[4]:
    Sp  Mt Value  count
0  MM1  S1     a      3
3  MM2  S3    mk      8
4  MM2  S4    bg     10
8  MM4  S2   uyi      7

Tenga en cuenta que si tiene múltiples valores máximos por grupo, todos serán devueltos.

Actualizar

En un granizo, es probable que esto sea lo que solicita el OP:

In [5]: df['count_max'] = df.groupby(['Mt'])['count'].transform(max)

In [6]: df
Out[6]:
    Sp  Mt Value  count  count_max
0  MM1  S1     a      3          3
1  MM1  S1     n      2          3
2  MM1  S3    cb      5          8
3  MM2  S3    mk      8          8
4  MM2  S4    bg     10         10
5  MM2  S4   dgd      1         10
6  MM4  S2    rd      2          7
7  MM4  S2    cb      2          7
8  MM4  S2   uyi      7          7
Zelazny7
fuente
@ Zelazny7, ¿hay alguna forma de adoptar esta respuesta para aplicar a la agrupación por una columna y luego mirar 2 columnas y hacer un máximo de ellas para obtener una mayor de las dos? No puedo hacer que eso funcione. Lo que tengo actualmente es: def Greater (Merge, maximumA, maximumB): a = Merge [maximumA] b = Merge [maximumB] return max (a, b) Merger.groupby ("Search_Term"). Apply (Greater, "Ratio_x "," Ratio_y ")
mathlover
3
@ Zelazny7 Estoy usando el segundo idxenfoque. Pero, solo puedo permitirme un máximo máximo para cada grupo (y mis datos tienen algunos duplicados-max). ¿Hay alguna manera de evitar esto con su solución?
3pitt
En realidad, eso no funciona para mí. No puedo rastrear el problema, porque el marco de datos si sale grande, pero la solución de @Rani funciona bien
Ladenkov Vladislav
Hola Zealzny, si quiero tomar la fila máxima de los 3 primeros en lugar de un valor máximo, ¿cómo puedo modificar su código?
Zephyr el
transformEl método puede tener un rendimiento de grupo cuando el conjunto de datos es lo suficientemente grande, obtenga el valor máximo primero y luego combinar los marcos de datos será mejor.
Woods Chen
170

Puede ordenar el dataFrame por conteo y luego eliminar duplicados. Creo que es más fácil:

df.sort_values('count', ascending=False).drop_duplicates(['Sp','Mt'])
Rani
fuente
44
¡Muy agradable! Rápido con marcos grandes (25k filas)
Nolan Conaway
2
Para aquellos que son algo nuevos con Python, deberá asignar esto a una nueva variable, no cambia la variable df actual.
Tyler
1
@Samir o úselo inplace = Truecomo argumento paradrop_duplicates
TMrtSmith
55
Esta es una gran respuesta cuando solo necesito una de las filas con los mismos valores máximos, sin embargo, no funcionará como se espera si necesito todas las filas con valores máximos.
Woods Chen, el
1
@WoodsChen, suelta duplicados de [sp, mt], por lo tanto, en su ejemplo, la salida debe ser solo una fila.
Rani
54

La solución fácil sería aplicar: la función idxmax () para obtener índices de filas con valores máximos. Esto filtraría todas las filas con el valor máximo en el grupo.

In [365]: import pandas as pd

In [366]: df = pd.DataFrame({
'sp' : ['MM1', 'MM1', 'MM1', 'MM2', 'MM2', 'MM2', 'MM4', 'MM4','MM4'],
'mt' : ['S1', 'S1', 'S3', 'S3', 'S4', 'S4', 'S2', 'S2', 'S2'],
'val' : ['a', 'n', 'cb', 'mk', 'bg', 'dgb', 'rd', 'cb', 'uyi'],
'count' : [3,2,5,8,10,1,2,2,7]
})

In [367]: df                                                                                                       
Out[367]: 
   count  mt   sp  val
0      3  S1  MM1    a
1      2  S1  MM1    n
2      5  S3  MM1   cb
3      8  S3  MM2   mk
4     10  S4  MM2   bg
5      1  S4  MM2  dgb
6      2  S2  MM4   rd
7      2  S2  MM4   cb
8      7  S2  MM4  uyi


### Apply idxmax() and use .loc() on dataframe to filter the rows with max values:
In [368]: df.loc[df.groupby(["sp", "mt"])["count"].idxmax()]                                                       
Out[368]: 
   count  mt   sp  val
0      3  S1  MM1    a
2      5  S3  MM1   cb
3      8  S3  MM2   mk
4     10  S4  MM2   bg
8      7  S2  MM4  uyi

### Just to show what values are returned by .idxmax() above:
In [369]: df.groupby(["sp", "mt"])["count"].idxmax().values                                                        
Out[369]: array([0, 2, 3, 4, 8])
Surya
fuente
44
El interlocutor aquí especificado "I want to get ALL the rows where count equals max in each group", de idxmax Return[s] index of first occurrence of maximum over requested axis"acuerdo con los documentos (0.21).
Max Power
1
Esta es una gran solución, pero para un problema diferente
Carlos Souza
33

Después de haber probado la solución sugerida por Zelazny en un DataFrame relativamente grande (~ 400k filas), me pareció muy lento. Aquí hay una alternativa que encontré para ejecutar órdenes de magnitud más rápido en mi conjunto de datos.

df = pd.DataFrame({
    'sp' : ['MM1', 'MM1', 'MM1', 'MM2', 'MM2', 'MM2', 'MM4', 'MM4', 'MM4'],
    'mt' : ['S1', 'S1', 'S3', 'S3', 'S4', 'S4', 'S2', 'S2', 'S2'],
    'val' : ['a', 'n', 'cb', 'mk', 'bg', 'dgb', 'rd', 'cb', 'uyi'],
    'count' : [3,2,5,8,10,1,2,2,7]
    })

df_grouped = df.groupby(['sp', 'mt']).agg({'count':'max'})

df_grouped = df_grouped.reset_index()

df_grouped = df_grouped.rename(columns={'count':'count_max'})

df = pd.merge(df, df_grouped, how='left', on=['sp', 'mt'])

df = df[df['count'] == df['count_max']]
Landewednack
fuente
1
De hecho, esto es mucho más rápido. transform parece ser lento para un gran conjunto de datos.
goh
1
¿Puedes agregar comentarios para explicar lo que hace cada línea?
tommy.carstensen
fwiw: Encontré que la solución de aspecto más elegante de @ Zelazny7 tardó mucho en ejecutarse para mi conjunto de ~ 100K filas, pero esta se ejecutó bastante rápido. (Estoy ejecutando un 0.13.0 ahora obsoleto, lo que podría explicar la lentitud).
Roland
2
Pero hacer esto df[df['count'] == df['count_max']]perderá filas de NaN, así como las respuestas anteriores.
Qy Zuo
Recomiendo encarecidamente utilizar este enfoque, para marcos de datos más grandes es mucho más rápido usar .appy () o .agg ().
Touya D. Serdan
18

Es posible que no tenga que ver con el grupo usando sort_values+drop_duplicates

df.sort_values('count').drop_duplicates(['Sp','Mt'],keep='last')
Out[190]: 
    Sp  Mt Value  count
0  MM1  S1     a      3
2  MM1  S3    cb      5
8  MM4  S2   uyi      7
3  MM2  S3    mk      8
4  MM2  S4    bg     10

También casi la misma lógica usando tail

df.sort_values('count').groupby(['Sp', 'Mt']).tail(1)
Out[52]: 
    Sp  Mt Value  count
0  MM1  S1     a      3
2  MM1  S3    cb      5
8  MM4  S2   uyi      7
3  MM2  S3    mk      8
4  MM2  S4    bg     10
YOBEN_S
fuente
Esto no solo es un orden de magnitud más rápido que las otras soluciones (al menos para mi caso de uso), sino que tiene el beneficio adicional de simplemente encadenar como parte de la construcción del marco de datos original.
Clay
Me estaba rascando la cabeza pensando que seguramente esto es simple, gracias por su brillante respuesta como siempre Sr. Wen.
Datanovice
7

Para mí, la solución más fácil sería mantener el valor cuando el recuento es igual al máximo. Por lo tanto, el siguiente comando de una línea es suficiente:

df[df['count'] == df.groupby(['Mt'])['count'].transform(max)]
PAC
fuente
4

Uso groupbyy idxmaxmétodos:

  1. transferir col datea datetime:

    df['date']=pd.to_datetime(df['date'])
  2. obtener el índice maxde columna date, después de groupyby ad_id:

    idx=df.groupby(by='ad_id')['date'].idxmax()
  3. obtener los datos deseados:

    df_max=df.loc[idx,]

Fuera [54]:

ad_id  price       date
7     22      2 2018-06-11
6     23      2 2018-06-22
2     24      2 2018-06-30
3     28      5 2018-06-22
blueear
fuente
2
df = pd.DataFrame({
'sp' : ['MM1', 'MM1', 'MM1', 'MM2', 'MM2', 'MM2', 'MM4', 'MM4','MM4'],
'mt' : ['S1', 'S1', 'S3', 'S3', 'S4', 'S4', 'S2', 'S2', 'S2'],
'val' : ['a', 'n', 'cb', 'mk', 'bg', 'dgb', 'rd', 'cb', 'uyi'],
'count' : [3,2,5,8,10,1,2,2,7]
})

df.groupby(['sp', 'mt']).apply(lambda grp: grp.nlargest(1, 'count'))
George Liu
fuente
2

Al darse cuenta de que "aplicar" "nlargest" a groupby object funciona igual de bien:

Ventaja adicional: también puede obtener los mejores valores n si es necesario:

In [85]: import pandas as pd

In [86]: df = pd.DataFrame({
    ...: 'sp' : ['MM1', 'MM1', 'MM1', 'MM2', 'MM2', 'MM2', 'MM4', 'MM4','MM4'],
    ...: 'mt' : ['S1', 'S1', 'S3', 'S3', 'S4', 'S4', 'S2', 'S2', 'S2'],
    ...: 'val' : ['a', 'n', 'cb', 'mk', 'bg', 'dgb', 'rd', 'cb', 'uyi'],
    ...: 'count' : [3,2,5,8,10,1,2,2,7]
    ...: })

## Apply nlargest(1) to find the max val df, and nlargest(n) gives top n values for df:
In [87]: df.groupby(["sp", "mt"]).apply(lambda x: x.nlargest(1, "count")).reset_index(drop=True)
Out[87]:
   count  mt   sp  val
0      3  S1  MM1    a
1      5  S3  MM1   cb
2      8  S3  MM2   mk
3     10  S4  MM2   bg
4      7  S2  MM4  uyi
Surya
fuente
2

Intente usar "nlargest" en el objeto groupby. La ventaja de usar nlargest es que devuelve el índice de las filas de donde se obtuvieron "los elementos más grandes". Nota: dividimos el segundo (1) elemento de nuestro índice ya que nuestro índice en este caso consiste en tuplas (por ejemplo, (s1, 0)).

df = pd.DataFrame({
'sp' : ['MM1', 'MM1', 'MM1', 'MM2', 'MM2', 'MM2', 'MM4', 'MM4','MM4'],
'mt' : ['S1', 'S1', 'S3', 'S3', 'S4', 'S4', 'S2', 'S2', 'S2'],
'val' : ['a', 'n', 'cb', 'mk', 'bg', 'dgb', 'rd', 'cb', 'uyi'],
'count' : [3,2,5,8,10,1,2,2,7]
})

d = df.groupby('mt')['count'].nlargest(1) # pass 1 since we want the max

df.iloc[[i[1] for i in d.index], :] # pass the index of d as list comprehension

ingrese la descripción de la imagen aquí

Escha
fuente
1

He estado usando este estilo funcional para muchas operaciones grupales:

df = pd.DataFrame({
   'Sp' : ['MM1', 'MM1', 'MM1', 'MM2', 'MM2', 'MM2', 'MM4', 'MM4', 'MM4'],
   'Mt' : ['S1', 'S1', 'S3', 'S3', 'S4', 'S4', 'S2', 'S2', 'S2'],
   'Val' : ['a', 'n', 'cb', 'mk', 'bg', 'dgb', 'rd', 'cb', 'uyi'],
   'Count' : [3,2,5,8,10,1,2,2,7]
})

df.groupby('Mt')\
  .apply(lambda group: group[group.Count == group.Count.max()])\
  .reset_index(drop=True)

    sp  mt  val  count
0  MM1  S1    a      3
1  MM4  S2  uyi      7
2  MM2  S3   mk      8
3  MM2  S4   bg     10

.reset_index(drop=True) lo lleva de vuelta al índice original al soltar el índice de grupo.

joh-mue
fuente