รับค่าสูงสุดตามกลุ่ม


93

นี่คือกรอบข้อมูลตัวอย่าง:

d <- data.frame(
  x   = runif(90),
  grp = gl(3, 30)
) 

ฉันต้องการย่อยของdที่มีแถวกับด้านบน 5 ค่าสำหรับค่าของแต่ละxgrp

การใช้ฐาน -R แนวทางของฉันจะเป็นดังนี้:

ordered <- d[order(d$x, decreasing = TRUE), ]    
splits <- split(ordered, ordered$grp)
heads <- lapply(splits, head)
do.call(rbind, heads)
##              x grp
## 1.19 0.8879631   1
## 1.4  0.8844818   1
## 1.12 0.8596197   1
## 1.26 0.8481809   1
## 1.18 0.8461516   1
## 1.29 0.8317092   1
## 2.31 0.9751049   2
## 2.34 0.9269764   2
## 2.57 0.8964114   2
## 2.58 0.8896466   2
## 2.45 0.8888834   2
## 2.35 0.8706823   2
## 3.74 0.9884852   3
## 3.73 0.9837653   3
## 3.83 0.9375398   3
## 3.64 0.9229036   3
## 3.69 0.8021373   3
## 3.86 0.7418946   3

โดยใช้dplyrฉันคาดว่าสิ่งนี้จะใช้งานได้:

d %>%
  arrange_(~ desc(x)) %>%
  group_by_(~ grp) %>%
  head(n = 5)

แต่จะส่งกลับเฉพาะแถว 5 อันดับแรกโดยรวมเท่านั้น

การแลกเปลี่ยนheadเพื่อtop_nส่งคืนทั้งd.

d %>%
  arrange_(~ desc(x)) %>%
  group_by_(~ grp) %>%
  top_n(n = 5)

ฉันจะรับชุดย่อยที่ถูกต้องได้อย่างไร

คำตอบ:


126

จากdplyr 1.0.0 " slice_min()และslice_max()เลือกแถวที่มีค่าต่ำสุดหรือสูงสุดของตัวแปรโดยยึดจากความสับสนtop_n()."

d %>% group_by(grp) %>% slice_max(order_by = x, n = 5)
# # A tibble: 15 x 2
# # Groups:   grp [3]
#     x grp  
# <dbl> <fct>
#  1 0.994 1    
#  2 0.957 1    
#  3 0.955 1    
#  4 0.940 1    
#  5 0.900 1    
#  6 0.963 2    
#  7 0.902 2    
#  8 0.895 2    
#  9 0.858 2    
# 10 0.799 2    
# 11 0.985 3    
# 12 0.893 3    
# 13 0.886 3    
# 14 0.815 3    
# 15 0.812 3

ก่อนdplyr 1.0.0ใช้top_n:

จาก?top_nเกี่ยวกับwtอาร์กิวเมนต์:

ตัวแปรที่จะใช้ในการสั่งซื้อ [... ] ค่าเริ่มต้นคือตัวแปรสุดท้ายใน tbl "

ตัวแปรสุดท้ายในชุดข้อมูลของคุณคือ "grp" ซึ่งไม่ใช่ตัวแปรที่คุณต้องการจัดอันดับและด้วยเหตุนี้คุณจึงtop_nพยายาม "คืนค่า d ทั้งหมด" ดังนั้นหากคุณต้องการในการจัดอันดับโดย "X" wt = xในชุดข้อมูลของคุณคุณจะต้องระบุ

d %>%
  group_by(grp) %>%
  top_n(n = 5, wt = x)

ข้อมูล:

set.seed(123)
d <- data.frame(
  x = runif(90),
  grp = gl(3, 30))

7
มีการเพิกเฉยต่อความสัมพันธ์หรือไม่?
MatíasGuzmán Naranjo


41

ค่อนข้างง่ายด้วยdata.table...

library(data.table)
setorder(setDT(d), -x)[, head(.SD, 5), keyby = grp]

หรือ

setorder(setDT(d), grp, -x)[, head(.SD, 5), by = grp]

หรือ (ควรจะเร็วกว่าสำหรับชุดข้อมูลขนาดใหญ่เนื่องจากหลีกเลี่ยงการโทร.SDหาแต่ละกลุ่ม)

setorder(setDT(d), grp, -x)[, indx := seq_len(.N), by = grp][indx <= 5]

แก้ไข:นี่คือวิธีdplyrเปรียบเทียบกับdata.table(ถ้าใครสนใจ)

