R:在大型数据集的矢量元素上使用 STRSPLIT 和 GREP 花费的时间太长

R: Using STRSPLIT and GREP on vector elements on large dataset takes too long

(我的第一个 StackFlow 问题)

我的目标是改进用于识别哪些 NetApp 文件共享与哪些 AD 权限分配组相关的 ETL 过程。当前名为 'TreeSize' 的应用程序扫描大量卷并输出大量大型 .CSV 文件 (35mb+)。我想合并此数据并删除每个组(或命名用户)不以大写 G 或 D('^[GD]')开头的所有权限信息。由于要处理超过 700,000 行,目前我需要 24 小时才能完成 运行。我希望有更好的方法来更有效地处理这些数据,从而大大缩短时间。

这是所有文件合并后的测试数据,与实际数据相似。使用 rownum 调整数据大小。 (真实数据700000+)

测试数据

set.seed(42)
rownum <- 2000  #Real number over 700000
i <- 1
datalist <- list()

while (i <= rownum) {
  
  randomStr1 <- paste(sample(c(0:9, letters, LETTERS[4:7], "-"),10, replace=TRUE),collapse="")
  randomStr2 <- paste(sample(c(0:9, letters, LETTERS[4:7], " "),10, replace=TRUE),collapse="")
  randomStr3 <- paste(sample(c(0:9, letters, LETTERS[4:7], " & "),10, replace=TRUE),collapse="")
  randomStr4 <- sample(c("full", "+r+w+x", "+r+x"),3)
  
  datalist$volume[i] <- rep(sample(LETTERS[1:6]))[1]
  datalist$permissions[i] <- paste(c(randomStr1,randomStr2,randomStr3),randomStr4,sep = ': ',collapse = ' | ')
  
  i = i+1
}
dat <- data.frame(datalist)
View(dat)

我创建了一个循环遍历合并数据的 WHILE 循环。我首先使用 STRSPLIT 创建一个向量,其中包含“ | 之间的每个向量元素” “每根管子。然后我在 GREP 命令中传递每个矢量元素,搜索 (‘^[GD]’) 的 RegExp。如果找到它,它会保留向量元素,如果找到多个元素,它会在分号和 space (“; “)

之间将数据合并在一起

这是我目前的做法。

  i <- 1
while (i <= length(dat$permissions)) {
  df <- strsplit(dat$permissions, " \| |: ")[[i]]              #create a vector containing each vector element      
  dat$permissions[i] <- paste(df[grep('^[GD]', df)], collapse = "; ") #Only keep where starts with G or D then Paste together
  print(paste(i, " of ", length(dat$permissions), " ", dat$permissions[i]))
i = i + 1 }
View(dat)

完成后,我导出到一个 .CSV 文件以完成转换。

处理这些数据以大幅减少处理时间的更好方法是什么?

我认为加快速度的关键是避免循环每一行,当它可以在 strsplit 和最终 paste 操作的单个矢量化操作中完成时。

paste(
  seq_along(dat$permissions), "of", nrow(dat), 
  lapply(strsplit(dat$permissions, " \| |: "), 
         \(x) paste(x[grepl("^[GD]", x)], collapse="; "))
)

应该会导致大约 250 倍的加速:

system.time({
paste(
  seq_along(dat$permissions), "of", nrow(dat), 
  lapply(strsplit(dat$permissions, " \| |: "), \(x) paste(x[grepl("^[GD]", x)], collapse="; "))
)
})
##   user  system elapsed 
##   0.03    0.00    0.03 

system.time({
  i <- 1
while (i <= length(dat$permissions)) {
  df <- strsplit(dat$permissions, " \| |: ")[[i]]              #create a vector containing each vector element      
  dat$permissions[i] <- paste(df[grep('^[GD]', df)], collapse = "; ") #Only keep where starts with G or D then Paste together
  print(paste(i, " of ", length(dat$permissions), " ", dat$permissions[i]))
i = i + 1 }
})
##   user  system elapsed 
##   8.11    0.06    8.17

加快速度的一个选择是避免拆分字符串并使用 stringr::str_extract_all():

直接提取匹配项
library(stringr)
library(purrr)

map_chr(str_extract_all(dat$permissions, "(?<=^|\| )[GD].*?(?=:)"), str_c, collapse =  "; ")

这进一步改进了 thelatemail 已经很快的替代方案:

microbenchmark::microbenchmark(
  extract = map_chr(str_extract_all(dat$permissions, "(?<=^|\| )[GD].*?(?=:)"), str_c, collapse =  "; "),
  splitmatch = sapply(strsplit(dat$permissions, " \| |: "), \(x) paste(x[grepl("^[GD]", x)], collapse = "; ")),
  check = "equal"
)

Unit: milliseconds
       expr     min       lq      mean  median       uq     max neval
    extract  5.8841  6.14375  6.681234  6.3134  6.41655 15.1264   100
 splitmatch 23.6010 24.00005 25.501808 24.2499 24.78320 40.4140   100