Obter as linhas que têm a contagem máxima em grupos usando groupby

243

Como localizo todas as linhas em um dataframe do pandas que tenham o valor máximo para a countcoluna após o agrupamento por ['Sp','Mt']colunas?

Exemplo 1: o seguinte dataFrame, que eu 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**

Saída esperada: obtenha as linhas de resultado cuja contagem é máxima entre os grupos, como:

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

Exemplo 2: esse quadro de dados, que eu 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 o exemplo acima, quero obter todas as linhas onde counté igual a max, em cada grupo, por exemplo:

MM2  S4   bg     10
MM4  S2   cb     8
MM4  S2   uyi    8
jojo12
fonte
Em que formato está o seu quadro de dados?
David Robinson
2
Eu não entendo. O que exatamente é um grupo? Por que a segunda linha no resultado começa 1 3?
Jo Então
stackoverflow.com/questions/18879782/... poderia ser útil
J_Arthur
1
Esta resposta é a solução mais rápida que pude encontrar: stackoverflow.com/a/21007047/778533
tommy.carstensen
Semelhante a esta pergunta, alguém poderia por favor responder: stackoverflow.com/questions/62069465/… Obrigado.
ds_Abc

Respostas:

323
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 obter os índices do DF original, você pode:

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

Observe que, se você tiver vários valores máximos por grupo, todos serão retornados.

Atualizar

Se houver uma chance, é isso que o OP está solicitando:

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
fonte
@ Zelazny7, existe uma maneira de adotar essa resposta para aplicar ao agrupamento por uma coluna e depois olhar para duas colunas e fazer o máximo delas para obter uma maior das duas? Não consigo fazer isso funcionar. O que eu tenho atualmente é: def Greater (Mesclar, maximumA, maximumB): a = Mesclar [maximumA] b = Mesclar [maximumB] retornar max (a, b) Merger.groupby ("Search_Term"). Apply (Greater, "Ratio_x "," Ratio_y ")
mathlover 15/11
3
@ Zelazny7 Estou usando a segunda idxabordagem. Porém, só posso pagar um único máximo para cada grupo (e meus dados têm alguns duplicados-máximos). existe uma maneira de contornar isso com sua solução?
3pitt
na verdade, isso não funciona para mim. Eu não posso controlar o problema, porque trama de dados de se sair grande, mas a solução por @Rani funciona bem
Ladenkov Vladislav
Olá Zealzny, Se eu quero ocupar o máximo de 3 linhas no máximo em vez de um valor máximo, como posso ajustar seu código?
Zephyr
transformO método pode ter desempenho de pool quando o conjunto de dados for grande o suficiente, obtenha o valor máximo primeiro e depois mesclar os quadros de dados.
Woods Chen
169

Você pode classificar o dataFrame por contagem e remover duplicatas. Eu acho que é mais fácil:

df.sort_values('count', ascending=False).drop_duplicates(['Sp','Mt'])
Rani
fonte
4
Muito agradável! Rápida com quadros bastante largo (25k linhas)
Nolan Conaway
2
Para quem é um pouco novo com o Python, você precisará atribuir isso a uma nova variável, isso não altera a variável df atual.
Tyler
1
@Samir ou utilização inplace = Truecomo um argumento paradrop_duplicates
TMrtSmith
5
Essa é uma ótima resposta quando precisar apenas de uma das linhas com os mesmos valores máximos, no entanto, não funcionará como esperado, se eu precisar de todas as linhas com valores máximos.
Woods Chen
1
@WoodsChen, ele remove duplicatas de [sp, mt]; portanto, no seu exemplo, a saída deve ser apenas uma linha.
Rani
54

A solução fácil seria aplicar a função: idxmax () para obter índices de linhas com valores máximos. Isso filtraria todas as linhas com valor máximo no 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
fonte
4
O questionador aqui 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"acordo com os documentos (0,21).
Max Power
1
Esta é uma ótima solução, mas para um problema diferente.
Carlos Souza
33

Tendo tentado a solução sugerida por Zelazny em um DataFrame relativamente grande (~ 400k linhas), achei muito lento. Aqui está uma alternativa que eu encontrei para executar ordens de magnitude mais rapidamente no meu conjunto de dados.

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
fonte
1
na verdade, isso é muito mais rápido. A transformação parece lenta para um conjunto de dados grande.
goh 11/07
1
Você pode adicionar comentários para explicar o que cada linha faz?
tommy.carstensen
fwiw: Encontrei a solução mais elegante do @ Zelazny7 que demorou muito tempo para ser executada no meu conjunto de ~ 100 mil linhas, mas essa foi executada rapidamente. (Estou executando um 0.13.0 agora obsoleto, o que pode explicar a lentidão).
Roland
2
Mas fazer isso df[df['count'] == df['count_max']]perderá linhas de NaN, bem como as respostas acima.
Qy Zuo
Eu sugiro usar essa abordagem, para quadros de dados maiores, é muito mais rápido usar .appy () ou .agg ().
Touya D. Serdan
18

Você pode não precisar se relacionar com o 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

Também quase a mesma 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
fonte
Isso não apenas é uma ordem de magnitude mais rápida que as outras soluções (pelo menos para o meu caso de uso), mas também tem o benefício adicional de encadear como parte da construção do quadro de dados original.
Clay
Eu estava coçando a cabeça pensando que certamente isso é simples, obrigado por sua resposta brilhante, como sempre, Sr. Wen.
Datanovice 26/11/19
7

Para mim, a solução mais fácil seria manter o valor quando a contagem for igual ao máximo. Portanto, o seguinte comando de uma linha é suficiente:

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

Uso groupbye idxmaxmétodos:

  1. transferir col datepara datetime:

    df['date']=pd.to_datetime(df['date'])
  2. obtenha o índice maxda coluna date, depois groupyby ad_id:

    idx=df.groupby(by='ad_id')['date'].idxmax()
  3. obtenha os dados desejados:

    df_max=df.loc[idx,]

Fora [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
fonte
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
fonte
2

Percebendo que "aplicar" "o maior" ao objeto agrupado funciona da mesma maneira:

Vantagem adicional - também pode buscar os n valores superiores, se necessário:

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
fonte
2

Tente usar "nlargest" no objeto groupby. A vantagem de usar nlargest é que ele retorna o índice das linhas de onde "os maiores itens" foram buscados. Nota: dividimos o segundo (1) elemento do nosso índice, pois nosso índice, nesse caso, consiste em tuplas (por exemplo, (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

insira a descrição da imagem aqui

escha
fonte
1

Eu tenho usado esse estilo funcional para muitas operações de grupo:

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) retorna ao índice original ao largar o índice de grupo.

joh-mue
fonte