set.seed(123)
d <- data.frame(
  x   = runif(1e6),
  grp = sample(1e4, 1e6, TRUE))

library(dplyr)
library(microbenchmark)
library(data.table)
dd <- copy(d)

microbenchmark(
  top_n = {d %>%
             group_by(grp) %>%
             top_n(n = 5, wt = x)},
  dohead = {d %>%
              arrange_(~ desc(x)) %>%
              group_by_(~ grp) %>%
              do(head(., n = 5))},
  slice = {d %>%
             arrange_(~ desc(x)) %>%
             group_by_(~ grp) %>%
             slice(1:5)},
  filter = {d %>% 
              arrange(desc(x)) %>%
              group_by(grp) %>%
              filter(row_number() <= 5L)},
  data.table1 = setorder(setDT(dd), -x)[, head(.SD, 5L), keyby = grp],
  data.table2 = setorder(setDT(dd), grp, -x)[, head(.SD, 5L), grp],
  data.table3 = setorder(setDT(dd), grp, -x)[, indx := seq_len(.N), grp][indx <= 5L],
  times = 10,
  unit = "relative"
)


#        expr        min         lq      mean     median        uq       max neval
#       top_n  24.246401  24.492972 16.300391  24.441351 11.749050  7.644748    10
#      dohead 122.891381 120.329722 77.763843 115.621635 54.996588 34.114738    10
#       slice  27.365711  26.839443 17.714303  26.433924 12.628934  7.899619    10
#      filter  27.755171  27.225461 17.936295  26.363739 12.935709  7.969806    10
# data.table1  13.753046  16.631143 10.775278  16.330942  8.359951  5.077140    10
# data.table2  12.047111  11.944557  7.862302  11.653385  5.509432  3.642733    10
# data.table3   1.000000   1.000000  1.000000   1.000000  1.000000  1.000000    10

การเพิ่มdata.tableโซลูชันที่เร็วขึ้นเล็กน้อย:

set.seed(123L)
d <- data.frame(
    x   = runif(1e8),
    grp = sample(1e4, 1e8, TRUE))
setDT(d)
setorder(d, grp, -x)
dd <- copy(d)

library(microbenchmark)
microbenchmark(
    data.table3 = d[, indx := seq_len(.N), grp][indx <= 5L],
    data.table4 = dd[dd[, .I[seq_len(.N) <= 5L], grp]$V1],
    times = 10L
)

เอาต์พุตเวลา:

Unit: milliseconds
        expr      min       lq     mean   median        uq      max neval
 data.table3 826.2148 865.6334 950.1380 902.1689 1006.1237 1260.129    10
 data.table4 729.3229 783.7000 859.2084 823.1635  966.8239 1014.397    10

เพิ่มdata.tableวิธีอื่นซึ่งน่าจะเร็วกว่าเล็กน้อย:dt <- setorder(setDT(dd), grp, -x); dt[dt[, .I[seq_len(.N) <= 5L], grp]$V1]
chinsoon12

@ chinsoon12 เป็นแขกของฉัน ฉันไม่มีเวลาเปรียบเทียบโซลูชันเหล่านี้อีกแล้ว
David Arenburg

เพิ่มdata.tableวิธีอื่นให้ง่ายขึ้น:setDT(d)[order(-x),x[1:5],keyby = .(grp)]
เต้าหู

@TaoHu มันค่อนข้างเหมือนกับสองวิธีแรก ฉันไม่คิดว่า:จะเอาชนะhead
David Arenburg

@DavidArenburg ใช่, ฉันเห็นด้วยกับคุณฉันคิดว่าความแตกต่างที่สุดsetorderเร็วกว่าorder
Tao Hu

34

คุณจำเป็นต้องห่อในการเรียกไปยังhead doในรหัสต่อไปนี้.แสดงถึงกลุ่มปัจจุบัน (ดูคำอธิบาย...ในdoหน้าวิธีใช้)

d %>%
  arrange_(~ desc(x)) %>%
  group_by_(~ grp) %>%
  do(head(., n = 5))

ดังกล่าวโดย akrun sliceเป็นอีกทางเลือกหนึ่ง

d %>%
  arrange_(~ desc(x)) %>%
  group_by_(~ grp) %>%
  slice(1:5)

แม้ว่าฉันจะไม่ได้ถามสิ่งนี้ แต่เพื่อความสมบูรณ์data.tableเวอร์ชันที่เป็นไปได้คือ (ขอบคุณ @Arun สำหรับการแก้ไข):

