【问题标题】:What's the fastest alternative in R to do this string regex processing?R 中执行此字符串正则表达式处理的最快替代方法是什么?
【发布时间】:2020-04-07 07:21:53
【问题描述】:

我正在处理 75GB 的 XML 文件,因此不可能将它们加载到内存中并构建 DOM XML 树。因此,我求助于处理块中的行(使用readr::read_lines_chunked),例如10k 行。这是一个 N=3 行的小演示,我从中提取了构建 tibble 所需的数据,但这并不是超级快:

library(tidyverse)
xml <- c("<row Id=\"4\" Attrib1=\"1\" Attrib2=\"7\" Attrib3=\"2008-07-31T21:42:52.667\" Attrib4=\"645\" Attrib5=\"45103\" Attrib6=\"fjbnjahkcbvahjsvdghvadjhavsdjbaJKHFCBJHABCJKBASJHcvbjavbfcjkhabcjkhabsckajbnckjasbnckjbwjhfbvjahsdcvbzjhvcbwiebfewqkn\" Attrib7=\"8\" Attrib8=\"11652943\" Attrib9=\"Rich B\" Attrib10=\"2019-09-03T17:25:25.207\" Attrib11=\"2019-10-21T14:03:54.607\" Attrib12=\"1\" Attrib13=\"a|b|c|d|e|f|g\" Attrib14=\"13\" Attrib15=\"3\" Attrib16=\"49\" Attrib17=\"2012-10-31T16:42:47.213\"/>",
         "<row Id=\"5\" Attrib1=\"2\" Attrib2=\"8\" Attrib3=\"2008-07-31T21:42:52.999\" Attrib4=\"649\" Attrib5=\"7634\" Attrib6=\"fjbnjahkcbvahjsvdghvadjhavsdjbaJKHFCBJHABCJKBASJHcvbjavbfcjkhabcjkhabsckajbnckjasbnckjbwjhfbvjahsdcvbzjhvcbwiebfewqkn\" Attrib7=\"8\" Attrib8=\"11652943\" Attrib9=\"Rich B\" Attrib10=\"2019-09-03T17:25:25.207\" Attrib11=\"2019-10-21T14:03:54.607\" Attrib12=\"2\" Attrib13=\"a|b|c|d|e|f|g\" Attrib14=\"342\" Attrib15=\"43\" Attrib16=\"767\" Attrib17=\"2012-10-31T16:42:47.213\"/>",
         "<row Id=\"6\" Attrib1=\"3\" Attrib2=\"9\" Attrib3=\"2008-07-31T21:42:52.999\" Attrib4=\"348\" Attrib5=\"2732\" Attrib6=\"djhfbsdjhfbijhsdbfjkdbnfkjndaskjfnskjdlnfkjlsdnf\" Attrib7=\"9\" Attrib8=\"34873\" Attrib9=\"FHDHsf\" Attrib10=\"2019-09-03T17:25:25.207\" Attrib11=\"2019-10-21T14:03:54.607\" Attrib12=\"3\" Attrib13=\"a|b|c|d|e|f|g\" Attrib14=\"342\" Attrib15=\"43\" Attrib16=\"767\" Attrib17=\"2012-10-31T16:42:47.4333\"/>")
pattern <- paste(".*(Id=\"\\d+\") ",
                 "(Attrib1=\"\\d+\") ",
                 "(Attrib2=\"\\d+\") ",
                 "(Attrib3=\"[0-9]+-[0-9]+-[0-9]+T[0-9]+:[0-9]+:[0-9]+[0-9]+.[0-9]+\") ",
                 "(Attrib4=\"\\d+\") ",
                 "(Attrib5=\"\\d+\")",
                 ".*(Attrib8=\"\\d+\") ",
                 ".*(Attrib10=\"[0-9]+-[0-9]+-[0-9]+T[0-9]+:[0-9]+:[0-9]+[0-9]+.[0-9]+\") ",
                 "(Attrib11=\"[0-9]+-[0-9]+-[0-9]+T[0-9]+:[0-9]+:[0-9]+[0-9]+.[0-9]+\")",
                 ".*(Attrib13=\"([a-z]|[0-9]|\\||\\s)+\") ",
                 "(Attrib14=\"\\d+\") ",
                 "(Attrib15=\"\\d+\") ",
                 "(Attrib16=\"\\d+\")",
                 sep="")
