【问题标题】:Delete characters before regular expression (R)删除正则表达式前的字符 (R)
【发布时间】:2016-07-27 21:14:07
【问题描述】:

我有一个股票代码的字符向量,其中代码名称以下列形式连接到该代码所在的国家/地区:country_name/ticker_name。我正在尝试拆分每个字符串并从“/”后面删除所有内容,返回仅包含股票名称的字符向量。这是一个示例向量:

sample_string <- c('US/SPY', 'US/AOL', 'US/MTC', 'US/PHA', 'US/PZI',
                   'US/AOL', 'US/BRCM')

我最初的想法是使用 stringr 库。我对那个包没有任何经验,但这是我正在尝试的:

library(stringr)
split_string <- str_split(sample_string, '/')

但我不确定如何仅将每个列表的第二个元素作为单个向量返回。

如何在大型字符向量(约 1.05 亿个条目)上执行此操作?

【问题讨论】:

  • 有很多方法可以实现这一点。例如sub(".*/(.*)", "\\1", sample_string)sub(".*/", "", sample_string)等。或者这可能会更快,因为避免使用正则表达式data.table::tstrsplit(sample_string, "/", fixed = TRUE)[[2]]
  • '.*/(.*)' 在这里充当指针吗?
  • 它的意思是“匹配反斜杠之前的所有内容(包括)并捕获它之后的所有内容”。然后\\1 告诉sub 返回捕获的组。虽然我认为在这种情况下它过于复杂。其他两个选项可能更好/更简单。不管怎样,这种类型的问题在 SO 上被问了很多次,如果你想在 R 中看到更多的正则表达式示例,你真的应该尝试谷歌。我推荐这个网站用于未来的正则表达式测试regex101.com 和这个用于教程@ 987654322@
  • 明白。感谢您抽出宝贵时间。

标签: r split stringr


【解决方案1】:

这里的一些基准测试包括@David Arenburg 建议的所有方法,以及使用stringr 包中的str_extract 的另一种方法。

sample_string <- rep(sample_string, 1000000)

library(data.table); library(stringr)
s1 <- function() sub(".*/(.*)", "\\1", sample_string)
s2 <- function() sub(".*/", "", sample_string)
s3 <- function() str_extract(sample_string, "(?<=/)(.*)")
s4 <- function() tstrsplit(sample_string, "/", fixed = TRUE)[[2]]

length(sample_string)
# [1] 7000000

identical(s1(), s2())
# [1] TRUE
identical(s1(), s3())
# [1] TRUE
identical(s1(), s4())
# [1] TRUE

microbenchmark::microbenchmark(s1(), s2(), s3(), s4(), times = 5)
# Unit: seconds
#  expr      min       lq     mean   median       uq      max neval
#  s1() 3.916555 3.917370 4.046708 3.923246 3.925184 4.551184     5
#  s2() 3.584694 3.593755 3.726922 3.610284 3.646449 4.199426     5
#  s3() 3.051398 3.062237 3.354410 3.138080 3.722347 3.797985     5
#  s4() 1.908283 1.964223 2.349522 2.117521 2.760612 2.996971     5

tstrsplit 方法最快。

更新

再添加一个@Frank的方法,这个比较不是很准确,要看实际数据,如果上面产生的sample_string有很多重复的情况,优势就很明显了:

s5 <- function() setDT(list(sample_string))[, v := tstrsplit(V1, "/", fixed = TRUE)[[2]], by=V1]$v

identical(s1(), s5())
# [1] TRUE

microbenchmark::microbenchmark(s1(), s2(), s3(), s4(), s5(), times = 5)
# Unit: milliseconds
#  expr        min       lq      mean    median        uq       max neval
#  s1() 3905.97703 3913.264 3922.8540 3913.4035 3932.2680 3949.3575     5
#  s2() 3568.63504 3576.755 3713.7230 3660.5570 3740.8252 4021.8426     5
#  s3() 3029.66877 3032.898 3061.0584 3052.6937 3086.9714 3103.0604     5
#  s4() 1322.42430 1679.475 1985.5440 1801.9054 1857.8056 3266.1101     5
#  s5()   82.71379  101.899  177.8306  121.6682  209.0579  373.8141     5

