如何在data.table上运行apply?

34

我有一个data.table,其中第2到20列是带空格的字符串(例如,“物种名称”)。 我想同时在所有这些列上运行str_replace(),以便所有“ 物种名称”变为“Species_Name”。 我可以选择执行以下操作:

data.table(apply(as.data.frame(dt[,2:dim(dt)[2], with=F]), 2, 
                               function(x){ str_replace(x," ","_") }))

如果我将其保持为一种data.table对象,那么我可以逐列进行操作:

dt[,SpeciesName := str_replace(SpeciesName, " ", "_")

如何对所有第二列到最后一列进行类似上述方法的操作?

2个回答

37

在2015-11-24完全重写,以修复之前版本中的错误。

还于2019-09-27添加了更现代的选项。

你有几个选择。

  1. 使用内嵌调用lapply()处理所有目标列,使用:=在原地分配修改后的值。这依赖于:=非常方便地支持同时分配给其左侧的多个列名。

  2. 使用for循环一次运行一个目标列,使用set()逐个修改每个值。

  3. 使用for循环遍历多个“naive”调用[.data.table(),每个调用都修改一个单独的列。

这些方法看起来速度都差不多,所以你使用哪个取决于个人口味。(1)非常精简和表达。我经常使用它,不过你可能会发现(2)更容易阅读。因为它们一次只处理和修改一个列,所以在你的data.table非常大,你有可能会遇到R session可用内存限制的罕见情况下,(2)或(3)将具有优势。

library(data.table)

## Create three identical 1000000-by-20 data.tables
DT1 <- data.table(1:1e6,
           as.data.table(replicate(1e6, paste(sample(letters, nr, TRUE),
                                             sample(letters, nr, TRUE)))))
cnames <- c("ID", paste0("X", 1:19))
setnames(DT1, cnames)
DT2 <- copy(DT1); DT3 <- copy(DT1)

## Method 1
system.time({
DT1[, .SDcols=cnames[-1L], cnames[-1L] := 
  lapply(.SD, function(x) gsub(" ", "_", x, fixed=TRUE)), ]
})
##   user  system elapsed 
##  10.90    0.11   11.06 

## Method 2
system.time({
    for(cname in cnames[-1]) {
        set(DT2, j=cname, value=gsub(" ", "_", DT2[[cname]], fixed=TRUE))
    }
})
##   user  system elapsed 
##  10.65    0.05   10.70 

## Method 3
system.time({
    for(cname in cnames[-1]) {
        DT3[ , (cname) := gsub(" ", "_", get(cname), fixed=TRUE)]
    }
})
##   user  system elapsed 
##  10.33    0.03   10.37 

要了解更多关于set():=的详细信息,请阅读它们的帮助页面,可以通过键入?set?":="获取。


1
这是一个有趣的案例。在这里,20个中的19个列正在被替换;:= 的右侧几乎是整个表格。当添加一两列到20个中或修改20个中的一两列时,:= 的优势更大。在这些情况下,大多数列保持不变,:= 比复制整个表格要快得多。 - Matt Dowle
1
@MatthewDowle -- 感谢您的评论。它们提醒我,上周末我对我在这里给出的答案有些不安,促使我重新审视了这个问题。显然,我有充分的理由感到不安。请查看我的修订答案。此外,请在您认为可以帮助的地方将您在评论中提出的任何建议添加到我的答案文本中。我会研究set(),但还不太有资格讨论它。再次感谢您为data.table软件包的持续发展所做的所有工作! - Josh O'Brien
定义DT1会返回一个错误:`DT1 <- data.table(1:1e6,
  • as.data.table(replicate(1e6, paste(sample(letters, nr, TRUE),
  • sample(letters, nr, TRUE)))))
Error in sample.int(length(x), size, replace, prob) : object 'nr' not found`
- Konstantinos
建议解决方案:DT1 <- data.table(1:1e6, as.data.table(replicate(19, paste(sample(letters, 2, TRUE), sample(letters, 2, TRUE))))) - Konstantinos
1
@Hack-R nr=20 解密注释 - amonk
显示剩余9条评论

7
你可以这样做:
library("stringr")
dt[, -1] <- lapply(dt[, -1], function(x) str_replace(x," ","_"))

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接