动态地按多列过滤data.table

时间:2014-01-09 17:29:53

标签: r data.table

假设我有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))) ]

这是我能做的最好的吗?

3 个答案:

答案 0 :(得分: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

然后dcast如果它需要宽,则返回。但我试着把事情做好,就像数据库一样。

可能有更直接的方法来实现上述目标,也许是一种避免速度.SD[...]的方法。

除此之外:我考虑过setkey(a,x)然后只是上半场。这适用于一列。但是第二个需要y<median(y),其中median(y)a的所有位置,所以你不能只在上半部分将y设置为y然后再设置一半,再为z设置一半,因为那个。但是,如果这样的事情是可能的,那对median来说就是非常专业的,这只是我假设的问题中的一个说明性例子。

答案 1 :(得分:4)

一个选项是构造所需的字符串并eval/parse

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

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

答案 2 :(得分:1)

我首选的方法是准备语句,因为我将直接运行它,所以:

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参数中的i