setDT(d)[order(-x), head(.SD, 5), by = grp]

1
@akrun ขอบคุณ ฉันไม่รู้เกี่ยวกับฟังก์ชันนั้น
Richie Cotton

@DavidArenburg ขอบคุณ นั่นคือสิ่งที่มาจากการโพสต์คำตอบอย่างเร่งรีบ ฉันลบเรื่องไร้สาระออกไปแล้ว
Richie Cotton

2
ริชชี่ FWIW คุณแค่ต้องการเพิ่มเล็กน้อย:setDT(d)[order(-x), head(.SD, 5L), by=grp]
อรุณ

คำตอบนี้ค่อนข้างล้าสมัย แต่ส่วนที่สองเป็นวิธีที่ไม่ได้ใช้หากคุณวาง~และใช้arrangeและgroup_byแทนที่จะเป็นarrange_และgroup_by_
Moody_Mudskipper

15

แนวทางของฉันในฐาน R คือ:

ordered <- d[order(d$x, decreasing = TRUE), ]
ordered[ave(d$x, d$grp, FUN = seq_along) <= 5L,]

และการใช้ dplyr วิธีsliceนี้อาจเร็วที่สุด แต่คุณสามารถใช้filterซึ่งน่าจะเร็วกว่าการใช้do(head(., 5)):

d %>% 
  arrange(desc(x)) %>%
  group_by(grp) %>%
  filter(row_number() <= 5L)

มาตรฐาน dplyr

set.seed(123)
d <- data.frame(
  x   = runif(1e6),
  grp = sample(1e4, 1e6, TRUE))

library(microbenchmark)

microbenchmark(
  top_n = {d %>%
             group_by(grp) %>%
             top_n(n = 5, wt = x)},
  dohead = {d %>%
              arrange_(~ desc(x)) %>%
              group_by_(~ grp) %>%
              do(head(., n = 5))},
  slice = {d %>%
             arrange_(~ desc(x)) %>%
             group_by_(~ grp) %>%
             slice(1:5)},
  filter = {d %>% 
              arrange(desc(x)) %>%
              group_by(grp) %>%
              filter(row_number() <= 5L)},
  times = 10,
  unit = "relative"
)

Unit: relative
   expr       min        lq    median        uq       max neval
  top_n  1.042735  1.075366  1.082113  1.085072  1.000846    10
 dohead 18.663825 19.342854 19.511495 19.840377 17.433518    10
  slice  1.000000  1.000000  1.000000  1.000000  1.000000    10
 filter  1.048556  1.044113  1.042184  1.180474  1.053378    10

@akrun filterต้องการฟังก์ชันเพิ่มเติมในขณะที่sliceเวอร์ชันของคุณไม่ ...
David Arenburg

1
คุณรู้แล้วว่าทำไมคุณไม่เพิ่มdata.tableที่นี่)
David Arenburg

5
ฉันรู้และบอกคุณได้: เพราะคำถามนั้นถามโดยเฉพาะสำหรับโซลูชัน dplyr
Talat

1
ฉันแค่ล้อเล่น ... มันไม่เหมือนที่คุณไม่เคยทำแบบนั้นเลย (ในเรื่องที่ตรงกันข้าม)
David Arenburg

@DavidArenburg ฉันไม่ได้บอกว่ามัน "ผิดกฎหมาย" หรืออะไรก็ตามที่ต้องการให้ข้อมูลคำตอบที่แน่นอน .. แน่นอนว่าคุณทำได้และระบุเกณฑ์มาตรฐานที่คุณต้องการ :) Btw คำถามที่คุณเชื่อมโยงเป็นตัวอย่างที่ดี โดยที่ไวยากรณ์ dplyr สะดวกกว่า (ฉันรู้ว่าอัตนัย!) มากกว่า data.table
Talat

1

top_n (n = 1) จะยังคงส่งคืนหลายแถวสำหรับแต่ละกลุ่มหากตัวแปรการสั่งซื้อไม่ซ้ำกันภายในแต่ละกลุ่ม ในการเลือกเหตุการณ์ที่แน่นอนสำหรับแต่ละกลุ่มให้เพิ่มตัวแปรที่ไม่ซ้ำกันในแต่ละแถว:

set.seed(123)
d <- data.frame(
  x   = runif(90),
  grp = gl(3, 30))

d %>%
  mutate(rn = row_number()) %>% 
  group_by(grp) %>%
  top_n(n = 1, wt = rn)

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.