base R gsub 和 stringr::str_replace_all 的不同行为?

Different behavior of base R gsub and stringr::str_replace_all?

我希望 gsubstringr::str_replace_all 到 return 的结果相同,但只有 gsub return 是预期的结果。我正在开发一节课来演示 str_replace_all,所以我想知道为什么这里 return 的结果不同。

txt <- ".72   2.51\n2015**   2.45   2.30   2.00   1.44   1.20   1.54   1.84   1.56   1.94   1.47   0.86   1.01\n2016**   1.53   1.75   2.40   2.62   2.35   2.03   1.25   0.52   0.45   0.56   1.88   1.17\n2017**   0.77   0.70   0.74   1.12   0.88   0.79   0.10   0.09   0.32   0.05   0.15   0.50\n2018**   0.70   0"

gsub(".*2017|2018.*", "", txt)

stringr::str_replace_all(txt, ".*2017|2018.*", "")

gsub returns 预期输出(2017 之前和包括 2017 以及 2018 之后和包括 2018 的所有内容均已删除。

gsub 的输出(预期)

[1] "**   0.77   0.70   0.74   1.12   0.88   0.79   0.10   0.09   0.32   0.05   0.15   0.50\n"

然而,str_replace_all 仅替换了 20172018,但保留了其余部分,即使两者使用相同的 pattern

str_replace_all 的输出(无意)

[1] ".72   2.51\n2015**   2.45   2.30   2.00   1.44   1.20   1.54   1.84   1.56   1.94   1.47   0.86   1.01\n2016**   1.53   1.75   2.40   2.62   2.35   2.03   1.25   0.52   0.45   0.56   1.88   1.17\n**   0.77   0.70   0.74   1.12   0.88   0.79   0.10   0.09   0.32   0.05   0.15   0.50\n"

为什么会这样?

Base R 依赖于两个正则表达式库。 默认 R 使用 TRE。 我们可以指定 perl = TRUE 来使用 PCRE(类似 perl 的正则表达式)。 {stringr} 包使用 ICU(Java 像正则表达式)。

在您的情况下,问题是点 . 与 PCRE 和 ICU 中的换行符不匹配,而它与 TRE 中的换行符匹配:

library(stringr)

txt <- ".72   2.51\n2015**   2.45   2.30   2.00   1.44   1.20   1.54   1.84   1.56   1.94   1.47   0.86   1.01\n2016**   1.53   1.75   2.40   2.62   2.35   2.03   1.25   0.52   0.45   0.56   1.88   1.17\n2017**   0.77   0.70   0.74   1.12   0.88   0.79   0.10   0.09   0.32   0.05   0.15   0.50\n2018**   0.70   0"

(base_tre <- gsub(".*2017|2018.*", "", txt))
#> [1] "**   0.77   0.70   0.74   1.12   0.88   0.79   0.10   0.09   0.32   0.05   0.15   0.50\n"
(base_perl <- gsub(".*2017|2018.*", "", txt, perl = TRUE))
#> [1] ".72   2.51\n2015**   2.45   2.30   2.00   1.44   1.20   1.54   1.84   1.56   1.94   1.47   0.86   1.01\n2016**   1.53   1.75   2.40   2.62   2.35   2.03   1.25   0.52   0.45   0.56   1.88   1.17\n**   0.77   0.70   0.74   1.12   0.88   0.79   0.10   0.09   0.32   0.05   0.15   0.50\n"
(string_r <- str_replace_all(txt, ".*2017|2018.*", ""))
#> [1] ".72   2.51\n2015**   2.45   2.30   2.00   1.44   1.20   1.54   1.84   1.56   1.94   1.47   0.86   1.01\n2016**   1.53   1.75   2.40   2.62   2.35   2.03   1.25   0.52   0.45   0.56   1.88   1.17\n**   0.77   0.70   0.74   1.12   0.88   0.79   0.10   0.09   0.32   0.05   0.15   0.50\n"

identical(base_perl, string_r)
#> [1] TRUE

我们可以使用modifiers 更改 PCRE 和 ICU 正则表达式的行为,以便匹配换行符 通过 .。这将产生与 base R TRE 相同的输出:

(base_perl <- gsub("(?s).*2017|2018(?s).*", "", txt, perl = TRUE))
#> [1] "**   0.77   0.70   0.74   1.12   0.88   0.79   0.10   0.09   0.32   0.05   0.15   0.50\n"

(string_r <- str_replace_all(txt, "(?s).*2017|2018(?s).*", ""))
#> [1] "**   0.77   0.70   0.74   1.12   0.88   0.79   0.10   0.09   0.32   0.05   0.15   0.50\n"

identical(base_perl, string_r)
#> [1] TRUE

最后,与 TRE 不同,PCRE 和 ICU 允许我们使用环顾四周,这也是 一个解决问题的选项

str_match(txt, "(?<=2017).*.(?=\n2018)")
#>      [,1]                                                                                    
#> [1,] "**   0.77   0.70   0.74   1.12   0.88   0.79   0.10   0.09   0.32   0.05   0.15   0.50"

reprex package (v0.3.0)

于 2021-08-10 创建