library(stringr)
library(dplyr)
library(tidyr)
library(gapminder)
library(nycflights13)Strings and regular expressions
Reference material — not covered in the taught sessions
This page is reference material. It is not taught in a session and you will not be assessed on the details of regular expressions.
It is here because real data is full of messy text — inconsistent column names, identifiers with mixed separators, categories typed five different ways — and stringr is how you deal with that in the tidyverse. Come back to it when you hit that problem in the report.
Setup
words and sentences used below are small example datasets that ship with stringr, so they are available as soon as it is loaded.
What is a string?
- Strings are a sequence of characters, which has to be represented in memory in binary.
- This was first widely done using ASCII (American Standard Code for Information Interchange).
- But ASCII only allows for 128 characters, so nowhere near enough for all languages.
- We now represent strings using Unicode (which didn’t appear in its current form until the 90s), which allows for a lot — more than a million — characters.
- UTF-8 is the most common encoding for Unicode. It uses a variable number of 8-bit units to represent characters.
- R uses UTF-8 by default (though it will report the label “unknown” unless non-ASCII characters appear), and
stringrfully supports Unicode.
stringr
- Removes inconsistencies found in base R.
- Built on top of the
stringipackage. - Starts all functions with
str_, so autocomplete is genuinely useful. - More advanced use requires regular expressions.
Regular expressions
As quoted by R for Data Science, 1st edition:
“When you first look at a regexp, you’ll think a cat walked across your keyboard, but as your understanding improves they will soon start to make sense.”
If you’ve read about regex before, you will also have come across:
“Some people, when confronted with a problem, think ‘I know, I’ll use regular expressions.’ Now they have two problems.”
The short version
- They allow you to match patterns in strings.
- The most basic form matches an actual chunk of text, e.g.
haginhagrid. - Match classes of character with
\s,\d,\w,[abc]and[^abc]. - Because we’re in R, you have to write
\\instead of\— so\\w,\\d. This catches everybody at least once. - Match any character at all with
.. - Expand a match with
?,+,*or{n,m}. - Anchor it to the start with
^or the end with$. - So
^\\w+_\\d{4}$matches"hagrid_2020"but not"hagrid_120".
The rest of this section works through each of those on one example vector.
Matching literal text
my_string <- c(
'Hagrid',
'Hermione',
'Harry.Potter',
'Ronald_Weasley',
'24xHouse Elves'
)
str_detect(my_string, 'Hag')[1] TRUE FALSE FALSE FALSE FALSE
Character classes
Match types of character with \\w (word), \\d (digit) and \\s (whitespace).
my_string[1] "Hagrid" "Hermione" "Harry.Potter" "Ronald_Weasley"
[5] "24xHouse Elves"
str_detect(my_string, '\\w')[1] TRUE TRUE TRUE TRUE TRUE
str_detect(my_string, '\\d')[1] FALSE FALSE FALSE FALSE TRUE
str_detect(my_string, '\\s')[1] FALSE FALSE FALSE FALSE TRUE
Matching any character
. matches anything, which is why it matches every element here.
str_detect(my_string, '.')[1] TRUE TRUE TRUE TRUE TRUE
Quantifiers
?, +, * and {n,m} control how many times the preceding pattern may repeat.
str_detect(my_string, '\\w+')[1] TRUE TRUE TRUE TRUE TRUE
Note the difference [xyz] versus [^xyz] makes — the second negates the class.
str_detect(my_string, '\\d{2}[xyz]\\w+\\s?\\w+')[1] FALSE FALSE FALSE FALSE TRUE
str_detect(my_string, '\\d{2}[^xyz]\\w+\\s?\\w+')[1] FALSE FALSE FALSE FALSE FALSE
Anchors
^ anchors to the start of the string, $ to the end. Anchoring is usually what turns a pattern that nearly works into one that does.
str_detect(my_string, '^H\\w+$')[1] TRUE TRUE FALSE FALSE FALSE
str_detect(my_string, '^Hag$')[1] FALSE FALSE FALSE FALSE FALSE
Basic stringr operations
String length and counts
# number of characters in each string
str_length(my_string)[1] 6 8 12 14 14
# number of matches within each string
str_count(my_string, 'Ha')[1] 1 0 1 0 0
Concatenating strings
str_c('this', 'that', sep = ', ')[1] "this, that"
Manipulating strings
Extracting and replacing
# extract substrings by position
str_sub(my_string, 1, 3)[1] "Hag" "Her" "Har" "Ron" "24x"
# replace the first match in each string
str_replace(my_string, '[\\._x]', ' ')[1] "Hagrid" "Hermione" "Harry Potter" "Ronald Weasley"
[5] "24 House Elves"
str_replace() replaces only the first match in each string. str_replace_all() replaces every match. Picking the wrong one is a quiet bug of exactly the kind this module keeps warning you about — it will not error.
Detecting and viewing
# returns a logical vector - the tidyverse alternative to grepl()
str_detect(my_string, 'Hagrid')[1] TRUE FALSE FALSE FALSE FALSE
# shows you what your pattern actually matched
str_view(my_string, '^\\w')[1] │ <H>agrid
[2] │ <H>ermione
[3] │ <H>arry.Potter
[4] │ <R>onald_Weasley
[5] │ <2>4xHouse Elves
str_view() is the single most useful debugging tool here. When a pattern isn’t working, look at what it is matching before changing it.
Splitting and sorting
sentences |>
head(1) |>
str_split(" ")[[1]]
[1] "The" "birch" "canoe" "slid" "on" "the" "smooth"
[8] "planks."
str_sort(words[1:10], locale = 'en') [1] "a" "able" "about" "absolute" "accept" "account"
[7] "achieve" "across" "act" "active"
Always set locale when sorting. Sort order is language-dependent, and leaving it implicit makes your results depend on the machine they ran on — which is exactly what reproducibility is meant to rule out.
Changing case
str_to_upper(words[1:10]) [1] "A" "ABLE" "ABOUT" "ABSOLUTE" "ACCEPT" "ACCOUNT"
[7] "ACHIEVE" "ACROSS" "ACT" "ACTIVE"
str_to_lower(words[1:10]) [1] "a" "able" "about" "absolute" "accept" "account"
[7] "achieve" "across" "act" "active"
str_to_sentence(words[1:10]) [1] "A" "Able" "About" "Absolute" "Accept" "Account"
[7] "Achieve" "Across" "Act" "Active"
str_to_title(words[1:10]) [1] "A" "Able" "About" "Absolute" "Accept" "Account"
[7] "Achieve" "Across" "Act" "Active"
sentences |>
head(1) |>
str_to_sentence()[1] "The birch canoe slid on the smooth planks."
sentences |>
head(1) |>
str_to_title()[1] "The Birch Canoe Slid On The Smooth Planks."
Practice
If you want to work through this material rather than just read it:
Concatenating and replacing
- Concatenate the strings
"day to"and"day", separated by a hyphen. - Using
starwars: select thehair_colorcolumn and replace', 'with'/'. Hint: pipe a column intopull()to get a vector forstringrto work on. - Using
flights: select the columns ending indelayand remove the underscore from those column names.
Splitting
- Split
"Harry, did you put your name in the Goblet of Fire?"into its components. - Use
boundary("word")instead of" "and compare the results. - Using the fifth line of
sentences: split by word boundary, convert to lowercase and sort. Hint:unlist().
Case
- Using
starwars: converthair_colorvalues to Sentence case, and all column names to Title Case. - Using
gapminder: remove any camelCase from the column names (all to lower case), and convert thecontinentvalues to upper case.
Further reading
- R for Data Science 2e, chapters 14 and 15
- The
stringrdocumentation, andvignette("regular-expressions", package = "stringr") - This blog post by Joel Spolsky on Unicode and character sets
- regex101.com — build and test a pattern interactively, with an explanation of each part. Remember to double your backslashes when you bring the pattern back into R.