【问题标题】:Different behavior of base R gsub and stringr::str_replace_all?基本 R gsub 和 stringr::str_replace_all 的不同行为?
【发布时间】:2020-06-19 13:17:52
【问题描述】:

我希望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.*", "")

gsub 返回预期的输出(包括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"

为什么会这样?

【问题讨论】:

    标签: r regex stringr string-substitution


    【解决方案1】:

    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 正则表达式的行为,以便匹配换行符 通过.。这将产生与基本 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 创建

    【讨论】:

    • 这很有帮助,但是如何修改str_replace_all 函数以使用perl = FALSE 获得gsub 的默认结果?
    • 您可以使用前瞻来匹配您的表达式(而不是用 "" 替换它)。
    猜你喜欢
    • 1970-01-01
    • 2023-02-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多