Aquí hay un marco de datos de muestra:
d <- data.frame(
x = runif(90),
grp = gl(3, 30)
)
Quiero que el subconjunto d
contenga las filas con los 5 valores superiores de x
para cada valor de grp
.
Usando base-R, mi enfoque sería algo como:
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
Usando dplyr
, esperaba que esto funcionara:
d %>%
arrange_(~ desc(x)) %>%
group_by_(~ grp) %>%
head(n = 5)
pero solo devuelve las 5 filas superiores generales.
Cambiando head
por top_n
devoluciones todo d
.
d %>%
arrange_(~ desc(x)) %>%
group_by_(~ grp) %>%
top_n(n = 5)
¿Cómo obtengo el subconjunto correcto?
fuente
Bastante fácil con
data.table
demasiado ...library(data.table) setorder(setDT(d), -x)[, head(.SD, 5), keyby = grp]
O
setorder(setDT(d), grp, -x)[, head(.SD, 5), by = grp]
O (debería ser más rápido para el conjunto de big data porque se evita llamar
.SD
a cada grupo)setorder(setDT(d), grp, -x)[, indx := seq_len(.N), by = grp][indx <= 5]
Editar: así es como se
dplyr
compara condata.table
(si alguien está interesado)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
Añadiendo una
data.table
solución ligeramente más rápida :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 )
salida de tiempo:
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
fuente
data.table
método que debería ser un poco más rápido:dt <- setorder(setDT(dd), grp, -x); dt[dt[, .I[seq_len(.N) <= 5L], grp]$V1]
data.table
método más fácil:setDT(d)[order(-x),x[1:5],keyby = .(grp)]
:
que ganehead
setorder
más rápida queorder
Necesitas completar
head
una llamada ado
. En el siguiente código,.
representa el grupo actual (ver descripción de...
en lado
página de ayuda).d %>% arrange_(~ desc(x)) %>% group_by_(~ grp) %>% do(head(., n = 5))
Como lo menciona akrun,
slice
es una alternativa.d %>% arrange_(~ desc(x)) %>% group_by_(~ grp) %>% slice(1:5)
Aunque no pregunté esto, para completar, una posible
data.table
versión es (gracias a @Arun por la corrección):setDT(d)[order(-x), head(.SD, 5), by = grp]
fuente
setDT(d)[order(-x), head(.SD, 5L), by=grp]
~
y usaarrange
y engroup_by
lugar dearrange_
ygroup_by_
Mi enfoque en la base R sería:
ordered <- d[order(d$x, decreasing = TRUE), ] ordered[ave(d$x, d$grp, FUN = seq_along) <= 5L,]
Y usando dplyr, el enfoque con
slice
es probablemente más rápido, pero también podría usar,filter
que probablemente será más rápido que usardo(head(., 5))
:d %>% arrange(desc(x)) %>% group_by(grp) %>% filter(row_number() <= 5L)
punto de referencia 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
fuente
filter
requiere una función adicional, mientras que suslice
versión no ...data.table
aquí;)top_n (n = 1) todavía devolverá varias filas para cada grupo si la variable de orden no es única dentro de cada grupo. Para seleccionar precisamente una ocurrencia para cada grupo, agregue una variable única a cada fila:
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)
fuente
Una
data.table
solución más para resaltar su sintaxis concisa:setDT(d) d[order(-x), .SD[1:5], grp]
fuente