35

我试图找出一种优雅的方法来使用分配通过应用共享函数来:=一次替换许多列。data.table一个典型的使用可能是将字符串函数(例如,gsub)应用于表中的所有字符列。data.frame将这样做的方式扩展到 a 并不难data.table,但我正在寻找一种与data.table做事方式一致的方法。

例如:

library(data.table)

m <- matrix(runif(10000), nrow = 100)
df <- df1 <- df2 <- df3 <- as.data.frame(m)
dt <- as.data.table(df)
head(names(df))
head(names(dt))

## replace V20-V100 with sqrt

# data.frame approach
# by column numbers
df1[20:100] <- lapply(df1[20:100], sqrt)
# by reference to column numbers
v <- 20:100
df2[v] <- lapply(df2[v], sqrt)
# by reference to column names
n <- paste0("V", 20:100)
df3[n] <- lapply(df3[n], sqrt)

# data.table approach
# by reference to column names
n <- paste0("V", 20:100)
dt[, n] <- lapply(dt[, n, with = FALSE], sqrt)

:=我知道使用分配来遍历列名向量会更有效:

for (col in paste0("V", 20:100)) dt[, col := sqrt(dt[[col]]), with = FALSE]

我不喜欢这样,因为我不喜欢data.tablej表达式中引用。我也知道我可以使用:=分配lapply给我知道列名:

dt[, c("V20", "V30", "V40", "V50", "V60") := lapply(list(V20, V30, V40, V50, V60), sqrt)]

(您可以通过构建具有未知列名的表达式来扩展它。)

以下是我尝试过的想法,但我无法让它们发挥作用。我犯了一个错误,还是我错过了另一种方法?

# possible data.table approaches?
# by reference to column names; assignment works, but not lapply
n <- paste0("V", 20:100)
dt[, n := lapply(n, sqrt), with = FALSE]
# by (smaller for example) list; lapply works, but not assignment
dt[, list(list(V20, V30, V40, V50, V60)) := lapply(list(V20, V30, V40, V50, V60), sqrt)]
# by reference to list; neither assignment nor lapply work
l <- parse(text = paste("list(", paste(paste0("V", 20:100), collapse = ", "), ")"))
dt[, eval(l) := lapply(eval(l), sqrt)]
4

3 回答 3

43

是的,你在这里的问题是正确的:

:=我知道使用分配来遍历列名向量会更有效:

for (col in paste0("V", 20:100)) dt[, col := sqrt(dt[[col]]), with = FALSE]

另外:请注意,这样做的新方法是:

for (col in paste0("V", 20:100))
  dt[ , (col) := sqrt(dt[[col]])]

因为with = FALSE无论是指 . 的 LHS 还是 RHS 都不容易阅读:=。一边结束。

如您所知,这很有效,因为它会一一处理每一列,因此一次只需要一列的工作内存。这可以在它工作和它因可怕的内存不足错误而失败之间产生差异。

lapplyon RHS of的问题是首先评估:=RHS (the );lapply即,创建了 80 列的结果。那是 80 列的新内存,必须分配和填充。因此,您需要 80 列的可用 RAM 才能使该操作成功。与随后将这 80 个新列分配( plonking)到 data.table 的列指针槽中的即时操作相比,RAM 的使用占主导地位。

正如@Frank 所指出的,如果你有很多列(比如 10,000 或更多),那么分派到[.data.table方法的小开销开始增加)。为了消除data.table::set?set描述为“可循环”的开销:=。我对这种类型的操作使用for循环。这是最快的方式,而且相当容易读写。

for (col in paste0("V", 20:100))
  set(dt, j = col, value = sqrt(dt[[col]]))

尽管只有 80 列,但这不太重要。(请注意,循环set大量行可能比大量列更常见。)但是,循环set并不能解决重复引用dt您在问题中提到的符号名称的问题:

我不喜欢这样,因为我不喜欢在 aj 表达式中引用 data.table。

同意。所以我能做的最好的就是恢复你的循环,:=get改为使用。

for (col in paste0("V", 20:100))
  dt[, (col) := sqrt(get(col))]

但是,我担心使用getin 会j带来开销。在#1380中进行基准测试。此外,在 RHS 上使用可能会令人困惑,get()但在 LHS 上则不然。为了解决这个问题,我们可以加糖 LHS 并允许get()#1381

for (col in paste0("V", 20:100))
  dt[, get(col) := sqrt(get(col))]

此外,也许valueofset可以在DT, #1382的范围内运行。

for (col in paste0("V", 20:100))
  set(dt, j = col, value = sqrt(get(col))
于 2015-10-07T19:25:52.707 回答
14

如果您想按字符串名称引用列,这些应该可以工作:

n = paste0("V", 20:100)
dt[, (n) := lapply(n, function(x) {sqrt(get(x))})]

或者

dt[, (n) := lapply(n, function(x) {sqrt(dt[[x]])})]
于 2013-06-05T16:38:48.313 回答
8

这是你想要的?

dt[ , names(dt)[20:100] :=lapply(.SD, function(x) sqrt(x) ) , .SDcols=20:100]

我听说 using.SD效率不高,因为它事先制作了表格的副本,但是如果您的表格不是很大(显然这是相对的,取决于您的系统规格),我怀疑它会产生很大的不同。

于 2013-06-05T15:49:21.517 回答