【讨论】:

  • 示例的长度很好,但为什么不直接显示制作它的代码呢?
  • @Frank 我将 OP 中的 sample_string 重复了 100 万次。添加到答案中。
  • 好的。滥用大量重复值...s5 &lt;- function() setDT(list(sample_string))[, v := tstrsplit(V1, "/", fixed = TRUE)[[2]], by=V1]$v 可能也与 OP 有关,因为它们有两次 AOL。它的测试与 s4 相同,而且速度快 10 倍。
  • 它做的事情类似于s6 &lt;- function() ave(sample_string, sample_string, FUN = function(s) sub(".*/(.*)", "\\1", s[1L])),所以是的,它只需要将每个不同的字符串值拆分一次(而不是在整个向量中每次出现一次)。
  • @Frank 如果是这样的话,这对 OP 的情况来说非常有意义。他不太可能拥有 100+ 百万个不同的字符串,例如 US/...
【解决方案2】:

关于您的问题的一些有用说明:首先,stringrpackage 中有一个 str_split_fixed 函数,它通过调用 lapply 来执行您希望它执行的操作。

library(data.table); library(stringr)
sample_string <- c('US/SPY', 'US/AOL', 'US/MTC', 'US/PHA', 'US/PZI',
                   'US/AOL', 'US/BRCM')
sample_string <- rep(sample_string, 1e5)
split_string <- str_split_fixed(sample_string, '/', 2)[,2]

它通过调用stringi::stri_split_fixed 工作,与

do.call("c", lapply(str_split(sample_string, '/'),"[[",2))

其次,另一种考虑提取列表中每个第二个元素的方法是完全按照 tstrsplit 的内部操作。

transpose(strsplit(sample_string, "/", fixed = T))[[2]]

总的来说,上面的操作应该比调用tstrsplit 稍微快一些。当然,这可能不值得详细输入,但它有助于了解该函数的作用。

library(data.table); library(stringr)
s4 <- function() tstrsplit(sample_string, "/", fixed = TRUE)[[2]]
s5 <- function() transpose(strsplit(sample_string, "/", fixed = T))[[2]]

identical(s4(), s5())
microbenchmark::microbenchmark(s4(), s5(), times = 20)

microbenchmark::microbenchmark(s4(), s5(), times = 20)
Unit: milliseconds
 expr      min       lq     mean   median       uq      max neval
 s4() 161.0744 193.3611 255.8136 234.9945 271.6811 434.7992    20
 s5() 140.8569 176.5600 233.3570 194.1676 251.7921 420.3431    20

关于第二种方法,简而言之,转置这个长度为 700 万的列表,每个包含 2 个元素,会将您的结果转换为长度为 2 的列表,每个包含 700 万个元素。然后,您将提取此列表的第二个元素。

【讨论】:

  • 注意:transpose也是data.table包中的一个函数,所以还不如直接tstrsplit吧?此外,转置本身似乎是用 C 编写的。
  • @Frank,是的,你是对的。我编辑了我的答案以反映这一点。那么差异归因于什么?函数开销?或者 R 的 C 函数是否可能比 data.table 的 C 函数在转置方面更快。我想我以后需要做一些探索。
  • 嗯,差别不大;我不会担心的。如您所料,可能是开销。我只是使用包装器(tstrsplit),而不是钻研它的内部几毫秒。同样,我不会摆弄来自基础的.subset2.Internal(mean(x)),即使它们提供了更大的性能改进@987654321 @和stackoverflow.com/questions/14209427/…
  • 绝对。我通常只是跑 lapply 去吃点零食。
  • 如何使用do.call("c", lapply(str_split(sample_string, '/'),"[[",2)) 获取他已经共享的代码的第二个元素是他第二个问题的合适答案。我还分享了该软件包中的另一个功能,该功能可以满足他的需求,并解释了第三个答案背后的基本原理。我不明白为什么这会冒犯任何人。
猜你喜欢
  • 1970-01-01
  • 2023-04-02
  • 1970-01-01
  • 2016-05-26
  • 1970-01-01
  • 2014-11-07
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多