通过函数在每行data.table的值上过滤行

时间:2014-08-12 11:18:42

标签: r data.table

从data.frame语法切换到data.table语法对我来说仍然不顺利。我认为以下事情应该是微不足道的,但不是。我在这里做错了什么:

> DT = data.table(x=rep(c("a","b","c"),each=3), y=c(1,3,6), v=1:9)
> DT
   x y v
1: a 1 1
2: a 3 2
3: a 6 3
4: b 1 4
5: b 3 5
6: b 6 6
7: c 1 7
8: c 3 8
9: c 6 9

我想要这样的事情:

cols = c("y", "v") # a vector of column names or indexes
DT[rowSums(cols) > 5] # Take only rows where
# values at colums y and v satisfy a condition. 'rowSums' here is just an
# example it can be any function that return TRUE or FALSE when applied 
# to values of the row. 

这项工作,但如果我想提供动态列名怎么办?我的表有很多列?

>DT[eval( quote(y + v > 5))] #and the following command gives the same result
> DT[y + v > 5]
   x y v
1: a 6 3
2: b 3 5
3: b 6 6
4: c 1 7
5: c 3 8
6: c 6 9
> DT[lapply(.SD, sum) > 5, .SDcols = 2:3] # Want the same result as above
Empty data.table (0 rows) of 3 cols: x,y,v
> DT[lapply(.SD, sum) > 5, ,.SDcols = 2:3]
Empty data.table (0 rows) of 3 cols: x,y,v
> DT[lapply(.SD, sum) > 5, , .SDcols = c("y", "v")]
Empty data.table (0 rows) of 3 cols: x,y,v

在答案后更新 事实证明,有很多方法可以做这件事,我想看看哪个想法是最好的表演者。以下是模拟的时序代码:

nr = 1e7
DT = data.table(x=sample(c("a","b","c"),nr, replace= T),
                y=sample(2:5, nr, replace = T), v=sample(1:9, nr, T))
threshold = 5
cols = c("y", "v")
col.ids = 2:3
filter.methods = 'DT[DT[, rowSums(.SD[, cols, with = F]) > threshold]]
DT[DT[, rowSums(.SD[, col.ids, with = F]) > threshold]]
DT[DT[, rowSums(.SD) > threshold, .SDcols = cols]]
DT[DT[, rowSums(.SD) > threshold, .SDcols = c("y", "v")]]
DT[DT[, rowSums(.SD) > threshold, .SDcols = col.ids]]
DT[ ,.SD[rowSums(.SD[, col.ids, with = F]) > threshold]]
DT[ ,.SD[rowSums(.SD[, cols, with = F]) > threshold]]
DT[, .SD[rowSums(.SD) > threshold], .SDcols = cols, by = x]
DT[, .SD[rowSums(.SD) > threshold], .SDcols = col.ids, by = x]
DT[, .SD[rowSums(.SD) > threshold], .SDcols = c("y", "v"), by = x]
DT[Reduce(`+`,eval(cols))>threshold]
DT[Reduce(`+`, mget(cols)) > threshold]
'
fm <- strsplit(filter.methods, "\n")
fm <- unlist(fm)
timing = data.frame()
rn = NULL
for (e in sample(fm, length(fm))) { 
  # Seen some weird pattern with first item in 'fm', so scramble it
  rn <- c(rn, e)
  if (e == "DT[Reduce(`+`,eval(cols))>threshold]") {
    cols = quote(list(y, v))
  } else {
    cols = c("y", "v")
  }
  tm <- system.time(eval(parse(text = e)))
  timing <- rbind(timing, 
                  data.frame(
                    as.list(tm[c("user.self", "sys.self", "elapsed")])
                    )
                  )
}
rownames(timing) <- rn
timing[order(timing$elapsed),]

