我试图通过应用共享函数,找出一种使用:=
赋值在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]
我不喜欢这样,因为我不喜欢在data.table
表达式中引用j
。我也知道,如果我知道列名,我可以使用:=
分配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)]
答案 0 :(得分:35)
是的,你在这里有问题:
我知道使用
:=
循环遍历列名称向量来分配更有效:
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
无论是指{L}还是:=
的RHS,都不容易阅读。放在一边。
如您所知,这是有效的,因为每列都会逐一进行,因此一次只需要一列工作内存。这可以在它工作之间产生影响,并且由于可怕的内存不足错误而失败。
lapply
的RHS :=
的问题是首先评估RHS(lapply
);即,创建了80列的结果。这个80列的新内存值必须分配和填充。因此,您需要80列的空闲RAM才能成功完成该操作。 RAM使用占主导地位的是后续即将将这80个新列分配( plonking )到data.table的列指针槽中的操作。
正如@Frank指出的那样,如果你有很多列(比如10,000或更多),那么派遣到[.data.table
方法的小开销就会开始累加起来。为了消除data.table::set
?set
下的:=
被描述为&#34; loopable&#34; for
。我使用for (col in paste0("V", 20:100))
set(dt, j = col, value = sqrt(dt[[col]]))
循环进行此类操作。它是最快的方式,并且相当容易编写和阅读。
set
虽然只有80列,但它不太重要。 (注意,对于大量行而不是大量列循环set
可能更常见。)但是,循环dt
并不能解决重复引用的问题。您在问题中提到的:=
符号名称:
我不喜欢这样,因为我不喜欢在j表达式中引用data.table。
同意。因此,我能做的最好的事情是恢复为get
的循环,但改为使用for (col in paste0("V", 20:100))
dt[, (col) := sqrt(get(col))]
。
get
但是,我担心在j
中使用get()
会产生开销。 #1380中的基准测试。另外,在RHS上使用get()
可能会令人困惑,但在LHS上却没有。为了解决这个问题,我们可以为LHS加糖并允许for (col in paste0("V", 20:100))
dt[, get(col) := sqrt(get(col))]
,#1381:
value
此外,set
的{{1}}可能会在DT
,#1382的范围内运行。
for (col in paste0("V", 20:100))
set(dt, j = col, value = sqrt(get(col))
答案 1 :(得分:14)
如果要按字符串名称引用列,则这些应该有效:
n = paste0("V", 20:100)
dt[, (n) := lapply(n, function(x) {sqrt(get(x))})]
或
dt[, (n) := lapply(n, function(x) {sqrt(dt[[x]])})]
答案 2 :(得分:7)
这是你在找什么?
dt[ , names(dt)[20:100] :=lapply(.SD, function(x) sqrt(x) ) , .SDcols=20:100]
我听说过使用.SD
并不是那么有效,因为它预先复制了表格,但是如果你的表格不是巨大的(显然这是相对的,取决于你的系统规格)我怀疑它会产生很大的不同。