【问题标题】:R - Is it possible to optimize or streamline multiple calls to grepl()?R - 是否可以优化或简化对 grepl() 的多次调用?
【发布时间】:2018-01-10 16:48:08
【问题描述】:

使用NOAA Severe Weather data,其中包括一个描述天气事件类型的变量EVTYPE(事件类型)。这些值包括许多同义词,我想在几个更广泛的名称下收集这些同义词。例如有TORNADO,但也有ROTATING WALL CLOUDFUNNEL CLOUDWHIRLWIND,它们在某种意义上描述了相对类似的事件。在不涉及气象学的微妙之处的情况下,我想将这些几乎同义的值组合在一个值名称下。

假设我已将数据集加载到数据框 noaa_clean 中,然后应用它:

tornado <- sapply(as.character(noaa_clean$EVTYPE), 
                   function(x){grepl("^.*TORNAD.*$", x) |
                               grepl("^.*SPOUT.*$", x) |
                               grepl("^.*WHIRL.*$", x) |
                               grepl("^.*FUNNEL.*$", x) |
                               grepl("^.*ROTATING WALL CLOUD.*$", x) |
                               grepl("^.*DUST DEVIL.*$", x)})
noaa_clean[tornado, "EVCAT"] <- "TORNADO"; rm(tornado)

它运行良好,但我有几个,需要一些时间(~5-10 分钟)才能运行它们。我的问题是这样的:有没有更好的方法来利用 grepl() 或正则表达式来提高效率?

【问题讨论】:

  • | 运算符即使在正则表达式中也可以工作。您可以使用grepl("^.*TORNAD|SPOUT|WHIRL|(...).*$", x) 调用grepl 一次(其中... 表示其他可能性)。
  • 如果您切换到stringi::stri_detect_regex 而不是grepl,您可能还会获得加速。 (并结合模式并丢失 sapply,如 MrFlick 的回答。)
  • 您可能还想使用单词边界\bTORNAD\b

标签: r regex grepl


【解决方案1】:

由于您特别询问了速度,因此对 cme​​ts 中发布的各种解决方案或作为答案的测试是:

#Initialize vector
x <- sample(c("TORNA", "SPOUT", "WHIRL", "FUNNEL", "ROTATING WALL CLOUD", "DUST DEVIL",
                LETTERS[1:8]), 1e6, replace = TRUE)

#Using separate grepl's
multi_grepl <- function(x) {grepl("TORNAD", x) |grepl("SPOUT", x) |grepl("WHIRL", x) |grepl("FUNNEL", x) | grepl("ROTATING WALL CLOUD", x) |grepl("DUST DEVIL", x)}

#One grepl
one_grepl <- function(x) grepl("TORNAD|SPOUT|WHIRL|FUNNEL|ROTATING WALL CLOUD|DUST DEVIL", x)

#Using stri_detect_regex
detect_regex <- function(x) stringi::stri_detect_regex(x, "TORNAD|SPOUT|WHIRL|FUNNEL|ROTATING WALL CLOUD|DUST DEVIL")

#Original solution with sapply
orig_sapply <- function(x) sapply(x, function(y){grepl("^.*TORNAD.*$", y) |grepl("^.*SPOUT.*$", y) |grepl("^.*WHIRL.*$", y) |grepl("^.*FUNNEL.*$", y) |grepl("^.*ROTATING WALL CLOUD.*$", y) |grepl("^.*DUST DEVIL.*$", y)})

#Using stri_detect_fixed
stri_fixed = function(x) { stri_detect_fixed(x, pattern = "TORNAD") | stri_detect_fixed(x, pattern = "SPOUT") | stri_detect_fixed(x, pattern = "WHIRL") | stri_detect_fixed(x, pattern = "FUNNEL") | stri_detect_fixed(x, pattern = "ROTATING WALL CLOUD") | stri_detect_fixed(x, pattern = "DUST DEVIL") }


#Checking that all these give same answer
identical(multi_grepl(x), one_grepl(x), detect_regex(x), orig_sapply(x), stri_fixed(x))
#[1] TRUE

microbenchmark::microbenchmark(multi_grepl(x),
                               one_grepl(x),
                               detect_regex(x),
                               orig_sapply(x),
                               stri_fixed(x), times = 20L)

