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 () ).

Roman Pekar
sumber
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
dorvak
sumber
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.)

LondonRob
sumber
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)
Chaffee Chen
sumber