【问题标题】:Split & extract part of string (between a "." and digit) in R在 R 中拆分和提取字符串的一部分(在“。”和数字之间)
【发布时间】:2019-07-12 12:42:44
【问题描述】:

我有一个字符变量 (companies),其观察结果如下所示:

  1. "612. Grt. Am. Mgt. & Inv. 7.33"
  2. “77. 威克斯 4.61”
  3. “265.王实验室8.75”
  4. “9. CrossLand Savings 6.32”
  5. “228. JPS Textile Group 2.00”

我正在尝试将这些字符串分成 3 部分:

  1. 第一个"."之前的所有数字,
  2. 第一个 "." 和下一个数字之间的所有内容 (一致的格式#.##),和
  3. 最后一个数字本身 (格式#.##)。

以第一个 obs 为例,我想:“612”、“Grt. Am. Mgt & Inv”、“5.01”

我尝试在rebus 中定义模式并使用str_match,但下面的代码仅适用于obs #2 和#3 等情况。它不能反映字符串中间部分的所有变化来捕获其他 obs。

pattern2 <- capture(one_or_more(DGT)) %R% DOT %R% SPC %R% 
            capture(or(one_or_more(WRD), one_or_more(WRD) %R% SPC 
            %R% one_or_more(WRD))) %R% SPC %R% capture(DGT %R% DOT 
            %R% one_or_more(DGT))

str_match(companies, pattern = pattern2)

有没有更好的方法将字符串分成这 3 个部分?

我不熟悉 regex,但我在这里看到了很多建议(我是 R 和 Stack Overflow 的新手)

【问题讨论】:

    标签: r regex stringr


    【解决方案1】:

    您可以使用正则表达式分隔字符串,然后拆分该字符串以获得结果:

    delimitedString = gsub( "^([0-9]+). (.*) ([0-9.]+)$", "\\1,\\2,\\3", companies  )
    
    do.call( 'rbind', strsplit(split = ",", x = delimitedString) )
    #      [,1]  [,2]                   [,3]  
    #[1,] "612" "Grt. Am. Mgt. & Inv." "7.33"
    #[2,] "77"  "Wickes"               "4.61"
    #[3,] "265" "Wang Labs"            "8.75"
    #[4,] "9"   "CrossLand Savings"    "6.32"
    #[5,] "228" "JPS Textile Group"    "2.00" 
    

    正则表达式解释

    • ^[0-9]+ :字符串开头(即^)由0到9的数字组成的任何模式
    • .* : 贪婪匹配,基本上是上面案例中被两个空格包围的任何东西
    • [0-9.]+$:又是数字 + 一个点,并在字符串的结尾(即$

    括号用于表示我想捕获 string 中由正则表达式拟合的那些部分。捕获它们后,这些子字符串将折叠并用逗号分隔。最后,我们可以用strsplit函数拆分整个字符串,用do.call函数绑定行

    【讨论】:

    • 谢谢,尤利西斯!这也奏效了。解释真的很有帮助
    • 我很高兴知道它有帮助,@Chan
    【解决方案2】:

    您应该能够调试您编写的正则表达式。

    > as.regex(pattern2)
    <regex> ([\d]+)\.\s((?:[\w]+|[\w]+\s[\w]+))\s(\d\.[\d]+)
    

    Plug it in 在 regex101,您会看到您的字符串并不总是匹配。右边的解释告诉您,在点和数字之间只允许 1 或 2 个空格分隔的单词。此外,WRD[\w]+ 模式)不匹配点和任何其他不是字母、数字或 _ 的字符。现在,你知道你需要匹配你的字符串

    ^(\d+)\.(.*?)\s*(\d\.\d{2})$
    

    this regex demo。翻译成 Rebus:

    pattern2 <- START %R%            # ^ - start of string
     capture(one_or_more(DGT)) %R%   # (\d+) - Group 1: one or more digits
     DOT %R%                         # \. - a dot
     "(.*?)" %R%                     # (.*?) - Group 2: any 0+ chars as few as possible
     zero_or_more(SPC) %R%           # \s* - 0+ whitespaces 
     capture(DGT %R% DOT %R% repeated(DGT, 2)) %R% # (\d\.\d{2}) - Group 3: #.## number
    END                              # $ - end of string
    

    检查:

    > pattern2
    <regex> ^([\d]+)\.(.*?)[\s]*(\d\.[\d]{2})$
    
    > companies <- c("612. Grt. Am. Mgt. & Inv. 7.33","77. Wickes 4.61","265. Wang Labs 8.75","9. CrossLand Savings 6.32","228. JPS Textile Group 2.00")
    > str_match(companies, pattern = pattern2)
         [,1]                             [,2]  [,3]                    [,4]  
    [1,] "612. Grt. Am. Mgt. & Inv. 7.33" "612" " Grt. Am. Mgt. & Inv." "7.33"
    [2,] "77. Wickes 4.61"                "77"  " Wickes"               "4.61"
    [3,] "265. Wang Labs 8.75"            "265" " Wang Labs"            "8.75"
    [4,] "9. CrossLand Savings 6.32"      "9"   " CrossLand Savings"    "6.32"
    [5,] "228. JPS Textile Group 2.00"    "228" " JPS Textile Group"    "2.00"
    

    警告capture(lazy(zero_or_more(ANY_CHAR))) 返回的([.]*?) 模式尽可能少地匹配 0 个或多个点,而不是匹配任何 0+ 个字符,因为 rebus 有一个错误:它包含所有repeatedone_or_morezero_or_more)字符与[],一个字符类。这就是“手动”添加(.*?) 的原因。

    可以使用[\w\W] / [\s\S][\d\D] 等常见结构解决或解决此问题:

    pattern2 <- START %R%                          # ^ - start of string
     capture(one_or_more(DGT)) %R%                 # (\d+) - Group 1: one or more digits
     DOT %R%                                       # \. - a dot
     capture(                                      # Group 2 start:
      lazy(zero_or_more(char_class(WRD, NOT_WRD))) #  - [\w\W] - any 0+ chars as few as possible
     ) %R%                                         # End of Group 2
     zero_or_more(SPC) %R%                         # \s* - 0+ whitespaces 
     capture(DGT %R% DOT %R% repeated(DGT, 2)) %R% # (\d\.\d{2}) - Group 3: #.## number
    END
    

    检查:

    > as.regex(pattern2)
    <regex> ^([\d]+)\.([\w\W]*?)[\s]*(\d\.[\d]{2})$
    

    请参阅regex demo

    【讨论】:

    • 感谢这个超级详细的解释!效果很好,我从你们的 cmets 身上学到了很多。
    【解决方案3】:

    您可以使用 3 个捕获组:

    ([^.]+)\.\s+(\D+)\s+(\d\.\d{2})
    

    例如

    companies=c("612. Grt. Am. Mgt. & Inv. 7.33")
    pattern="([^.]+)\\.\\s+(\\D+)\\s+(\\d\\.\\d{2})"
    str_match(companies, pattern)
    

    结果

         [,1]                             [,2]  [,3]                   [,4]  
    [1,] "612. Grt. Am. Mgt. & Inv. 7.33" "612" "Grt. Am. Mgt. & Inv." "7.33"
    

    查看regex101 demo | R demo

    说明

    • ([^.]+) 在第 1 组中捕获匹配 1+ 次而不是点(如果不匹配换行符,请使用 [^.\r\n]
    • \.\s+ 匹配一个点和 1 次以上的空白字符
    • (\D+) 在第 2 组中捕获匹配 1+ 次而不是数字
    • \s+ 匹配 1+ 次空白字符
    • (\d\.\d{2}) 在第 3 组中捕获一个数字、一个点和 2 个数字(格式 #.##)

    【讨论】:

      【解决方案4】:

      您可以使用分组正则表达式匹配信息,并从您想要的三个组中提取信息,而不是拆分文本。尝试使用这个正则表达式,

      (.+?)\.\s+(.+)\s+(\d+\.\d+)
      

      这将捕获您在 group1、group2 和 group3 中的信息。

      Demo

      这里,group1 获取公司信息前的第一个数字,group2 获取公司信息,group3 获取表单#.## 的最后一个数字

      检查这个r代码,

      companies = c("612. Grt. Am. Mgt. & Inv. 7.33")
      result <- str_match(companies, pattern = "(.+?)\\.\\s+(.+)\\s+(\\d+\\.\\d+)")
      result[,2]
      result[,3]
      result[,4]
      

      打印,

      [1] "612"
      [1] "Grt. Am. Mgt. & Inv."
      [1] "7.33"
      

      【讨论】:

        【解决方案5】:

        使用以下正则表达式:

        ^(.*?)\.(.*?)(?=\d)(.*)$

        Demo

        三个捕获组包含所需的信息:第一组捕获所有内容,直到找到第一个 '.',第二组捕获所有内容,直到找到一个数字(这是通过正向前瞻完成的,这确保了数字不会被消耗,因为我们需要在下一组中捕获它),第三组捕获所有内容直到结束。

        【讨论】:

        • 谢谢!该演示帮助澄清了很多
        猜你喜欢
        • 1970-01-01
        • 2019-10-04
        • 1970-01-01
        • 2020-02-14
        • 1970-01-01
        • 2015-09-19
        • 2023-03-10
        • 1970-01-01
        相关资源
        最近更新 更多