Berikut ini contoh kerangka data:
d <- data.frame(
x = runif(90),
grp = gl(3, 30)
)
Saya ingin subset d
berisi baris dengan 5 nilai teratas x
untuk setiap nilai grp
.
Menggunakan base-R, pendekatan saya akan menjadi seperti:
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
Dengan menggunakan dplyr
, saya berharap ini berhasil:
d %>%
arrange_(~ desc(x)) %>%
group_by_(~ grp) %>%
head(n = 5)
tetapi hanya mengembalikan 5 baris teratas secara keseluruhan.
Menukar head
untuk top_n
mengembalikan keseluruhan d
.
d %>%
arrange_(~ desc(x)) %>%
group_by_(~ grp) %>%
top_n(n = 5)
Bagaimana cara mendapatkan subset yang benar?
sumber
Cukup mudah dengan
data.table
juga ...library(data.table) setorder(setDT(d), -x)[, head(.SD, 5), keyby = grp]
Atau
setorder(setDT(d), grp, -x)[, head(.SD, 5), by = grp]
Atau (Harus lebih cepat untuk kumpulan data besar karena menghindari panggilan
.SD
untuk setiap grup)setorder(setDT(d), grp, -x)[, indx := seq_len(.N), by = grp][indx <= 5]
Edit: Berikut
dplyr
perbandingannyadata.table
(jika ada yang tertarik)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
Menambahkan solusi yang sedikit lebih cepat
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 )
keluaran waktu:
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
sumber
data.table
metode lain yang seharusnya sedikit lebih cepat:dt <- setorder(setDT(dd), grp, -x); dt[dt[, .I[seq_len(.N) <= 5L], grp]$V1]
data.table
metode lain lebih mudah:setDT(d)[order(-x),x[1:5],keyby = .(grp)]
:
akan mengalahkanhead
setorder
lebih cepat daripadaorder
Anda perlu menyelesaikan
head
panggilan kedo
. Dalam kode berikut,.
mewakili grup saat ini (lihat deskripsi...
dido
halaman bantuan).d %>% arrange_(~ desc(x)) %>% group_by_(~ grp) %>% do(head(., n = 5))
Seperti yang disebutkan akrun,
slice
merupakan alternatif.d %>% arrange_(~ desc(x)) %>% group_by_(~ grp) %>% slice(1:5)
Meskipun saya tidak menanyakan ini, untuk kelengkapan,
data.table
versi yang mungkin adalah (terima kasih kepada @Arun untuk perbaikannya):setDT(d)[order(-x), head(.SD, 5), by = grp]
sumber
setDT(d)[order(-x), head(.SD, 5L), by=grp]
~
dan menggunakanarrange
dangroup_by
bukannyaarrange_
dangroup_by_
Pendekatan saya di basis R adalah:
ordered <- d[order(d$x, decreasing = TRUE), ] ordered[ave(d$x, d$grp, FUN = seq_along) <= 5L,]
Dan menggunakan dplyr, pendekatan dengan
slice
mungkin tercepat, tetapi Anda juga bisa menggunakanfilter
yang kemungkinan akan lebih cepat daripada menggunakando(head(., 5))
:d %>% arrange(desc(x)) %>% group_by(grp) %>% filter(row_number() <= 5L)
patokan 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
sumber
filter
membutuhkan fungsi tambahan, sementaraslice
versi Anda tidak ...data.table
sini;)top_n (n = 1) masih akan mengembalikan beberapa baris untuk setiap grup jika variabel pemesanan tidak unik dalam setiap grup. Untuk memilih dengan tepat satu kemunculan untuk setiap grup, tambahkan variabel unik ke setiap baris:
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)
sumber
Satu lagi
data.table
solusi untuk menyoroti sintaksisnya yang ringkas:setDT(d) d[order(-x), .SD[1:5], grp]
sumber