programing

그룹별 최상위 값 가져오기

copyandpastes 2023. 6. 15. 22:54
반응형

그룹별 최상위 값 가져오기

다음은 샘플 데이터 프레임입니다.

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

다음의 서브셋을 원합니다.d값이 상위 5개인 행 포함x각각의 가치에 대하여grp.

Base-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의 전액을 반환하기 위해d.

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

올바른 하위 집합을 얻으려면 어떻게 해야 합니까?

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

매우 간단합니다.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

포장이 필요합니다.head…을 방문하여다음 코드에서,.현재 그룹을 나타냅니다(설명 참조)....에서do도움말 페이지).

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

아크룬이 언급했듯이,slice대안입니다.

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

내가 이것을 묻지는 않았지만, 완성도를 위해, 가능성.data.table버전은 다음과 같습니다(수정을 위해 @Arun 덕분).

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

기본 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

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)

하나 더data.table간결한 구문을 강조하는 솔루션:

setDT(d)
d[order(-x), .SD[1:5], grp]

언급URL : https://stackoverflow.com/questions/27766054/getting-the-top-values-by-group

반응형