按多列动态筛选数据表

6
假设我有一个带有几列的 data.table
a <- data.table(id=1:1000, x=runif(100), y=runif(100), z=runif(100))

我想删除那些xyz低于中位数的行:

a <- a[ x > median(x) & y > median(y) & z > median(z) ]

(旁注:上述调用median是3次还是3000次?)

我的做法是

my.cols <- c("x","y","z")
my.meds <- sapply(my.cols, function(n) median(a[[n]]))
a <- a[ Reduce(`&`,Map(function(i) a[[my.cols[i]]] > my.meds[i], 1:length(my.cols))) ]

这是我能做到的最好吗?


1
关于编程的内容:请注意,median 被调用了 3 次,您可以在运行 data.table 命令之前调用 debug(median) 进行检查。 - eddi
2
或者类似的,trace 可以避免因 debug 而停止。 - Kevin Ushey
3个回答

5

当你在遍历列时,通常长格式更容易操作。因此,为了展示这种选项,在这种情况下使用它并不是最好的选择。

以下代码不是很快,因为它在.SD上使用了[。但无论如何,这里是逻辑,等我们开始优化.SD[...]时可以使用。

> a
       id           x         y        z
   1:  60 0.006884017 0.9159115 0.876148
   2: 160 0.006884017 0.9159115 0.876148
   3: 260 0.006884017 0.9159115 0.876148
   4: 360 0.006884017 0.9159115 0.876148
   5: 460 0.006884017 0.9159115 0.876148
  ---                                   
 996: 504 0.990417986 0.7167666 0.751991
 997: 604 0.990417986 0.7167666 0.751991
 998: 704 0.990417986 0.7167666 0.751991
 999: 804 0.990417986 0.7167666 0.751991
1000: 904 0.990417986 0.7167666 0.751991

> require(reshape2)    # but data.table v1.8.11 has a fast melt built-in
> DT = as.data.table(melt(a, "id"))    # copies here => bad
> DT
       id variable       value
   1:  60        x 0.006884017
   2: 160        x 0.006884017
   3: 260        x 0.006884017
   4: 360        x 0.006884017
   5: 460        x 0.006884017
  ---                         
2996: 504        z 0.751991033
2997: 604        z 0.751991033
2998: 704        z 0.751991033
2999: 804        z 0.751991033
3000: 904        z 0.751991033

现在数据已经处于长格式中(它是否一开始就是长格式呢?),接下来的步骤会更容易:
> DT[, below:=value<median(value), by=variable]
> DT
       id variable       value below
   1:  60        x 0.006884017  TRUE
   2: 160        x 0.006884017  TRUE
   3: 260        x 0.006884017  TRUE
   4: 360        x 0.006884017  TRUE
   5: 460        x 0.006884017  TRUE
  ---                               
2996: 504        z 0.751991033 FALSE
2997: 604        z 0.751991033 FALSE
2998: 704        z 0.751991033 FALSE
2999: 804        z 0.751991033 FALSE
3000: 904        z 0.751991033 FALSE

> DT[below==TRUE, .SD[.N==3], by=id]
      id variable      value below
  1:  88        x 0.01873885  TRUE
  2:  88        y 0.05834677  TRUE
  3:  88        z 0.08973225  TRUE
  4: 188        x 0.01873885  TRUE
  5: 188        y 0.05834677  TRUE
 ---                              
356: 848        y 0.39433186  TRUE
357: 848        z 0.14152092  TRUE
358: 948        x 0.48932049  TRUE
359: 948        y 0.39433186  TRUE
360: 948        z 0.14152092  TRUE

然后,如果需要宽度,请将其转换回来。 但是我尽量保持长的东西,就像数据库一样。
可能有更直接的方法来完成上述操作,也许还有一种避免速度问题的方法,可以避免使用.SD[...]。
附注:我考虑过setkey(a,x),然后只取前一半。这对于一个列是有效的。但是第二个需要y

4

一种选项是构建您想要的字符串并将其 eval/parse

EVAL = function(...)eval(parse(text=paste0(...)))   # standard helper function

a[ EVAL(my.cols, ">median(", my.cols, ")", collapse=" & ") ]

2
我倾向于按照直接运行的方式准备语句,因此:

library(data.table)
a = data.table(id=1:1000, x=runif(100), y=runif(100), z=runif(100))
upper = c("x","y","z")
l = lapply(upper, function(col) call(">", as.name(col), call("median", as.name(col))))
ii = Reduce(function(c1, c2) substitute(.c1 & .c2, list(.c1=c1, .c2=c2)), l)
ii
#x > median(x) & y > median(y) & z > median(z)
a[eval(ii)]
#       id         x         y         z
#  1:    7 0.4750376 0.8936338 0.6158251
# ...

你需要在预处理语句的参数中使用eval

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