#Unit: milliseconds
#            expr        min         lq       mean     median         uq        max neval
#  multi_grepl(x)   724.6716   738.5227   754.2347   747.1441   769.2897   819.9971    20
#    one_grepl(x)   406.7987   410.3197   420.0083   412.1168   426.5932   453.2471    20
# detect_regex(x)   167.4844   170.0834   174.1256   172.7410   177.1546   187.3211    20
#  orig_sapply(x) 47172.3407 47379.8250 47666.7177 47546.2221 47875.9352 48517.2228    20
#   stri_fixed(x)   261.4303   265.9189   270.5816   268.6038   273.2486   288.7071    20

看来stri_detect_regex 是最快的。有趣的是,这与我在regex 中有^.*.*$ 时尝试的最后一次迭代不同。感谢@Gregor 指出这一点。请注意,您原来的 sapply 非常慢,因为它多次执行 grepl 搜索(每个元素一次)。而不是整个向量只有一次。


最后,更长的单个字符串的结果:

prefixes <- replicate(1e6, paste0(sample(LETTERS, sample(100:200), replace = TRUE), collapse = ""))
suffixes <- replicate(1e6, paste0(sample(LETTERS, sample(200:300), replace = TRUE), collapse = ""))
x_long <- paste0(prefixes, x, suffixes)

microbenchmark::microbenchmark(multi_grepl(x_long),
                               one_grepl(x_long),
                               detect_regex(x_long),
                               stri_fixed(x_long), times = 20L)

#Unit: seconds
#                 expr       min        lq      mean    median        uq       max neval
#  multi_grepl(x_long) 27.654274 27.721042 28.194273 27.962656 28.626697 29.909105    20
#    one_grepl(x_long) 11.478831 11.510868 11.775088 11.583650 11.663479 14.318680    20
# detect_regex(x_long)  8.673534  8.729508  8.808797  8.774432  8.878907  9.028005    20
#   stri_fixed(x_long)  4.502196  4.540850  4.609050  4.591879  4.690035  4.750445    20

【讨论】:

  • ^.*.*$ 的使用是不必要的,实际上会适得其反。我认为没有它你会看到更快的时间。
  • 我想我找到了一个更快的解决方案:stringi::stri_detect_fixed。无法组合模式(因为它不是正则表达式),但字节比较足够快,几乎是 one_grepl 解决方案的两倍。 stri_fixed = function(x) { stri_detect_fixed(x, pattern = "TORNAD") | stri_detect_fixed(x, pattern = "SPOUT") | stri_detect_fixed(x, pattern = "WHIRL") | stri_detect_fixed(x, pattern = "FUNNEL") | stri_detect_fixed(x, pattern = "ROTATING WALL CLOUD") | stri_detect_fixed(x, pattern = "DUST DEVIL") }
  • 如果你有雄心壮志,我​​也很好奇如果单个字符串更长,排名会如何比较。我对您的初始排名感到惊讶,因为我在最近的一个项目中发现 stringi 更快,其中每个字符串都是电子邮件的前 200 个单词。
  • 另外,有趣的是,当我按照 MrFlick 的建议删除 ^.*.*$ 时,排名完全改变,detect_regex 在顶部,我的 stri_fixed 方法在第二个,one_grepl在遥远的第三。
  • 我很高兴我发布了这个问题;这里有很多有用的见解。我sapply 上的时间反映了运行时的感受。
【解决方案2】:

正则表达式本身可以使用| 作为OR 匹配。你可以这样做

tornado  <- grepl("(TORNAD|SPOUT|WHIRL|FUNNEL|ROTATING WALL CLOUD|DUST DEVIL)", as.character(noaa_clean$EVTYPE))

另外请注意,我们不需要使用 sapply(),因为 grepl 已经是 R 中的矢量化函数。

【讨论】:

  • grepl 是否需要 ^.*.*$ 来查找字符串中的任何位置?
  • 根据您的偏好(例如从文件中加载字符串列表),您可以使用paste0 从单词向量中创建搜索字符串。 grepl(paste0(Words,collapse = '|'),noaa_clean$EVTYPE)
  • @ConnerM。它们不是必需的;这些类型的正则表达式已经在字符串中的任何地方查找。
猜你喜欢
  • 2021-11-20
  • 1970-01-01
  • 1970-01-01
  • 2014-10-20
  • 1970-01-01
  • 2023-03-27
  • 1970-01-01
  • 1970-01-01
  • 2019-09-12
相关资源
最近更新 更多