Use regex to extract mixed fraction and text that may also contain mixed fractions with R (stringr)

Viewed 79

Please see below for a sample of the data I am working with, I have 100K+ entries in total, however.

note that the ... in the comment under UNIT is just to make it fit. For example, the full UNIT text is for the first item is 4- to 5-mm-diameter and the fifth item is 3 1/2- to 4-inch-diameter, etc.

library(tidyverse)
                                                #i QTY    UNIT
parts <- c("6 4- to 5-mm-diameter plugs",       #1  6     4- to...diameter
           "6 large bricks",                    #2  6     large
           "1 1/3 shipment concrete",           #3  1.33  shipment
           "1 (14- to 15-oz) gold bars",        #4  1     (14- to 15-oz)
           "16 3 1/2- to 4-inch-diameter caps", #5  16    3 1/2- to...eter
           "1 1/2 tons sand",                   #6  1.5   tons
           "2 1 1/4- to 3-inch diameter caps",  #7  2     1 1/4- to...eter      
           "1/3 shipment cement")               #8  .333  shipment 

I've had some moderate success working from some of the answers on SO but I run into problems when the UNIT text also contains mixed fractions as in items 1 and 5:

# Goal: extract QTY as mixed frac
parts %>% 
  str_extract("(\\d+[\\/\\d[ ]?]*|\\d*)")

# i=1, 5 broken
#[1] "6 4"      "6 "       "1 1/3 "   "1 "       "16 3 1/2" "1 1/2 "  

# Goal: extract UNIT word
parts %>% 
  str_extract("[[:graph:]]{3,11}|[- to ].{5,21}")

# all i with some problem  
# [1] " 4- to 5-mm-diameter p" " large bricks"          " 1/3 shipment concrete"
# [4] " (14- to 15-oz) gold b" " 3 1/2- to 4-inch-diam" " 1/2 tons sand"

My goals is to extract QTY and UNIT as shown in the comment of the code from first to last entry as 6, 6, 1 1/3, 1, 16, 1, 2, 1/3 - in addition, I am trying to pull out the text under UNIT abbreviated just so it'd fit in the code section - here it is in full: 4- to 5-mm-diameter, large, shipment, (14- to 15-oz), 3 1/2- to 4-inch-diameter, tons, 1 1/4- to 3-inch diameter, shipment.

My intuition suggests I should do this in two steps but please let me know if there are better ways to achieve this.

Thank you.

edit: added a critical example number 8.

1 Answers

You may use

m <- str_match(parts, '^(\\d+(?:\\s+\\d+/\\d+)?|\\d+/\\d+)\\s+((?:\\d+(?:-?in(?:ch)?|")?\\s+)*\\S+(?:\\s+to\\s+(?:\\d+(?:-?in(?:ch)?|")?\\s+)*\\S+)?)')
qty <- m[,2]
# => [1] "6"     "6"     "1 1/3" "1"     "16"    "1 1/2" "2"     "1/3"  
unit <- m[,3]
# => [1] "4- to 5-mm-diameter"       "large"                    
#    [3] "shipment"                  "(14- to 15-oz)"           
#    [5] "3 1/2- to 4-inch-diameter" "tons"                     
#    [7] "1 1/4- to 3-inch diameter" "shipment"      

See the R demo and the regex demo. Details:

  • ^ - start of string
  • (\d+(?:\s+\d+/\d+)?|\d+/\d+) - Group 1 (m[,2]): one or more digits followed with an optional occurrence of one or more whitespaces, one or more digits, / and one or more digits, or a / enclosed with one or more digits
  • \s+ - one or more whitespaces
  • ((?:\d+(?:-?in(?:ch)?|")?\s+)*\S+(?:\s+to\s+(?:\d+(?:-?in(?:ch)?|")?\s+)*\S+)?) - Group 2 (m[,3]):
    • (?:\d+(?:-?in(?:ch)?|")?\s+)* - zero or more occurrences of one or more digits followed with an optional occurrence of a " or an optional -, in and then an optional ch substring and then one or more whitespaces
    • \S+ - one or more chars other than whitespace (a "word")
    • (?:\s+to\s+(?:\d+(?:-?in(?:ch)?|")?\s+)*\S+)? - an optional occurrence of:
      • \s+to\s+ - to enclosed with one or more whitespaces
      • (?:\d+(?:-?in(?:ch)?|")?\s+)* - see above
      • \S+ - one or more chars other than whitespace.
Related