### OUTPUT ####
#                                                                     user.self sys.self elapsed
# DT[Reduce(`+`,eval(cols))>threshold]                                   0.416    0.168   0.581
# DT[Reduce(`+`, mget(cols)) > threshold]                                0.412    0.172   0.582
# DT[DT[, rowSums(.SD) > threshold, .SDcols = cols]]                     0.572    0.316   0.889
# DT[DT[, rowSums(.SD) > threshold, .SDcols = col.ids]]                  0.568    0.320   0.889
# DT[DT[, rowSums(.SD) > threshold, .SDcols = c("y", "v")]]              0.576    0.316   0.890
# DT[ ,.SD[rowSums(.SD[, col.ids, with = F]) > threshold]]               0.648    0.404   1.052
# DT[DT[, rowSums(.SD[, cols, with = F]) > threshold]]                   0.688    0.368   1.052
# DT[DT[, rowSums(.SD[, col.ids, with = F]) > threshold]]                0.612    0.440   1.053
# DT[ ,.SD[rowSums(.SD[, cols, with = F]) > threshold]]                  0.692    0.368   1.058
# DT[, .SD[rowSums(.SD) > threshold], .SDcols = c("y", "v"), by = x]     0.800    0.448   1.248
# DT[, .SD[rowSums(.SD) > threshold], .SDcols = col.ids, by = x]         0.836    0.412   1.248
# DT[, .SD[rowSums(.SD) > threshold], .SDcols = cols, by = x]            0.836    0.416   1.249

因此速度方面的冠军是:

DT[Reduce(`+`,eval(cols))>threshold]
DT[Reduce(`+`, mget(cols)) > threshold]

我更喜欢我的mget。而且我认为其他因为调用rowSums而较慢的原因,而Reduce只会有助于形成表达式。真诚地感谢所有给出答案的人。我很难决定选择接受&#39;接受&#39;回答。 Reduce - 基于sum操作非常具体,而rowSums - 基础是使用任意函数的示例。

3 个答案:

答案 0 :(得分:6)

cols = c("y", "v")

尝试

DT[DT[, rowSums(.SD[, cols, with = F]) > 5]]

或者

DT[DT[, rowSums(.SD[, 2:3, with = F]) > 5]]

或者

DT[DT[, rowSums(.SD) > 5, .SDcols = cols]]

或者

DT[DT[, rowSums(.SD) > 5, .SDcols = c("y", "v")]]

或者

DT[DT[, rowSums(.SD) > 5, .SDcols = 2:3]]

或者

DT[ ,.SD[rowSums(.SD[, 2:3, with = F]) > 5]]

或者

DT[ ,.SD[rowSums(.SD[, cols, with = F]) > 5]]

或者

DT[, .SD[rowSums(.SD) > 5], .SDcols = cols, by = x]

或者

DT[, .SD[rowSums(.SD) > 5], .SDcols = 2:3, by = x]

或者

DT[, .SD[rowSums(.SD) > 5], .SDcols = c("y", "v"), by = x]

每个都会产生

#    x y v
# 1: a 6 3
# 2: b 3 5
# 3: b 6 6
# 4: c 1 7
# 5: c 3 8
# 6: c 6 9

一些解释:

  1. .SD也是一个data.table对象,可以在DT范围内运行。从而, 此行DT[ ,rowSums(.SD[, cols, with = F]) > 5]将返回一个逻辑向量,指示DT具有y + v > 5的情况。因此,我们将添加另一个DT,以便在DT

  2. 中选择此索引
  3. 当您使用.SDcols时,它会将.SD缩小到这些列。因此,如果您只执行DT[, .SD[rowSums(.SD) > 5], .SDcols = 2:3]之类的操作,则会丢失x列,因此添加了by = x

  4. 使用.SDcols时的另一个选择是返回逻辑向量,然后将其嵌入另一个DT

答案 1 :(得分:4)

这是另一种可能性:

cols <- quote(list(y, v))
DT[Reduce(`+`,eval(cols))>5]

或者,如果您希望将cols保留为字符向量:

cols <- c('y', 'v')
DT[Reduce(`+`, mget(cols)) > 5]

答案 2 :(得分:2)

一种方法是:

cols <- quote(list(y, v))
DT[DT[,Reduce(`+`,eval(cols))>5]]
#    x y v
# 1: a 6 3
# 2: b 3 5
# 3: b 6 6
# 4: c 1 7
# 5: c 3 8
# 6: c 6 9