使用`by = .I`在data.table中进行行操作

时间:2016-06-06 21:43:10

标签: r data.table

这是关于row operations in data.table

的一个很好的解释

我想到的另一种选择是为每一行使用唯一的id,然后使用by参数应用函数。像这样:

library(data.table)

dt <- data.table(V0 =LETTERS[c(1,1,2,2,3)],
                 V1=1:5,
                 V2=3:7,
                 V3=5:1)

# create a column with row positions
dt[, rowpos := .I]

# calculate standard deviation by row
dt[ ,  sdd := sd(.SD[, -1, with=FALSE]), by = rowpos ] 

问题:

  1. 有没有理由不使用这种方法?也许是其他更有效的替代方案?

  2. 为什么使用by = .I的工作方式不同?

    dt[ , sdd := sd(.SD[, -1, with=FALSE]), by = .I ]

1 个答案:

答案 0 :(得分:15)

注意:这个答案的第(3)节在2019年4月更新,因为数据有很多变化。随着时间的推移,原来的版本过时了。此外,使用从data.table的所有实例中删除的参数with=,因为它已被弃用。

1)嗯,至少在rowsums示例中,不使用它的一个原因是性能和创建不必要的列。与下面的选项f2相比,它快了近4倍,不需要行列:

dt <- data.table(V0 =LETTERS[c(1,1,2,2,3)], V1=1:5, V2=3:7, V3=5:1)
f1 <- function(dt){
  dt[, rowpos := .I] 
  dt[ ,  sdd := rowSums(.SD[, 2:4]), by = rowpos ] }
f2 <- function(dt){dt[, sdd := rowSums(dt[, 2:4])]}

library(microbenchmark)
microbenchmark(f1(dt),f2(dt))
# Unit: milliseconds
#   expr      min       lq     mean   median       uq      max neval cld
# f1(dt) 3.669049 3.732434 4.013946 3.793352 3.972714 5.834608   100   b
# f2(dt) 1.052702 1.085857 1.154132 1.105301 1.138658 2.825464   100  a 

2)关于第二个问题,虽然dt[, sdd := sum(.SD[, 2:4]), by = .I]不起作用,但dt[, sdd := sum(.SD[, 2:4]), by = 1:NROW(dt)]完美无缺。鉴于根据?data.table &#34; .I是一个等于seq_len(nrow(x))&#34; 的整数向量,人们可能期望它们是等价的。但是,差异在于.I适用于j,而不是by。 NB .I的值在data.table内部计算,因此不能预先作为参数值传递,如by=.I中所示。

也可能预期by = .I应该抛出错误。但是这不会发生,因为加载data.table包会在data.table命名空间中创建一个对象.I,该对象可以从全局环境访问,其值为NULL。您可以通过在命令提示符下键入.I来测试它。 (注意,同样适用于.SD.EACHI.N.GRP.BY

.I
# Error: object '.I' not found
library(data.table)
.I
# NULL
data.table::.I
# NULL

这样做的结果是by = .I的行为等同于by = NULL

3)虽然我们在第1部分已经看到,在rowSums的情况下,已经有效地循环行,但是比创建rowpos列要快得多。但是当我们没有快速的行方式时,循环呢?

by = rowposby = 1:NROW(dt)版本与for循环对set()进行基准测试,这里提供了丰富的信息。我们发现在set循环中循环for比使用data.table by参数进行循环的任何一种方法都要慢。但是,创建附加列的by循环与使用seq_len(NROW(dt))的循环之间的时序差异可以忽略不计。没有任何性能差异,似乎f.nrow可能更可取,但只是基于更简洁而不创建不必要的列

dt <- data.table(V0 = rep(LETTERS[c(1,1,2,2,3)], 1e3), V1=1:5, V2=3:7, V3=5:1)

f.rowpos <- function() {
  dt[, rowpos := .I] 
  dt[,  sdd := sum(.SD[, 2:4]), by = rowpos ] 
}

f.nrow <- function() {
  dt[, sdd := sum(.SD[, 2:4]), by = seq_len(NROW(dt)) ]
}

f.forset<- function() {
  for (i in seq_len(NROW(dt))) set(dt, i, 'sdd', sum(dt[i, 2:4]))
}

microbenchmark(f.rowpos(),f.nrow(), f.forset(), times = 5)
# Unit: milliseconds
#       expr       min        lq      mean    median        uq       max neval
# f.rowpos()  559.1115  575.3162  580.2853  578.6865  588.5532  599.7591     5
#   f.nrow()  558.4327  582.4434  584.6893  587.1732  588.6689  606.7282     5
# f.forset() 1172.6560 1178.8399 1298.4842 1255.4375 1292.7393 1592.7486     5

所以,总的来说,即使在没有优化功能的情况下,例如rowSums已经按行操作,也可以选择使用rowpos列,尽管不是更快,不需要创建冗余列。