Uzyskaj wiersze, które mają maksymalną liczbę w grupach za pomocą groupby

244

Jak znaleźć wszystkie wiersze w ramce danych pandy, które mają maksymalną wartość dla countkolumny, po zgrupowaniu według ['Sp','Mt']kolumn?

Przykład 1: następujące ramki danych, które grupuję według ['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**

Oczekiwany wynik: uzyskaj wiersze wyników, których liczba jest maksymalna między grupami, na przykład:

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

Przykład 2: ta ramka danych, którą grupuję według ['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

W powyższym przykładzie chcę uzyskać wszystkie wiersze o wartości countrównej maksimum w każdej grupie, np .:

MM2  S4   bg     10
MM4  S2   cb     8
MM4  S2   uyi    8
jojo12
źródło
W jakim formacie jest twoja ramka danych?
David Robinson
2
Nie rozumiem Czym dokładnie jest grupa? Dlaczego zaczyna się druga linia w wyniku 1 3?
Jo So
stackoverflow.com/questions/18879782/... Może być przydatny
J_Arthur 24.09.2013
1
Ta odpowiedź jest najszybszym rozwiązaniem, jakie udało mi się znaleźć: stackoverflow.com/a/21007047/778533
tommy.carstensen
Podobnie do tego pytania, czy ktoś mógłby odpowiedzieć na to: stackoverflow.com/questions/62069465/… Dzięki.
ds_Abc

Odpowiedzi:

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

Aby uzyskać indeksy oryginalnego DF, możesz:

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

Pamiętaj, że jeśli masz wiele maksymalnych wartości na grupę, wszystkie zostaną zwrócone.

Aktualizacja

Z okazji gradobicia, że ​​właśnie tego wymaga PO:

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
źródło
@ Zelazny7, czy istnieje sposób na przyjęcie tej odpowiedzi w celu zastosowania do grupowania według kolumny, a następnie spojrzenia na 2 kolumny i zrobienia maksimum z nich, aby uzyskać większą z tych dwóch? Nie mogę tego zmusić do pracy. Obecnie mam: 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 ”)
matlover,
3
@ Zelazny7 Używam drugiego idxpodejścia. Ale mogę sobie pozwolić tylko na jedno maksimum dla każdej grupy (a moje dane mają kilka duplikatów). czy jest sposób na obejście tego z rozwiązaniem?
3pitt
w rzeczywistości to nie działa dla mnie. Nie mogę wyśledzić problemu, ponieważ ramka danych jest dość duża, ale rozwiązanie @Rani działa dobrze
Ladenkov Vladislav
Cześć Zealzny, Jeśli chcę wziąć 3 najwyższe wiersze zamiast jednej wartości maksymalnej, jak mogę poprawić kod?
Zephyr
transformmetoda może mieć wydajność puli, gdy zestaw danych jest wystarczająco duży, najpierw uzyskaj maksymalną wartość, a następnie scal ramki danych.
Woods Chen,
170

Możesz posortować dataFrame według liczby, a następnie usunąć duplikaty. Myślę, że to łatwiejsze:

df.sort_values('count', ascending=False).drop_duplicates(['Sp','Mt'])
Rani
źródło
4
Bardzo dobrze! Szybki z dużymi ramkami (25 tys. Rzędów)
Nolan Conaway,
2
Dla tych, którzy są nieco nowi w Pythonie, musisz przypisać to do nowej zmiennej, to nie zmienia bieżącej zmiennej df.
Tyler
1
@Samir lub użyj inplace = Truejako argumentu dodrop_duplicates
TMrtSmith
5
To świetna odpowiedź, gdy potrzebujesz tylko jednego wiersza z tymi samymi maksymalnymi wartościami, jednak nie zadziała zgodnie z oczekiwaniami, jeśli potrzebuję wszystkich wierszy o maksymalnych wartościach.
Woods Chen
1
@WoodsChen upuszcza duplikaty [sp, mt], dlatego w twoim przykładzie dane wyjściowe powinny zawierać tylko jeden wiersz.
Rani
54

Łatwym rozwiązaniem byłoby zastosowanie: funkcja idxmax (), aby uzyskać indeksy wierszy o maksymalnych wartościach. Spowoduje to odfiltrowanie wszystkich wierszy o maksymalnej wartości w grupie.

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
źródło
4
Pytający tutaj określony "I want to get ALL the rows where count equals max in each group", a jednocześnie idxmax Return[s] index of first occurrence of maximum over requested axis"zgodnie z dokumentami (0.21).
Max Power
1
To świetne rozwiązanie, ale z innego powodu
Carlos Souza
33

Po wypróbowaniu rozwiązania zaproponowanego przez Zelazny na stosunkowo dużej ramce danych (~ 400 tys. Wierszy) stwierdziłem, że działa bardzo wolno. Oto alternatywa, którą znalazłem, aby szybciej uruchamiać rzędy wielkości w moim zestawie danych.

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
źródło
1
w rzeczywistości jest to o wiele szybsze. transformacja wydaje się być powolna w przypadku dużego zestawu danych.
goh
1
Czy możesz dodać komentarze, aby wyjaśnić, co robi każda linia?
tommy.carstensen
fwiw: Odkryłem, że bardziej elegancko wyglądające rozwiązanie z @ Zelazny7 zajęło dużo czasu dla mojego zestawu ~ 100 000 wierszy, ale ten działał dość szybko. (Używam teraz już przestarzałej wersji 0.13.0, co może tłumaczyć spowolnienie).
Roland
2
Ale zrobienie tego df[df['count'] == df['count_max']]spowoduje utratę wierszy NaN, a także powyższych odpowiedzi.
Qy Zuo,
Zdecydowanie sugeruję użycie tego podejścia, w przypadku większych ramek danych znacznie szybsze jest użycie .appy () lub .agg ().
Touya D. Serdan,
18

Może nie być konieczne korzystanie z grupy za pomocą 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

Również prawie taka sama logika przy użyciu 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
źródło
Jest to nie tylko rząd wielkości szybszy niż inne rozwiązania (przynajmniej w moim przypadku użycia), ale ma tę dodatkową zaletę, że po prostu łączy w ramach konstrukcji oryginalnej ramki danych.
Clay
Drapałem się po głowie, myśląc, że to na pewno proste, dzięki za wspaniałą odpowiedź, jak zawsze, panie Wen.
Datanovice
7

Dla mnie najłatwiejszym rozwiązaniem byłoby zachowanie wartości, gdy liczba jest równa maksimum. Dlatego wystarczy jedno polecenie w jednym wierszu:

df[df['count'] == df.groupby(['Mt'])['count'].transform(max)]
PAC
źródło
4

Zastosowanie groupbyi idxmaxmetody:

  1. przenieś col datedo datetime:

    df['date']=pd.to_datetime(df['date'])
  2. pobierz indeks maxkolumny datepo groupyby ad_id:

    idx=df.groupby(by='ad_id')['date'].idxmax()
  3. uzyskaj potrzebne dane:

    df_max=df.loc[idx,]

Out [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
źródło
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
źródło
2

Zrozumienie, że „zastosowanie” „największego” obiektu do grupowania działa równie dobrze:

Dodatkowa zaleta - w razie potrzeby można również pobrać najwyższe wartości n :

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
źródło
2

Spróbuj użyć „największego” obiektu grupy. Zaletą użycia nlargest jest to, że zwraca indeks wierszy, z których pobrano „najlżejsze elementy”. Uwaga: przecinamy drugi (1) element naszego indeksu, ponieważ nasz indeks w tym przypadku składa się z krotek (np. (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

wprowadź opis zdjęcia tutaj

escha
źródło
1

Używam tego funkcjonalnego stylu do wielu operacji grupowych:

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) przywraca pierwotny indeks, upuszczając indeks grupy.

joh-mue
źródło