在多列上按组获取top_n行

时间:2019-06-03 13:57:47

标签: r dplyr data.table

我想知道是否有比下面的方法更好的解决方案。我有一个数据框,我想根据每个组的最高值来获取每一列的均值。

set.seed(123)
df <- data.frame(
  A = sample(c("A","B","C"), 20, replace=TRUE),
  B = rnorm(60, 5, 2),
  C = rnorm(60, 0, 2),
  D = rnorm(60, 10, 2))

library("dplyr")
top <- 5
top.B <- df %>% group_by(A) %>% top_n(n=top, wt=B) %>% summarize(top.A=mean(B))
top.C <- df %>% group_by(A) %>% top_n(n=-top, wt=C) %>% summarize(top.C=mean(C))
top.D <- df %>% group_by(A) %>% top_n(n=top, wt=D) %>% summarize(top.D=mean(D))
top5 <- merge(top.B, top.C, by="A")
top5 <- merge(top5, top.D, by="A")

我能够通过合并数据帧来完成它。结果看起来像:

  A    top.A     top.C    top.D
1 A 7.663078 -1.986632 12.62946
2 B 6.926882 -2.186245 13.18132
3 C 7.548887 -2.255001 12.15677

我想知道是否可以在不创建那些新数据框的情况下做到这一点。请注意,C列的平均值是从最低值开始的,或者是从顶部开始按降序排列的。

谢谢。

4 个答案:

答案 0 :(得分:2)

一种dplyr可能是:

df %>%
 group_by(A) %>%
 summarise_all(list(~ mean(.[dense_rank(desc(.)) <= 5])))

  A         B     C     D
  <fct> <dbl> <dbl> <dbl>
1 A      7.66  2.16  12.6
2 B      6.93  1.79  13.2
3 C      7.55  2.23  12.2

如果您想要C列的底部5个观察值:

df %>%
 group_by(A) %>%
 summarise(B = mean(B[dense_rank(desc(B)) <= 5]),
           C = mean(C[dense_rank(C) <= 5]),
           D = mean(D[dense_rank(desc(D)) <= 5]))

  A         B     C     D
  <fct> <dbl> <dbl> <dbl>
1 A      7.66 -1.99  12.6
2 B      6.93 -2.19  13.2
3 C      7.55 -2.26  12.2

答案 1 :(得分:2)

一个data.table选项:

获得前5名的平均值

get_mean_top5 <- function(x) -mean(sort(-x, partial = 1:5)[1:5])
df[, lapply(.SD, get_mean_top5), keyby = A, .SDcols = c("B", "D")]
#    A        B        D
# 1: A 6.097723 12.75887
# 2: B 7.942064 12.33379
# 3: C 8.190137 12.93201

如果是倒数5,则为平均值:

get_mean_bot5 <- function(x) mean(sort(x, partial = 1:5)[1:5])
df[, lapply(.SD, get_mean_bot5), keyby = A, .SDcols = c("C")]

要一步获得完整表格:

setDT(df, key = "A")
df[, lapply(.SD, get_mean_top5), keyby = A, .SDcols = c("B", "D")
   ][df[, lapply(.SD, get_mean_bot5), keyby = A, .SDcols = c("C")]]

答案 2 :(得分:1)

这是map

的一个选项
library(tidyverse)
map(names(df)[-1], ~ 
          df %>% 
             select(A, .x) %>%
             group_by(A) %>%
             top_n(n = top, wt = !! rlang::sym(.x)) %>% 
             summarise(!! str_c('top.', .x) := mean(!! rlang::sym(.x)))) %>%
     reduce(inner_join, by = 'A')
# A tibble: 3 x 4
#  A     top.B top.C top.D
#  <fct> <dbl> <dbl> <dbl>
#1 A      6.10  3.20  12.8
#2 B      7.94  2.17  12.3
#3 C      8.19  1.18  12.9

或者将frank中的data.tablesummarise_all一起使用(类似于@tmfmnk帖子中的选项)

library(data.table)
df %>%
    group_by(A) %>% 
    summarise_all(list( ~ mean(.[frank(-.) <= 5])))
# A tibble: 3 x 4
#  A         B     C     D
#  <fct> <dbl> <dbl> <dbl>
#1 A      6.10  3.20  12.8
#2 B      7.94  2.17  12.3
#3 C      8.19  1.18  12.9

或使用order

df %>% 
    group_by(A) %>%
    summarise_all(list(~ mean(.x[order(-.)][1:5])))
# A tibble: 3 x 4
#  A         B     C     D
#  <fct> <dbl> <dbl> <dbl>
#1 A      6.10  3.20  12.8
#2 B      7.94  2.17  12.3
#3 C      8.19  1.18  12.9

答案 3 :(得分:1)

以某种方式,我得到的价值与您不同,但是这种方法应该有效

library(dplyr)
df %>% 
  gather(key, value, -A) %>%
  group_by(A, key) %>%
  top_n(5, value) %>%
  summarise(m = mean(value)) %>%
  ungroup() %>%
  spread(key, m)

# A tibble: 3 x 4
  A         B     C     D
  <fct> <dbl> <dbl> <dbl>
1 A      6.10  3.20  12.8
2 B      7.94  2.17  12.3
3 C      8.19  1.18  12.9

这里的数据:

set.seed(123)
df <- data.frame(
  A = sample(c("A","B","C"), 20, replace=TRUE),
  B = rnorm(60, 5, 2),
  C = rnorm(60, 0, 2),
  D = rnorm(60, 10, 2))