【问题标题】:Elegantly assigning multiple columns in data.table with lapply()使用 lapply() 优雅地分配 data.table 中的多个列
【发布时间】:2013-06-01 09:21:37
【问题描述】:

我试图找出一种优雅的方式来使用:= 赋值通过应用共享函数来一次替换data.table 中的许多列。一个典型的使用可能是将字符串函数(例如,gsub)应用于表中的所有字符列。将data.frame的做事方式扩展为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]

我不喜欢这样,因为我不喜欢在 j 表达式中引用 data.table。我也知道我可以使用:= 来分配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)]

【问题讨论】:

    标签: r data.table


    【解决方案1】:

    是的,你的问题在这里:

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

    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 是指:= 的左轴还是右轴都不容易阅读。放在一边。

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

    := 的 RHS 上的 lapply 的问题是首先评估 RHS(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 符号名称的问题你在问题中提到:

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

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

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

    但是,我担心在 j 中使用 get 会产生开销。在#1380 中进行基准测试。此外,在 RHS 上使用 get() 而在 LHS 上使用可能会造成混淆。为了解决这个问题,我们可以加糖 LHS 并允许 get() 以及 #1381

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

    另外,set 中的value 可能会在DT#1382 的范围内运行。

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

    【讨论】:

    • 非常感谢您在col 周围加上括号。直到我想起那个技巧,我才得到一个名为“col”的列。
    • 更快的替代方法是先更改相应列的名称:setnames(dt, col, "v"),然后是dt[, v := sqrt(v)],然后再改回setnames(dt, "v", col)setnames 通过引用更改名称,因此它应该可以扩展但比 get 更好。
    【解决方案2】:

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

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

    dt[, (n) := lapply(n, function(x) {sqrt(dt[[x]])})]
    

    【讨论】:

    • 最后一行需要额外的 ):dt[, (n) := lapply(n, function(x) {sqrt(dt[[x]])})]
    • 也可以添加.SDcols 选项,例如dt[, (n) := lapply(.SD, sqrt), .SDcols = n]?嗯..再想一想,也许西蒙已经做过类似的事情了。
    • lapply(n, function(x) {sqrt(dt[[x]])})中的这个n是什么意思
    【解决方案3】:

    这是你要找的吗?

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

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

    【讨论】:

    • @Frank +1 以获得该答案,我已添加书签以供将来参考。我不认为在这里使用for 循环。聪明。
    • @Frank,我不知道 for 循环 + set 方法。我将不得不考虑将来使用它。
    • @attitude_stool 由data.table 的作者阅读this comment。显然,这是.SD理想用法以及它的设计目的。
    • 您也可以将其缩短为 dt[ , 20:100 := lapply(.SD, sqrt), .SDcols = 20:100]
    猜你喜欢
    • 2016-10-09
    • 2019-07-22
    • 2014-08-18
    • 2018-07-28
    • 2020-11-21
    • 1970-01-01
    • 2012-07-25
    • 2014-07-06
    • 1970-01-01
    相关资源
    最近更新 更多