Characters, Strings, and Regular Expressions in R

Working with text data in R combines base string utilities, the tidyverse stringr package, text mining packages like tidytext, and regular expressions.


1. Characters, Strings, and Vectorized Padding

Column Renaming & Padding Example

When modifying column names across a dataset, an iterative loop can be awkward, repetitive, and slow:

# Verbose loop approach
new_names <- list()
l <- 1
for (n in colnames(x)) {
  ifelse((l < 10), (colnames(x)[l] <- c(paste0('Otu000', c(l)))), (colnames(x)[l] <- c(paste0('Otu00', c(l))))) 
  new_names <- append(new_names, colnames(x)[l])
  l <- l + 1
}

A vectorized approach using stringr::str_pad() is concise, fast, and idiomatic:

# Clean, vectorized stringr approach
new_names <- paste0("Otu", stringr::str_pad(seq_along(colnames(x)), 4, pad = "0"))

2. Regular Expressions (regex)

Drew’s example (using gsub):

Searching through these ugly column headings in data frame called x:

Raw Data (595) 1 - 0 h  
Raw Data (595) 5 - 0 h 30 min 
Raw Data (595) 7 - 1 h  
Raw Data (595) 10 - 1 h 30 min 
Raw Data (595) 13 - 2 h  
Raw Data (595) 16 - 2 h 30 min 
x <- read.delim("x.txt",header=F,stringsAsFactors = FALSE)
## Error in `file()`:
## ! cannot open the connection

I used the following gsub() statement (within an apply() function, but that’s not important here) on data frame x:

gsub('\\.*(\\d*) h (\\d*).*', '\\1.0\\2',x[[1]])
## Error:
## ! object 'x' not found

(On a second look, it’s possible the initial \\.* isn’t necessary, but as always, test your regex’s out before executing them)

perl example

gsub('\\.*(\\d*)\\sh\\s(\\d*).*', '\\1.0\\2',x[[1]], perl=T)
## Error:
## ! object 'x' not found

\\s means “space” which would also match \\t and \\n
\\d{2} means 2 digits
[5-9] [a-z] \W \D

And it catches the following substrings (highlighted in cyan):

Raw Data (595) 1 - 0 h  
Raw Data (595) 5 - 0 h 30 min 
Raw Data (595) 7 - 1 h  
Raw Data (595) 10 - 1 h 30 min 
Raw Data (595) 13 - 2 h  
Raw Data (595) 16 - 2 h 30 min 
d <- data.frame(c("Raw Data (595) 1 - 0 h",  
  "Raw Data (595) 5 - 0 h 30 min",
  "Raw Data (595) 7 - 1 h",
  "Raw Data (595) 10 - 1 h 30 min",
  "Raw Data (595) 13 - 2 h",
  "Raw Data (595) 16 - 2 h 30 min" ))
class(d)
## [1] "data.frame"
dim(d)
## [1] 6 1
dl <- as.list(d)
[1] 6 1
[1] "data.frame"

Perl =T version

gsub('\\.*(\\d*)\\sh\\s(\\d*).*', '\\1.0\\2',d, perl=T)
## [1] "c(\"Raw Data (595) 1 - 0 h\", \"Raw Data (595) 5 - 0.030"
gsub('\\.*(\\d*)\\sh\\s(\\d*).*', '\\1.0\\2',dl, perl=T)
## [1] "c(\"Raw Data (595) 1 - 0 h\", \"Raw Data (595) 5 - 0.030"
[1] "c(1, 5, 6, 2, 3, 4)"
[1] "c(1, 5, 6, 2, 3, 4)"

Non Perl version:

gsub('\\.*(\\d*) h (\\d*).*', '\\1.0\\2',d)
## [1] "c(\"Raw Data (595) 1 - 0 h\", \"Raw Data (595) 5 - 0.030"
gsub('\\.*(\\d*) h (\\d*).*', '\\1.0\\2',dl)
## [1] "c(\"Raw Data (595) 1 - 0 h\", \"Raw Data (595) 5 - 0.030"
[1] "c(1, 5, 6, 2, 3, 4)"
[1] "c(1, 5, 6, 2, 3, 4)"

Drew Doering used this in the context of converting time values in the format “X h Y min” to a decimal hour format, to facilitate downstream analysis/plotting. You can see that specific command in-context here on line 49: https://github.com/dtdoering/grofitr/blob/b70f82755e83ad27b4ebe07c9e78b3cc36159351/findRates.R

However, playing with this in my own instance of RStudio, it doesn’t seem to be doing what I wanted it to do with that specific command. Here is a reproducible example you can use to replace that last command at the bottom:

timePoints <- c("Raw Data (595) 1 - 0 h",
                "Raw Data (595) 5 - 0 h 30 min",
                "Raw Data (595) 7 - 1 h", 
                "Raw Data (595) 10 - 1 h 30 min", 
                "Raw Data (595) 13 - 2 h", 
                "Raw Data (595) 16 - 2 h 30 min",
                "Raw Data (595) 50 - 21 h 30 min")

sapply(timePoints,
       function(x) gsub('.* - (\\d*) h( (\\d*).*)?', 
                        '\\1.0\\3',
                        x, perl = F), 
       USE.NAMES = FALSE)
## [1] "0.0"    "0.030"  "1.0"    "1.030"  "2.0"    "2.030"  "21.030"

Drew would then go on to do some string operations and arithmetic to divide the right side of the decimal by 60.


This site uses Just the Docs, a documentation theme for Jekyll.