# match the groups in pattern and extract the matches
tmp <- str_match(xml, pattern)[,-c(1,12)]
# remove non matching NA rows  
r <- which(is.na(tmp[,1]))
if (length(r) > 0) {
  tmp <- tmp[-r,]
}
# remove the metadata and stay with the data within the double quotes only
tmp <- apply(tmp, 1, function(s) {
  str_remove_all(str_match(s, "(\".*\")")[,-1], "\"")
})
# need the transposed version of tmp
tmp <- t(tmp)
tmp
# convert to a tibble
colnames(tmp) <- c("Id", "Attrib1", "Attrib2", "Attrib3", "Attrib4", "Attrib5", "Attrib8", "Attrib10", "Attrib11", "Attrib13", "Attrib14", "Attrib15", "Attrib16")
as_tibble(tmp)

在性能方面有更好的方法吗?

更新:我在 10k 行(而不是 3 行)上对上面的代码进行了基准测试,它是 900 秒。然后,我将属性正则表达式组的数量从 13 个减少到 7 个(仅至关重要的组),并且相同的基准测试下降到 128 秒。

外推到 9731474 行,我从约 10 天缩短到约 35 小时。然后我使用 Linux 命令split -l1621913 -d Huge.xml Huge_split_ --verbose 将大文件拆分为 6 个文件,以匹配我拥有的内核数量,现在在每个拆分文件上并行运行代码......所以我正在查看 35/6=~5.8小时......这还不错。我愿意:

library(doMC)
registerDoMC(6)
resultList <- foreach (i=0:5) %dopar% {
  file <- sprintf('Huge_split_0%d', i)  
  partial <- # run the chunk algorithm on file
  return(partial)
}

【问题讨论】:

  • 有什么特别的原因你必须用正则表达式解决这个问题吗?这似乎用xml2 解析得相对较好,如果您有per-column 后XML 正则表达式调整,那么它们可以更有效地完成。
  • @r2evans XML 文件是 75GB,这就是原因。我无法在内存中加载这样的文件。我提供的是一个小的 3 行数据样本,这样人们就可以看到我现在在做什么......还有另外 9'731'474 行这样的数据。
  • 我加载了这三个字符串,用&lt;xml&gt; 和&lt;/xml&gt; 包装它们(也许是草率),xml2::read_xml 读得很好。如果您需要实际的父节点命名法,您始终可以在第一次读取时保留它,然后在窗口/滚动其他节点时重新使用它。

标签: r xml parsing bigdata tidyverse


【解决方案1】:

使用xml2,我能够显着缩短处理时间,尤其是在规模更大的情况下。由于我对xml2 并不完全精通,因此可能还有另一种更好的方法。

library(stringr)
func_regex <- function(xmlvec) {
  tmp <- str_match(xmlvec, pattern)[,-c(1,12)]
  # remove non matching NA rows  
  r <- which(is.na(tmp[,1]))
  if (length(r) > 0) {
    tmp <- tmp[-r,]
  }
  # remove the metadata and stay with the data within the double quotes only
  tmp <- apply(tmp, 1, function(s) {
    str_remove_all(str_match(s, "(\".*\")")[,-1], "\"")
  })
  # need the transposed version of tmp
  tmp <- as.data.frame(t(tmp))
  colnames(tmp) <- c("Id", "Attrib1", "Attrib2", "Attrib3", "Attrib4", "Attrib5", "Attrib8", "Attrib10", "Attrib11", "Attrib13", "Attrib14", "Attrib15", "Attrib16")
  tmp
}

library(xml2)
func_xml2 <- function(xmlvec) {
  as.data.frame(do.call(
    rbind,
    lapply(xml_children(read_xml(paste("<xml>", paste(xmlvec, collapse=""), "</xml>"))),
           function(x) xml_attrs(x))
  ))
}

