根据条件,从一个大数据表中选择每个组中的一行。

7

我有一个表格,其中键被重复多次,现在需要选择每个键的一行数据,选择时需要使用另外一列中最大值。以下是目前的解决方案示例:

N = 10
k = 2
DT = data.table(X = rep(1:N, each = k), Y = rnorm(k*N))
     X           Y
 1:  1 -1.37925206
 2:  1 -0.53837461
 3:  2  0.26516340
 4:  2 -0.04643483
 5:  3  0.40331424
 6:  3  0.28667275
 7:  4 -0.30342327
 8:  4 -2.13143267
 9:  5  2.11178673
10:  5 -0.98047230
11:  6 -0.27230783
12:  6 -0.79540934
13:  7  1.54264549
14:  7  0.40079650
15:  8 -0.98474297
16:  8  0.73179201
17:  9 -0.34590491
18:  9 -0.55897393
19: 10  0.97523187
20: 10  1.16924293
> DT[, .SD[Y == max(Y)], by = X]
     X          Y
 1:  1 -0.5383746
 2:  2  0.2651634
 3:  3  0.4033142
 4:  4 -0.3034233
 5:  5  2.1117867
 6:  6 -0.2723078
 7:  7  1.5426455
 8:  8  0.7317920
 9:  9 -0.3459049
10: 10  1.1692429

问题在于对于较大的数据表,这需要很长时间:
N = 10000
k = 25
DT = data.table(X = rep(1:N, each = k), Y = rnorm(k*N))
system.time(DT[, .SD[Y == max(Y)], by = X])
   user  system elapsed 
   9.69    0.00    9.69 

我的实际表格有一亿行...

有人能提供更高效的解决方案吗?


编辑 - set key 的重要性

所提出的解决方案很好,但必须使用 setkey 或使 DT 有序才能正常工作:

查看 rep 中未使用 "each" 的示例:

N = 10
k = 2
DT = data.table(X = rep(1:N, k), Y = rnorm(k*N))
DT[DT[, Y == max(Y), by = X]$V1,]
     X           Y
 1:  1  1.26925708
 2:  4 -0.66625732
 3:  5  0.41498548
 4:  8  0.03531185
 5:  9  0.30608380
 6:  1  0.50308578
 7:  4  0.19848227
 8:  6  0.86458423
 9:  8  0.69825500
10: 10 -0.38160503

1
这是因为.SD[..]为每个组调用[.data.table。我们已经意识到这一点,很可能会在1.9.8中进行优化。有一些解决方案可以绕过这个问题(通常使用.I),你可以通过搜索找到。我会在优化后更新这篇文章。 - Arun
@Arun,“cran”版本与“devel”版本对于“.SD”解决方案(由Khashaa和AnandaMahto测试)提供了不同的结果/错误。我正在使用开发版本。 - akrun
1
@akrun,corone,如果你们使用的是1.9.4版本,请执行以下操作:options(datatable.auto.index=FALSE),这样就可以正常工作了。这是新功能中的一个错误,在1.9.5版本中已经修复。 - Arun
1
@Corone,不确定你是否意识到,如果给定X的max(Y)重复,你的代码将返回每个唯一X的多行。 - talat
@docendodiscimus 是的,说得好。在我的实际情况中,数据不会有并列项(或者就像例子中出现并列项那样不太可能),但是我想每个组的“条件”都需要得到唯一满足。 - Corvus
你可以尝试一下这个:unique(setorder(DT, -Y), by = X)。对于你的大样本数据,在我的笔记本电脑上使用该方法的system.time都为0。它还将确保每个X只返回一行,即使存在并列情况。 - talat
1个回答

7

这将比.SD更快。

 system.time({setkey(DT, X)
    DT[DT[,Y==max(Y), by=X]$V1,]})
  # user  system elapsed 
  #0.016   0.000   0.016 

或者

system.time(DT[DT[, .I[Y==max(Y)], by=X]$V1])
#  user  system elapsed 
# 0.023   0.000   0.023 

如果只有两列,
system.time(DT[,list(Y=max(Y)), by=X])
#   user  system elapsed 
#  0.006   0.000   0.007 

相比之下,

system.time(DT[, .SD[Y == max(Y)], by = X] )
#  user  system elapsed 
# 2.946   0.006   2.962 

根据@Khashaa,@AnandaMahto的评论,CRAN版本(1.9.4)与dev版本(1.9.5)在使用.SD方法时会得到不同的结果。我使用的是dev版本。根据@Arun的评论,您可以通过设置options来获得“CRAN”版本相同的结果。

 options(datatable.auto.index=FALSE)

注意:如果出现“并列”的情况,本文所述的解决方案将为每个组返回多行(如@docendo discimus所提到的)。我的解决方案基于OP发布的“代码”。
如果有“并列”,则可以使用uniqueby选项(如果列数> 2)。
 setkey(DT,X)
 unique(DT[DT[,Y==max(Y), by=X]$V1,], by=c("X", "Y"))

微基准测试

library(microbenchmark)
f1 <- function(){setkey(DT,X)[DT[, Y==max(Y), by=X]$V1,]}
f2 <- function(){DT[DT[, .I[Y==max(Y)], by=X]$V1]}
f3 <- function(){DT[, list(Y=max(Y)), by=X]}
f4 <- function(){DT[, .SD[Y==max(Y)], by=X]}
microbenchmark(f1(), f2(), f3(), f4(), unit='relative', times=20L)
#Unit: relative
# expr        min         lq       mean     median         uq        max neval
# f1()   2.794435   2.733706   3.024097   2.756398   2.832654   6.697893    20
# f2()   4.302534   4.291715   4.535051   4.271834   4.342437   8.114811    20
# f3()   1.000000   1.000000   1.000000   1.000000   1.000000   1.000000    20
# f4() 533.119480 522.069189 504.739719 507.494095 493.641512 466.862691    20
# cld
#  a 
#  a 
#  a 
#  b

数据

N = 10000
k = 25
set.seed(25)
DT = data.table(X = rep(1:N, each = k), Y = rnorm(k*N))

@Khashaa,你能在“devel”版本上试一下吗? - akrun
1
错误已经消失,使用 devel 版本 :) - Khashaa
1
但我认为我们不应该依赖于开发版本作为一般手段。我们清楚什么定义了预期的行为吗? - A5C1D2H2I1M1N2O1R2T1
@akrun V1 不会按照原始顺序给出 TRUE/FALSE - 它会按照排序后的键的顺序给出,因为 by 部分必须对其进行排序。请参见我在问题中添加的示例,其中键未排序。 - Corvus
1
如果您使用.I,这将不会成为一个问题。 - akrun
显示剩余19条评论

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