9.2 stringr: Regular Expressions

DATA 412/612 · Week 9 · PDF · Source (.qmd)

A pattern is a regular expression. Escape in R with two backslashes.

1. A pattern is a regular expression

Note 9.1 used literal text. A regular expression describes a set of strings. stringr (through stringi) uses ICU regex. Write the pattern as an ordinary R string, so backslashes double: the regex \. is "\." in R.

words is 980 common English words that come with stringr. Use it for the examples.

library(tidyverse)

str_view(words, "ie")
##   [7] │ ach<ie>ve
##  [86] │ bel<ie>ve
## [111] │ br<ie>f
## [158] │ cl<ie>nt
## [228] │ d<ie>
## [292] │ exper<ie>nce
## [310] │ f<ie>ld
## [342] │ fr<ie>nd
## [472] │ l<ie>
## [620] │ p<ie>ce
## [670] │ qu<ie>t
## [726] │ sc<ie>nce
## [781] │ soc<ie>ty
## [872] │ t<ie>
## [918] │ v<ie>w

str_view() shows the match. It is the fastest way to test a pattern.


2. The language, in pieces

. matches any single character (except a newline). Escape it when you want a real dot: "\.".

str_view(c("abc", "a.c", "ac"), "a.c")
## [1] │ <abc>
## [2] │ <a.c>
str_view(c("abc", "a.c", "ac"), "a\.")
## [2] │ <a.>c

^ is the start of the string. $ is the end.

str_view(words, "^a")
##  [1] │ <a>
##  [2] │ <a>ble
##  [3] │ <a>bout
##  [4] │ <a>bsolute
##  [5] │ <a>ccept
##  [6] │ <a>ccount
##  [7] │ <a>chieve
##  [8] │ <a>cross
##  [9] │ <a>ct
## [10] │ <a>ctive
## [11] │ <a>ctual
## [12] │ <a>dd
## [13] │ <a>ddress
## [14] │ <a>dmit
## [15] │ <a>dvertise
## [16] │ <a>ffect
## [17] │ <a>fford
## [18] │ <a>fter
## [19] │ <a>fternoon
## [20] │ <a>gain
## ... and 45 more
str_view(words, "x$")
## [108] │ bo<x>
## [747] │ se<x>
## [772] │ si<x>
## [841] │ ta<x>
str_view(words, "^apple$")

A character class matches one character from a set: [abc], [a-z], [0-9]. [^aeiou] is “not a vowel.” [^ ] is “not a space.”

str_view(words, "^[aeiou]")
##  [1] │ <a>
##  [2] │ <a>ble
##  [3] │ <a>bout
##  [4] │ <a>bsolute
##  [5] │ <a>ccept
##  [6] │ <a>ccount
##  [7] │ <a>chieve
##  [8] │ <a>cross
##  [9] │ <a>ct
## [10] │ <a>ctive
## [11] │ <a>ctual
## [12] │ <a>dd
## [13] │ <a>ddress
## [14] │ <a>dmit
## [15] │ <a>dvertise
## [16] │ <a>ffect
## [17] │ <a>fford
## [18] │ <a>fter
## [19] │ <a>fternoon
## [20] │ <a>gain
## ... and 155 more
str_view(c("a b", "ab"), "[^ ]")
## [1] │ <a> <b>
## [2] │ <a><b>

(?i) at the front makes the rest of the pattern case-insensitive.

str_detect(c("Apple", "BANANA", "pear"), "(?i)^a")
## [1]  TRUE FALSE FALSE

Repetition sits after the thing it repeats:

  • ? — 0 or 1
  • + — 1 or more
  • * — 0 or more
  • {n} — exactly n
  • {n,} — n or more
  • {n,m} — between n and m
str_view(words, "^a.{3}$")
##  [2] │ <able>
## [33] │ <also>
## [49] │ <area>
## [64] │ <away>
str_view(c("color", "colour"), "colou?r")
## [1] │ <color>
## [2] │ <colour>

Parentheses make a group. \1 is “whatever group 1 matched.” Note 4.2 used matches("(.)\1") in select(). That is a repeated character.

str_view(words, "(.)\1")
##  [5] │ a<cc>ept
##  [6] │ a<cc>ount
##  [8] │ acro<ss>
## [12] │ a<dd>
## [13] │ a<dd>re<ss>
## [16] │ a<ff>ect
## [17] │ a<ff>ord
## [19] │ aftern<oo>n
## [25] │ agr<ee>
## [27] │ a<ll>
## [28] │ a<ll>ow
## [43] │ a<pp>arent
## [44] │ a<pp>ear
## [45] │ a<pp>ly
## [46] │ a<pp>oint
## [47] │ a<pp>roach
## [48] │ a<pp>ropriate
## [53] │ a<rr>ange
## [57] │ a<ss>ociate
## [58] │ a<ss>ume
## ... and 137 more
str_replace("apple", "(.)(.)", "\2\1")
## [1] "paple"

3. Detect, count, extract, split, replace

All of these take the vector first, then the pattern.

x <- c("apple", "banana", "pear", "mango")

str_detect(x, "a")
## [1] TRUE TRUE TRUE TRUE
x[str_detect(x, "^p")]
## [1] "pear"
str_subset(x, "^p")
## [1] "pear"
str_count(x, "a")
## [1] 1 3 1 1
str_count("abababa", "aba")
## [1] 2
str_extract(x, "[aeiou]")
## [1] "a" "a" "e" "a"
str_extract_all(x, "[aeiou]")
## [[1]]
## [1] "a" "e"
## 
## [[2]]
## [1] "a" "a" "a"
## 
## [[3]]
## [1] "e" "a"
## 
## [[4]]
## [1] "a" "o"
str_split("a-b-c", "-")
## [[1]]
## [1] "a" "b" "c"
str_replace(x, "[aeiou]", "-")
## [1] "-pple"  "b-nana" "p-ar"   "m-ngo"
str_replace_all(x, "[aeiou]", "-")
## [1] "-ppl-"  "b-n-n-" "p--r"   "m-ng-"

str_count() counts non-overlapping matches. "aba" in "abababa" is 2, not 3.

str_extract() is the first match. str_extract_all() is every match, as a list.

str_subset(x, pattern) is x[str_detect(x, pattern)].


4. separate() with a regex

tidyr::separate() splits one column into several. sep can be a regex, not only a fixed character.

tibble(x = c("a-1", "b.2", "c 3")) |>
  separate(x, into = c("letter", "num"), sep = "[^[:alnum:]]")
## # A tibble: 3 × 2
##   letter num  
##   <chr>  <chr>
## 1 a      1    
## 2 b      2    
## 3 c      3

[^[:alnum:]] is any character that is not a letter or a digit: a hyphen, a dot, or a space.


5. Practice

Use words. Print the matches (or str_view() them).

  1. Four-letter words that start with a.
  2. Words that start with a vowel.
  3. Words that end in ed but not eed.
  4. Words that start with three consonants.