更新:
自data.table 1.4.3版本以后,已经实现了按行分组by=.I
,可以按照OP的预期工作。请注意,使用by=.I
将在数据表中创建一个名为I
的新列,其中包含行号。然后可以根据需要保留或删除行号列。
本答案的以下部分记录了适用于旧版本data.table的早期版本。如果有人仍在使用旧版本,则可以保留它作为参考。
注意: 由于data.table随时间的许多变化使原始版本过时,因此本答案第(3)部分已于2019年4月进行更新。 此外,已从所有data.table实例中删除了with=
参数的使用,因为它已被弃用。
1) 首先,至少对于rowsums
的示例而言,不使用它的一个原因是性能和创建不必要的列。与以下选项f2相比,后者几乎快4倍,而且不需要rowpos列 (请注意,原始问题使用rowSums
作为示例函数,这部分回答针对该函数。 OP之后编辑了问题,为此第3部分更为相关):
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(.SD), .SDcols= 2:4]
library(microbenchmark)
microbenchmark(f1(dt),f2(dt))
2) 对于您的第二个问题,虽然dt[, sdd := sum(.SD[, 2:4]), by = .I]
无法工作,但dt[, sdd := sum(.SD[, 2:4]), by = 1:NROW(dt)]
可以完美地工作。根据?data.table
中的说明,“.I 是一个整数向量,等于 seq_len(nrow(x))”,人们可能会认为它们是等效的。然而,区别在于.I
用于j
中,而不是用于by
中。请注意,.I
的值是在data.table内部计算的,因此无法提前作为参数值传递,例如by=.I
。
可能还期望by = .I
应该只会引发错误。但这并不会发生,因为加载data.table
包会在data.table命名空间中创建一个对象.I
,可从全局环境访问,其值为NULL
。您可以通过在命令提示符处键入.I
来测试此内容。(请注意,相同的原理适用于.SD
、.EACHI
、.N
、.GRP
和.BY
)
.I
library(data.table)
.I
data.table::.I
这意味着 by = .I
的行为等同于 by = NULL
。
3) 尽管我们已经在第一部分中看到,在rowSums
的情况下,由于其已经有效地循环遍历行,因此有比创建rowpos
列更快的方法。但是当我们没有快速的逐行函数时该怎么办?
对比使用by=rowpos
和 by=1:NROW(dt)
的版本与使用set()
的for
循环可以了解到性能差异。我们发现,在使用data.table
的by
参数进行循环的两种方法中,循环set
的for
循环速度较慢。然而,在创建附加列的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)
总之,即使在没有像rowSums
这样已经按行操作的优化函数的情况下,也有使用不需要创建多余列的替代方法,虽然速度不一定更快。
Reduce("+", dt[, 2:4, with = FALSE])
来(1)不按行循环,(2)不转换为“矩阵”。对于其他按行操作,您可以考虑类似于Reduce
操作,以避免将函数应用到每一行上,或者-也许-将数据存储为“矩阵”,并使用特定/高效的“矩阵”函数。 - alexis_lazsd
的一个选项似乎是sqrt(rowSums((dt[, 2:4, with = FALSE] - Reduce("+", dt[, 2:4, with = FALSE]) / 3) ^ 2) / (3 - 1))
。 - alexis_lazby=.I
不会出错,但它与1:nrow(dt)
不等价 - 如果我是你,我会提交一个错误报告。 - eddi