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


85

我有一个data.table,我想用它对某些列执行相同的操作。这些列的名称在字符向量中给出。在此特定示例中,我想将所有这些列乘以-1。

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

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

现在,我以这种方式进行操作,遍历字符向量:

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

有没有一种方法可以直接执行此操作而无需for循环?

Answers:


150

这似乎可行:

dt[ , (cols) := lapply(.SD, "*", -1), .SDcols = cols]

结果是

    a  b d
1: -1 -1 1
2: -2 -2 2
3: -3 -3 3

这里有一些技巧:

  • 由于中包含括号(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]])

21
另一种方法是使用set一个for-loop。我怀疑会更快。
阿伦(Arun)

3
@Arun,我进行了编辑。这是你的意思吗?我没有用set之前。
弗兰克

8
+1好答案。是的,我也喜欢在这种情况下使用for循环set
马特·道尔

2
是的,使用set()速度似乎更快,我的数据集的速度提高了约4倍!惊人。
Konstantinos

2
谢谢@JamesHirschorn。我不确定,但是我怀疑以这种方式设置列而不是使用.SD还是有更多的开销,无论如何,SD是出现在入门小插图github.com/Rdatatable/data.table/wiki/Getting-started中的标准习惯用法我认为这种成语的部分原因是避免两次键入表名。
弗兰克(Frank)

20

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

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]

1
有没有办法根据规则更改名称?例如,在dplyr中,您可以执行虹膜%>%mutate_at(vars(matches(“ Sepal”)),list(times_two =〜。* 2)),它将在新名称后附加“ _times_two”。
kennyB '19

1
我认为这是不可能的,但对此并不确定。
hannes101 '19

这将添加名称为的列out_cols,同时仍保留cols原样。因此,您需要通过以下两种方法之一消除它们:1)仅要求log.a和log.b:将a链接[,.(outcols)]到最后并重新存储到dtvia <-。2)用链条拆下旧柱子[,c(cols):=NULL]。非链接解决方案3)dt[,c(cols):=...]之后setnames(dt, cols, newcols)
mpag

@mpag是的,但是对于我的经验研究用例,我大多数时候都需要数据集中的两个序列。
hannes101 '19

11

更新:以下是一种无需for循环的巧妙方法

dt[,(cols):= - dt[,..cols]]

这是一种易于读取代码的简洁方法。但根据以下微基准测试结果,其性能落后于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

如下图所示

performance_comparison_chart

我以前的回答:以下内容也适用

for (j in cols)
  dt[,(j):= -1 * dt[,  ..j]]

这与一年半前弗兰克的回答本质上是一样的。
Dean MacGregor

1
谢谢,弗兰克的答案是使用集合。当我处理具有数百万行的大型data.table时,我发现:=运算符的性能优于功能
Orhan Celik

2
我为一个旧问题添加答案的原因如下:我也遇到了类似的问题,我在Google搜索中发现了此帖子。之后,我找到了解决我问题的方法,并且我认为它也适用于此。实际上,我的建议使用的是data.table的新功能,该功能在该库的新版本中可用,而在提出问题时尚不存在。我认为分享是个好主意,认为其他有类似问题的人最终会在Google搜索中找到。
Orhan Celik

1
您是否基准测试dt包含3行?
Uwe

3
汉尼斯的答案在进行不同的计算,因此不应该与其他人进行比较,对吗?
弗兰克(Frank)

2

以上解决方案似乎都不适合按组计算。以下是我得到的最好的结果:

for(col in cols)
{
    DT[, (col) := scale(.SD[[col]], center = TRUE, scale = TRUE), g]
}

1

添加示例以基于列的字符串向量创建新列。基于Jfly的答案:

dt <- data.table(a = rnorm(1:100), b = rnorm(1:100), c = rnorm(1:100), g = c(rep(1:10, 10)))

col0 <- c("a", "b", "c")
col1 <- paste0("max.", col0)  

for(i in seq_along(col0)) {
  dt[, (col1[i]) := max(get(col0[i])), g]
}

dt[,.N, c("g", col1)]

0
library(data.table)
(dt <- data.table(a = 1:3, b = 1:3, d = 1:3))

Hence:

   a b d
1: 1 1 1
2: 2 2 2
3: 3 3 3

Whereas (dt*(-1)) yields:

    a  b  d
1: -1 -1 -1
2: -2 -2 -2
3: -3 -3 -3

1
Fyi,标题中的“每个指定的列”表示询问者有兴趣将其应用于列的子集(可能不是全部)。
弗兰克(Frank)

1
@坦率的确定!在那种情况下,OP可以执行dt [,c(“ a”,“ b”)] *(-1)。
amonk

1
好吧,让我们说完整点吧dt[, cols] <- dt[, cols] * (-1)
Gregor Thomas

似乎需要的新语法是dt [,cols] <-dt [,..cols] *(-1)
Arthur Yip

0

dplyr函数在data.tables上起作用,因此这里的dplyr解决方案也“避免了for循环” :)

dt %>% mutate(across(all_of(cols), ~ -1 * .))

我是基准奥尔罕使用的代码(添加行和列),你会看到dplyr::mutateacross大多执行比大多数其他解决方案的速度越来越慢比使用lapply的data.table解决方案。

library(data.table); library(dplyr)
dt <- data.table(a = 1:100000, b = 1:100000, d = 1:100000) %>% 
  mutate(a2 = a, a3 = a, a4 = a, a5 = a, a6 = a)
cols <- c("a", "b", "a2", "a3", "a4", "a5", "a6")

dt %>% mutate(across(all_of(cols), ~ -1 * .))
#>               a       b      d      a2      a3      a4      a5      a6
#>      1:      -1      -1      1      -1      -1      -1      -1      -1
#>      2:      -2      -2      2      -2      -2      -2      -2      -2
#>      3:      -3      -3      3      -3      -3      -3      -3      -3
#>      4:      -4      -4      4      -4      -4      -4      -4      -4
#>      5:      -5      -5      5      -5      -5      -5      -5      -5
#>     ---                                                               
#>  99996:  -99996  -99996  99996  -99996  -99996  -99996  -99996  -99996
#>  99997:  -99997  -99997  99997  -99997  -99997  -99997  -99997  -99997
#>  99998:  -99998  -99998  99998  -99998  -99998  -99998  -99998  -99998
#>  99999:  -99999  -99999  99999  -99999  -99999  -99999  -99999  -99999
#> 100000: -100000 -100000 100000 -100000 -100000 -100000 -100000 -100000

library(microbenchmark)
mbm = microbenchmark(
  base_with_forloop = for (col in 1:length(cols)) {
    dt[ , eval(parse(text = paste0(cols[col], ":=-1*", cols[col])))]
  },
  franks_soln1_w_lapply = dt[ , (cols) := lapply(.SD, "*", -1), .SDcols = cols],
  franks_soln2_w_forloop =  for (j in cols) set(dt, j = j, value = -dt[[j]]),
  orhans_soln_w_forloop = for (j in cols) dt[,(j):= -1 * dt[,  ..j]],
  orhans_soln2 = dt[,(cols):= - dt[,..cols]],
  dplyr_soln = (dt %>% mutate(across(all_of(cols), ~ -1 * .))),
  times=1000
)

library(ggplot2)
ggplot(mbm) +
  geom_violin(aes(x = expr, y = time)) +
  coord_flip()

reprex软件包(v0.3.0)创建于2020-10-16

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.