如何将相同的函数应用于data.table中的每个指定列

Dea*_*gor 70 r data.table

我有一个data.table,我希望在某些列上执行相同的操作.这些列的名称以字符向量给出.在这个特定的例子中,我想将所有这些列乘以-1.

一些玩具数据和指定相关列的向量:

library(data.table)
dt <- data.table(a = 1:3, b = 1:3, d = 1:3)
cols <- c("a", "b")
Run Code Online (Sandbox Code Playgroud)

现在我这样做,循环遍历字符向量:

for (col in 1:length(cols)) {
   dt[ , eval(parse(text = paste0(cols[col], ":=-1*", cols[col])))]
}
Run Code Online (Sandbox Code Playgroud)

有没有办法在没有for循环的情况下直接执行此操作?

Fra*_*ank 127

这似乎有效:

dt[ , (cols) := lapply(.SD, "*", -1), .SDcols = cols]
Run Code Online (Sandbox Code Playgroud)

结果是

    a  b d
1: -1 -1 1
2: -2 -2 2
3: -3 -3 3
Run Code Online (Sandbox Code Playgroud)

这里有一些技巧:

  • 因为有括号(cols) :=,结果将分配给指定的列cols,而不是名为"cols"的新变量.
  • .SDcols告诉我们我们只关注那些列,并允许我们使用与这些列关联的ata .SDSubset D.
  • lapply(.SD, ...)操作.SD,列表是列(如所有data.frames和data.tables).lapply返回一个列表,所以最后j看起来像cols := list(...).

编辑:这是另一种可能更快的方式,正如@Arun提到的:

for (j in cols) set(dt, j = j, value = -dt[[j]])
Run Code Online (Sandbox Code Playgroud)

  • 另一种方法是使用`set`和`for-loop`.我怀疑它会更快. (16认同)
  • +1很棒的答案.是的,对于像这样的情况,我更喜欢`for`循环和`set`. (7认同)
  • 是的,使用 `set()` 似乎更快,对于我的数据集来说快了大约 4 倍!惊人的。 (4认同)
  • @Arun我做了一个编辑.这是你的意思吗?我以前没用过`set`. (3认同)
  • 谢谢,@ JamesHirschorn.我不确定,但我怀疑以这种方式对列进行子集化的开销更多,而不是使用.SD,无论如何都是标准的成语,出现在介绍小插图https://github.com/Rdatatable/data.table/wiki/Getting-started我认为成语的部分原因是避免两次输入表名. (2认同)

han*_*101 13

当您想要更改列的名称时,我想添加一个答案.如果您想计算多列的对数,这非常方便,这在经验工作中通常就是这种情况.

cols <- c("a", "b")
out_cols = paste("log", cols, sep = ".")
dt[, c(out_cols) := lapply(.SD, function(x){log(x = x, base = exp(1))}), .SDcols = cols]
Run Code Online (Sandbox Code Playgroud)


Orh*_*lik 7

更新:以下是一个简洁的方法,无需循环

dt[,(cols):= - dt[,..cols]]
Run Code Online (Sandbox Code Playgroud)

这是一种简单的代码可读性方法.但就性能而言,根据以下微基准测试结果,它仍然落后于Frank的解决方案

mbm = microbenchmark(
  base = for (col in 1:length(cols)) {
    dt[ , eval(parse(text = paste0(cols[col], ":=-1*", cols[col])))]
  },
  franks_solution1 = dt[ , (cols) := lapply(.SD, "*", -1), .SDcols = cols],
  franks_solution2 =  for (j in cols) set(dt, j = j, value = -dt[[j]]),
  hannes_solution = dt[, c(out_cols) := lapply(.SD, function(x){log(x = x, base = exp(1))}), .SDcols = cols],
  orhans_solution = for (j in cols) dt[,(j):= -1 * dt[,  ..j]],
  orhans_solution2 = dt[,(cols):= - dt[,..cols]],
  times=1000
)
mbm

Unit: microseconds
expr                  min        lq      mean    median       uq       max neval
base_solution    3874.048 4184.4070 5205.8782 4452.5090 5127.586 69641.789  1000  
franks_solution1  313.846  349.1285  448.4770  379.8970  447.384  5654.149  1000    
franks_solution2 1500.306 1667.6910 2041.6134 1774.3580 1961.229  9723.070  1000    
hannes_solution   326.154  405.5385  561.8263  495.1795  576.000 12432.400  1000
orhans_solution  3747.690 4008.8175 5029.8333 4299.4840 4933.739 35025.202  1000  
orhans_solution2  752.000  831.5900 1061.6974  897.6405 1026.872  9913.018  1000
Run Code Online (Sandbox Code Playgroud)

如下图所示

performance_comparison_chart

我以前的答案:以下内容也有效

for (j in cols)
  dt[,(j):= -1 * dt[,  ..j]]
Run Code Online (Sandbox Code Playgroud)

  • Hannes 的答案是进行不同的计算,因此不应与其他人进行比较,对吗? (3认同)
  • 我添加一个旧问题的答案的原因如下:我也有一个类似的问题,我通过谷歌搜索发现了这篇文章。后来我找到了我的问题的解决方案,我发现它也适用于这里。实际上,我的建议使用了新版本的库中可用的 data.table 的新函数,该函数在提出问题时并不存在。我认为分享是个好主意,认为其他有类似问题的人最终会在谷歌搜索中出现。 (2认同)
  • 您是否使用由 3 行组成的“dt”进行基准测试? (2认同)