Panda mendapatkan n teratas catatan dalam setiap kelompok


164

Misalkan saya memiliki panda DataFrame seperti ini:

>>> df = pd.DataFrame({'id':[1,1,1,2,2,2,2,3,4],'value':[1,2,3,1,2,3,4,1,1]})
>>> df
   id  value
0   1      1
1   1      2
2   1      3
3   2      1
4   2      2
5   2      3
6   2      4
7   3      1
8   4      1

Saya ingin mendapatkan DataFrame baru dengan 2 catatan teratas untuk setiap id, seperti ini:

   id  value
0   1      1
1   1      2
3   2      1
4   2      2
7   3      1
8   4      1

Saya dapat melakukannya dengan penomoran catatan dalam grup demi grup dengan:

>>> dfN = df.groupby('id').apply(lambda x:x['value'].reset_index()).reset_index()
>>> dfN
   id  level_1  index  value
0   1        0      0      1
1   1        1      1      2
2   1        2      2      3
3   2        0      3      1
4   2        1      4      2
5   2        2      5      3
6   2        3      6      4
7   3        0      7      1
8   4        0      8      1
>>> dfN[dfN['level_1'] <= 1][['id', 'value']]
   id  value
0   1      1
1   1      2
3   2      1
4   2      2
7   3      1
8   4      1

Tetapi apakah ada pendekatan yang lebih efektif / elegan untuk melakukan ini? Dan juga ada pendekatan yang lebih elegan untuk mencatat angka dalam setiap kelompok (seperti fungsi jendela SQL row_number () ).



1
"top-n" tidak berarti "baris paling atas / pertama / kepala", seperti yang Anda cari! Ini berarti "n baris dengan nilai terbesar".
smci

Jawaban:


183

Apakah kamu sudah mencoba? df.groupby('id').head(2)

Ouput dihasilkan:

>>> df.groupby('id').head(2)
       id  value
id             
1  0   1      1
   1   1      2 
2  3   2      1
   4   2      2
3  7   3      1
4  8   4      1

(Perlu diingat bahwa Anda mungkin perlu memesan / mengurutkan sebelumnya, tergantung pada data Anda)

EDIT: Seperti yang disebutkan oleh penanya, gunakan df.groupby('id').head(2).reset_index(drop=True)untuk menghapus multindex dan meratakan hasilnya.

>>> df.groupby('id').head(2).reset_index(drop=True)
    id  value
0   1      1
1   1      2
2   2      1
3   2      2
4   3      1
5   4      1

1
Ya, saya pikir itu saja. Mengabaikan hal ini entah bagaimana. Apakah Anda tahu cara yang baik untuk mencatat angka dalam grup?
Roman Pekar

4
Untuk mendapatkan hasil yang saya butuhkan, saya juga menambahkan.reset_index(drop=True)
Roman Pekar

1
github.com/pydata/pandas/pull/5510 baru saja bergabung; akan di 0,13, metode baru untuk melakukan hal ini disebut cumcount(nomor catatan di masing-masing kelompok)
Jeff

1
@Jeff kabar baik. Saya berharap saya memiliki lebih banyak waktu untuk berkontribusi pada Pandas :(
Roman Pekar

3
Untuk menjadikan @dorvak jawabannya lebih lengkap, jika Anda ingin 2 nilai terkecil per idlakukan df.sort_values(['id', 'value'], axis=0).groupby('id').head(2). Contoh lain, nilai terbesar per iddiberikan oleh df.sort_values(['id', 'value'], axis=0).groupby('id').tail(1).
Elmex80s

133

Sejak 0.14.1 , Anda sekarang dapat melakukan nlargestdan nsmallestpada groupbyobjek:

In [23]: df.groupby('id')['value'].nlargest(2)
Out[23]: 
id   
1   2    3
    1    2
2   6    4
    5    3
3   7    1
4   8    1
dtype: int64

Ada keanehan sedikit bahwa Anda mendapatkan indeks asli di sana juga, tapi ini mungkin benar-benar berguna tergantung pada apa indeks asli Anda adalah .

Jika Anda tidak tertarik dengannya, Anda bisa melakukannya .reset_index(level=1, drop=True)untuk menghilangkannya sama sekali.

(Catatan: Dari 0.17.1 Anda juga dapat melakukan ini di DataFrameGroupBy, tetapi untuk saat ini hanya berfungsi dengan Seriesdan SeriesGroupBy.)


Ada cara untuk mendapatkan unique_limit(n)? Seperti saya ingin yang pertama n nilai unik? Jika saya memintanya nlargestakan mengurutkan seluruh df yang bisa mahal
citynorman

2
Ini tidak berfungsi untuk kasus ketika Anda melakukan agregat di grup oleh? Misalnya, df.groupby([pd.Grouper(freq='M'), 'A'])['B'].count().nlargest(5, 'B') ini hanya mengembalikan keseluruhan 5 teratas di seluruh seri, bukan oleh masing-masing kelompok
geominded

Pernyataan bahwa ini sekarang juga mungkin di DataFrameGroupBys muncul untuk menjadi palsu, permintaan tarik terkait muncul untuk menambahkan nlargestke sederhana DataFrames saja. Yang agak disayangkan, karena bagaimana jika Anda ingin memilih lebih dari satu kolom?
oulenz

7

Terkadang mengurutkan seluruh data di depan sangat memakan waktu. Kita dapat mengelompokkannya terlebih dahulu dan melakukan topk untuk setiap grup:

g = df.groupby(['id']).apply(lambda x: x.nlargest(topk,['value'])).reset_index(drop=True)
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.