Obteniendo los mejores valores por grupo

Aquí hay un dataframe de muestra:

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

Quiero que el subconjunto de d contenga las filas con los 5 valores superiores de x para cada valor de grp .

Usando base-R, mi enfoque sería algo así 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.

Al cambiar el head por top_n devuelve todo d .

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

¿Cómo obtengo el subconjunto correcto?

Desde ?top_n , sobre el argumento wt :

La variable a utilizar para ordenar […] se predetermina a la última variable en el tbl “.

La última variable en su conjunto de datos es “grp”, que no es la variable que desea clasificar, y es por eso que su top_n bash “devuelve el total de d”. Por lo tanto, si desea clasificar por “x” en su conjunto de datos, debe especificar wt = x .

 set.seed(123) d <- data.frame( x = runif(90), grp = gl(3, 30)) d %>% group_by(grp) %>% top_n(n = 5, wt = x) # x grp # 1 0.9404673 1 # 2 0.9568333 1 # 3 0.8998250 1 # 4 0.9545036 1 # 5 0.9942698 1 # 6 0.9630242 2 # 7 0.9022990 2 # 8 0.8578277 2 # 9 0.7989248 2 # 10 0.8950454 2 # 11 0.8146400 3 # 12 0.8123895 3 # 13 0.9849570 3 # 14 0.8930511 3 # 15 0.8864691 3 

Bastante fácil con data.table también …

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

O

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

O (debe ser más rápido para grandes conjuntos de datos porque evita llamar a .SD para cada grupo)

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

Editar: Así es como dplyr compara con data.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 

Debe envolver la head en una llamada a do . En el siguiente código,. representa el grupo actual (ver descripción de ... en la página de ayuda).

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

Como se menciona por akrun, slice es una alternativa.

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

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 el más rápido, pero también podría usar filter que probablemente sea más rápido que usar do(head(., 5)) :

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

dplyr punto de referencia

 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 

top_n (n = 1) seguirá devolviendo varias filas para cada grupo si la variable de ordenamiento 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)