使用lapply()优雅地在data.table中分配多个列

att*_*ool 30 r data.table

我试图通过应用共享函数找出一种优雅的方法来使用:=赋值来一次替换多列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)
Run Code Online (Sandbox Code Playgroud)

我知道使用:=to assign 循环遍历列名的向量更有效:

for (col in paste0("V", 20:100)) dt[, col := sqrt(dt[[col]]), with = FALSE]
Run Code Online (Sandbox Code Playgroud)

我不喜欢这个,因为我不喜欢data.tablej表达式中引用它.我也知道我可以使用:=赋值lapply我知道列名:

dt[, c("V20", "V30", "V40", "V50", "V60") := lapply(list(V20, V30, V40, V50, V60), sqrt)]
Run Code Online (Sandbox Code Playgroud)

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

以下是我在此尝试的想法,但我无法让他们工作.我犯了错误,还是有其他方法我错过了?

# 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)]
Run Code Online (Sandbox Code Playgroud)

Mat*_*wle 37

是的,你在这里是对的:

我知道使用:=to assign 循环遍历列名的向量更有效:

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]])]
Run Code Online (Sandbox Code Playgroud)

因为with = FALSE它不易被读取,无论它是指LHS还是RHS :=.放在一边.

如您所知,这是有效的,因为每列都会逐一进行,因此一次只需要一列工作内存.这可以在它工作之间产生差异,并且由于可怕的内存不足错误而失败.

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

正如@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]]))
Run Code Online (Sandbox Code Playgroud)

虽然只有80列,但它不太重要.(注意,循环set遍历大量行而不是大量列可能更常见.)但是,循环set不能解决重复引用dt您在问题中提到的符号名称的问题:

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

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

for (col in paste0("V", 20:100))
  dt[, (col) := sqrt(get(col))]
Run Code Online (Sandbox Code Playgroud)

但是,我担心使用getin j可能效率低下.需要进行基准测试,#1380.此外,也许get()在RHS上使用而不是在LHS上使用会令人困惑.为了解决这个问题,我们可以为LHS加糖并允许get(),#1381:

for (col in paste0("V", 20:100))
  dt[, get(col) := sqrt(get(col))]
Run Code Online (Sandbox Code Playgroud)

此外,或许valueset可能的范围内运行DT,#1382.

for (col in paste0("V", 20:100))
  set(dt, j = col, value = sqrt(get(col))
Run Code Online (Sandbox Code Playgroud)


edd*_*ddi 14

如果要按字符串名称引用列,这些应该有效:

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

要么

dt[, (n) := lapply(n, function(x) {sqrt(dt[[x]])})]
Run Code Online (Sandbox Code Playgroud)

  • 在最后一行需要一个额外的):`dt [,(n):= lapply(n,function(x){sqrt(dt [[x]])})]` (3认同)

Sim*_*lon 7

这是你想要的?

dt[ , names(dt)[20:100] :=lapply(.SD, function(x) sqrt(x) ) , .SDcols=20:100]
Run Code Online (Sandbox Code Playgroud)

我听说告诉大家,使用.SD不是很有效率,因为它使表的副本事前,但如果你的表是不是巨大的,我怀疑它将使太大的差别(当然这取决于你的系统规格这是相对的).

  • 我被告知[`set`也可以加速这样的操作](http://stackoverflow.com/questions/16846380/how-to-apply-same-function-to-every-specified-column-in -a-数据表/ 16846530#16846530). (4认同)