使用 dplyr 中的 filter、group_by 和 tail?

14

这是一个示例数据框 df:

df <- structure(list(x = 1:30, y = 101:130, g = structure(c(1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L), .Label = c("A", "B", "C"), class = "factor")), .Names = c("x", "y", "g"), row.names = c(NA, -30L), class = "data.frame")

我希望能够获取筛选数据中每个组的10个最小的y值。

但是

df2 <- df %>% filter(x>3) %>% group_by(g) %>%  tail(y, n=10)

仅返回最后一组的行(在此情况下为C):

Source: local data frame [10 x 3]
Groups: g

    x   y g
18 21 121 C
19 22 122 C
20 23 123 C
21 24 124 C
22 25 125 C
23 26 126 C
24 27 127 C
25 28 128 C
26 29 129 C
27 30 130 C

我哪里做错了吗?


你是指 y 的最小值还是每个组的最后一个条目? - talat
@beginneR,你说得完全正确,我是指每个组的最低条目(而不是最后一条),这在我的原始数据框中可能是相同的,但当然很重要。谢谢你指出来! - erc
谢谢澄清。请注意,tail 只会选择向量或数据框的最后一些条目(在这种情况下为最后几行),因此除非您在之前对数据进行排序,否则 tail 并不一定选择最低的值(只有当它们恰好位于那个位置时才是这样)。 - talat
另外一个问题:在出现平局的情况下,你希望怎样处理?是只选择确切的前10行,还是选择所有y值为最低的10行? - talat
6个回答

25
您可以在do内部使用tail
df2 <- df %>% filter(x>3) %>% group_by(g) %>%  do(tail(., n=10))
.的使用对于此功能至关重要。从do帮助页面中可以看到:"您可以使用.来指代当前组。"
编辑:
正如@beginneR所指出的,我只关注如何在使用dplyr的组中使用tail,而忽略了问题中关于寻找10个y最小值的部分。正确完成这个任务需要添加arrange,使用tail时,这意味着按照y的降序排列。
df2 <- df %>% filter(x>3) %>% group_by(g) %>%  arrange(desc(y)) %>% do(tail(., n=10))

7

这里还有两个选项:

df %>% filter(x>3) %>% group_by(g) %>% top_n(3, desc(y))

在这里,我们使用了top_n,但是使用了desc(y),因为我们想要最小的y值,而不是最大的("top")y值。

df %>% filter(x>3) %>% group_by(g) %>% arrange(y) %>% filter(1:n() <= 10)

这相当于

df %>% filter(x>3) %>% group_by(g) %>% arrange(y) %>% slice(1:10)

分组后,我们按增加的 y 排序每个组,然后选择每个组的前10行(如果组内行数不足10行,则选择所有行)。

由于有些人对需要选择的最小值和最后条目存在困惑:本答案选择的是最低值,而不是最后的条目。


2
更紧凑的语法通过data.table实现:
library(data.table)
dt = as.data.table(df)

# original tail question
dt[x > 3, tail(y, 10), by = g]

# 10 smallest values of y
# many options for this, here's one:
dt[x > 3, head(sort(y), 10), by = g]

# here's another, trying to take advantage of setkey speed
setkey(dt, g, y)
dt[x > 3, head(y, 10), by = g]

2
我不知道为什么 tail 不起作用,但你可以尝试这个方法:
df %>% 
 filter(x > 3) %>% 
 group_by(g) %>% 
 filter(.,rank(desc(y),ties.method = "min") <= 10)

太好了,谢谢,我不知道rank。希望等一会儿再接受,看看是否有使用tail的解决方案。 - erc
@Spacedman 你是对的,我主要是试图模仿Hadley在top_n中所做的。 - joran
这取决于OP的确切意思,目前还不清楚。如果需要,迄今为止所有的答案都可以轻松地进行调整。 - joran
@joran 是的,没错。这也是我删除评论并再次询问 OP 他想做什么的原因。我只是对“最低值”这个描述有些疑惑。 - talat
1
你需要 . 吗?你不能只使用 filter(min_rank(desc(y)) <= 10) 吗? - rrs
显示剩余2条评论

2

更多答案!这是一个计算排名的“窗口函数”之一,非常适合此工作。

df %>%
  filter(x > 3) %>%
  group_by(g) %>%
  filter(y %>% min_rank <= 10)

1
这仍然不会选择最低的值,正如 OP(原帖发布者)在此期间指定的那样。如果您选择 3 而不是 10,请检查一下。 - talat
@beginneR 感谢你指出这点!我的错误在于使用了 desc。现在可以工作了吗,还是我完全误解了问题? :P - AndrewMacDonald
现在看起来不错。样本数据选择不好(或选择10行的决策)因为在一些组中,在第一个过滤器之后少于10行,所以函数与所需的不同是不明显的(尽管结果可能恰好符合预期)。 - talat
为什么要在 filter 中使用管道符号?难道不能直接使用 filter(min_rank(y) <= 10) 吗? - rrs
@rrs因为它很漂亮。使用magrittr,甚至可以说y %>% min_rank %>% is_weakly_less_than(10)。我想,如果必须要为我的行为辩护,我会说将过滤变量立即放在左侧是很好的,而不是在嵌套的括号中寻找它。 - AndrewMacDonald

1
为什么尾部函数应该与分组数据帧一起使用?它不知道有关分组的信息。
添加顺序列,按该列选择,删除该列。这里我使用3而不是10以增加紧凑性(并且使用%.%因为dplyr旧版本):
> df %.% filter(x>3) %.% group_by(g) %.% mutate(i=order(y)) %.% filter(i <= 3) %.% select(-matches("i"))
Source: local data frame [9 x 3]
Groups: g

   x   y g
1  4 104 A
2  5 105 A
3  6 106 A
4 11 111 B
5 12 112 B
6 13 113 B
7 21 121 C
8 22 122 C
9 23 123 C

4
谢谢,我会尝试这样做。但是我不理解你的第一个论点,“mean”,“sum”等函数也不知道分组,这不是使用dplyr或类似的分组函数的全部意义吗?那么tail又有什么不同呢? - erc
当使用 dplyr 中的 mean 函数时,您需要将其放在 summarise 中并获取单个值。您不能直接将其传递到 mean 函数中... - Spacedman

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接