在data.table中使用ifelse选择每个组的一行

17

我正在对一个data.table进行分组,并希望从每个组中选择第一行满足x == 1的行,如果不存在这样的行,则选择具有任何x值的第一行

我正在对一个data.table进行分组,并希望从每个组中选择第一行满足x == 1的行,如果不存在这样的行,则选择具有任何x值的第一行

d <- data.table(
           a = c(1,1,1,  2,2,  3,3), 
           x = c(0,1,0,  0,0,  1,1), 
           y = c(1,2,3,  1,2,  1,2)
)

这次尝试

d[, ifelse(any(.SD[,x] == 1),.SD[x == 1][1], .SD[1]), by = a]
返回
   a V1
1: 1  1
2: 2  0
3: 3  1

但我期望

   a  x  y
1: 1  1  2
2: 2  0  1
3: 3  1  1

有什么办法可以做得正确吗?


ifelse将返回与第一个参数的长度相同的长度。(见 ?ifelse) 在这种情况下,第一个参数是单个逻辑值。我将ifelse用作最后的手段。即使在这种情况下,大多数情况下我也不使用它。并且我建议远离它。除了所有这些要记住的事情之外,它还很慢。 - Arun
3个回答

15

我认为这是同时使用matchnomatch参数的一个很好的用例

d[, .SD[match(1L, x, nomatch = 1L)], by = a]
#    a x y
# 1: 1 1 2
# 2: 2 0 1
# 3: 3 1 1

如果找不到匹配项,则基本上返回1,结果将为您提供组中的第一行。如果存在多个匹配项,则会根据您的要求返回第一个匹配项。


15

另一种选择(which.max 基本上就是为了做你想要的事情而设计的):

d[, .SD[which.max(x == 1)], by = a]
#   a x y
#1: 1 1 2
#2: 2 0 1
#3: 3 1 1

6

我们也可以使用 .I 来返回行索引,并将其用于子集化行。

d[d[, .I[which.max(x==1)], by = a]$V1]
#   a x y
#1: 1 1 2
#2: 2 0 1
#3: 3 1 1

在当前版本的data.table中,相比于.SD.I方法在子集行方面更加高效(但这在未来可能会改变)。这也是一个类似的帖子


另一种选择是使用order(为了效率还可以使用setkey)对数据集按'a'和'x'分组后排序,然后用head获取第一行。

d[order(a ,-x), head(.SD, 1) ,by = a]
#   a x y
#1: 1 1 2
#2: 2 0 1
#3: 3 1 1

性能测试

我们最初考虑在超过100万行的数据上进行性能测试,但是.SD方法的运行时间较长,因此我们选择在使用data.table_1.9.7时仅比较3e5行数据。

set.seed(24)
d1 <- data.table(a = rep(1:1e5, 3), x = sample(0:1, 1e5*3, 
           replace=TRUE), y = rnorm(1e5*3))

system.time(d1[, .SD[which.max(x == 1)], by = a])
#   user  system elapsed 
#  56.21   30.64   86.42 

system.time(d1[, .SD[match(1L, x, nomatch = 1L)], by = a])
# user  system elapsed 
#  55.27   30.07   83.75 

system.time(d1[d1[, .I[which.max(x==1)], by = a]$V1])
#  user  system elapsed 
#   0.19    0.00    0.19 


system.time(d1[order(a ,-x), head(.SD, 1) ,by = a])
# user  system elapsed 
#   0.03    0.00    0.04 

4
一个熟悉的变化,来源于 https://dev59.com/1WQn5IYBdhLWcg3wxZni#16574176/。值得一提的是,与 .SD[stuff] 方法相比,可能会有性能上的改进。 - Frank
@SteffenJ。正如Arun在帖子中评论的那样,最好不要使用ifelse。在这种情况下,您只想为每个“a”子集一个单独的行。在这种情况下,您可以使用if。即d[, if(any(.SD[,x] == 1)) .SD[x == 1][1] else .SD[1], by = a]但是,这可能会很慢,因为我们三次使用了.SD,并且不推荐。另一种情况是ifelse适用于vectors - akrun
在1.9.7版本上吗? - David Arenburg
4
不管怎样,你的回答只是两个eddis答案的抄袭。我认为你不应该把它发布为自己的回答。 - David Arenburg
2
是的,我不认为有人会相信你完全抄袭了它。我们都内化了这个习语,在回答和工作中多次使用,我想(尽管我总是试图引用原始来源,因为我肯定自己从未想过)。 - Frank
显示剩余6条评论

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