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

qdr*_*ead 6 regex r string-substitution stringr

我希望gsubstringr::str_replace_all在下面返回相同的结果,但只gsub返回预期的结果。我正在开发一个课程来演示,str_replace_all所以我想知道为什么它会在这里返回不同的结果。

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.*", "")
Run Code Online (Sandbox Code Playgroud)

gsub返回预期的输出(之前和包括2017,之后和包括的所有内容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"
Run Code Online (Sandbox Code Playgroud)

然而,str_replace_all只替换2017and2018而留下其余的,即使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"
Run Code Online (Sandbox Code Playgroud)

为什么会这样?

Tim*_*Fan 10

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
Run Code Online (Sandbox Code Playgroud)

我们可以使用修饰符 来改变 PCRE 和 ICU 正则表达式的行为,以便换行符与.. 这将产生与基本 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
Run Code Online (Sandbox Code Playgroud)

最后,与 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"
Run Code Online (Sandbox Code Playgroud)

reprex 包(v0.3.0)于 2021 年 8 月 10 日创建