(编辑:我意识到我正在从func_regex 使用pattern,这是一个草率的违反范围。也许我会修复它并更新基准测试,我不'不认为它会提高xml2的相对速度提升。)

足够相似的输出:

str(func_regex(xml))
# 'data.frame': 3 obs. of  13 variables:
#  $ Id      : Factor w/ 3 levels "4","5","6": 1 2 3
#  $ Attrib1 : Factor w/ 3 levels "1","2","3": 1 2 3
#  $ Attrib2 : Factor w/ 3 levels "7","8","9": 1 2 3
#  $ Attrib3 : Factor w/ 2 levels "2008-07-31T21:42:52.667",..: 1 2 2
#  $ Attrib4 : Factor w/ 3 levels "348","645","649": 2 3 1
#  $ Attrib5 : Factor w/ 3 levels "2732","45103",..: 2 3 1
#  $ Attrib8 : Factor w/ 2 levels "11652943","34873": 1 1 2
#  $ Attrib10: Factor w/ 1 level "2019-09-03T17:25:25.207": 1 1 1
#  $ Attrib11: Factor w/ 1 level "2019-10-21T14:03:54.607": 1 1 1
#  $ Attrib13: Factor w/ 1 level "a|b|c|d|e|f|g": 1 1 1
#  $ Attrib14: Factor w/ 2 levels "13","342": 1 2 2
#  $ Attrib15: Factor w/ 2 levels "3","43": 1 2 2
#  $ Attrib16: Factor w/ 2 levels "49","767": 1 2 2

str(func_xml2(xml))
# 'data.frame': 3 obs. of  18 variables:
#  $ Id      : Factor w/ 3 levels "4","5","6": 1 2 3
#  $ Attrib1 : Factor w/ 3 levels "1","2","3": 1 2 3
#  $ Attrib2 : Factor w/ 3 levels "7","8","9": 1 2 3
#  $ Attrib3 : Factor w/ 2 levels "2008-07-31T21:42:52.667",..: 1 2 2
#  $ Attrib4 : Factor w/ 3 levels "348","645","649": 2 3 1
#  $ Attrib5 : Factor w/ 3 levels "2732","45103",..: 2 3 1
#  $ Attrib6 : Factor w/ 2 levels "djhfbsdjhfbijhsdbfjkdbnfkjndaskjfnskjdlnfkjlsdnf",..: 2 2 1
#  $ Attrib7 : Factor w/ 2 levels "8","9": 1 1 2
#  $ Attrib8 : Factor w/ 2 levels "11652943","34873": 1 1 2
#  $ Attrib9 : Factor w/ 2 levels "FHDHsf","Rich B": 2 2 1
#  $ Attrib10: Factor w/ 1 level "2019-09-03T17:25:25.207": 1 1 1
#  $ Attrib11: Factor w/ 1 level "2019-10-21T14:03:54.607": 1 1 1
#  $ Attrib12: Factor w/ 3 levels "1","2","3": 1 2 3
#  $ Attrib13: Factor w/ 1 level "a|b|c|d|e|f|g": 1 1 1
#  $ Attrib14: Factor w/ 2 levels "13","342": 1 2 2
#  $ Attrib15: Factor w/ 2 levels "3","43": 1 2 2
#  $ Attrib16: Factor w/ 2 levels "49","767": 1 2 2
#  $ Attrib17: Factor w/ 2 levels "2012-10-31T16:42:47.213",..: 1 1 2

基准测试:

microbenchmark::microbenchmark(
  func_regex(xml),
  func_xml2(xml),
  times = 10
)
# Unit: milliseconds
#             expr    min     lq    mean  median     uq    max neval
#  func_regex(xml) 1.4306 1.4728 1.57756 1.48660 1.5875 2.2086    10
#   func_xml2(xml) 1.0714 1.1075 1.18385 1.15275 1.1875 1.5418    10

xml1000 <- rep(xml, 1000)
microbenchmark::microbenchmark(
  func_regex(xml1000),
  func_xml2(xml1000),
  times = 10
)
# Unit: milliseconds
#                 expr      min       lq     mean   median       uq      max neval
#  func_regex(xml1000) 458.4921 531.1159 570.1703 534.8204 538.6754 782.6863    10
#   func_xml2(xml1000) 107.1230 107.7632 110.7316 109.1315 111.1904 121.8560    10

xml100000 <- rep(xml, 100000)
microbenchmark::microbenchmark(
  func_regex(xml100000),
  func_xml2(xml100000),
  times = 10
)
# Unit: seconds
#                   expr      min       lq     mean   median       uq      max neval
#  func_regex(xml100000) 52.89568 53.97438 55.64431 54.67441 56.95971 61.86949    10
#   func_xml2(xml100000) 13.77857 16.02327 16.50498 16.58733 17.38458 17.81042    10

【讨论】:

  • 非常感谢您的出色回答。在我的 PC 中,您的解决方案的速度要快几个数量级!
猜你喜欢
  • 2012-09-13
  • 1970-01-01
  • 2020-01-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-11-02
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多