Skip to Content
Course content

173: Regular Expressions in R with stringr

Click on the "Edit" button in the top corner of the screen to edit your slide content.

Regex is one of those things that feels like a superpower until you're staring at a string of symbols that looks like a cat walked across your keyboard. I've spent plenty of hours debugging a single misplaced parenthesis in a regular expression, so trust me when I say: the tool you use to apply that regex in R matters just as much as the expression itself.

The friction of Base R string functions

If you've been using R for a while, your first instinct is probably to reach for grep(), sub(), or gsub(). They're built-in, so there's no package to load. But here is where it gets annoying: the API is inconsistent. grep() returns an index by default, but if you add value = TRUE, it returns the string. sub() replaces the first match, while gsub() (global substitution) replaces all of them. The argument order also shifts around, which leads to those "why is this returning a logical vector instead of a character vector?" moments that kill your flow.

Imagine we have a vector of messy product codes like "ID_4421_North", "ID_9902_South", and "ID_1123_East". We just want the numeric ID. In base R, you might do something like this:

codes <- c("ID_4421_North", "ID_9902_South", "ID_1123_East")
# Using gsub to strip everything that isn't a digit
gsub("[^0-9]", "", codes)

It works, but it's a "destructive" approach—you're defining what you don't want and throwing it away. If your data gets slightly more complex, like "ID_4421_v2_North", your base R regex suddenly starts returning "44212" because it's blindly stripping everything but numbers. It's brittle.

Finding consistency with stringr

This is why I almost always move my work into stringr. It's part of the tidyverse, and the engineers who built it did something brilliant: they made every single function follow the same pattern. The string always comes first, and the pattern always comes second. No more guessing where the arguments go.

Instead of trying to "delete" the parts we don't want, we can use str_extract() to precisely grab the pattern we actually care about. Let's look at that same product code problem, but with a more robust approach:

library(stringr)
codes <- c("ID_4421_North", "ID_9902_South", "ID_1123_East")

# Extract exactly 4 digits that follow an underscore
str_extract(codes, "(?<=_)\\d{4}")

I used a "positive lookbehind" here (?<=_), which tells R: "find four digits, but only if they are preceded by an underscore." The beauty of stringr isn't just the function names; it's that it integrates perfectly with pipes (%>% or |>), allowing you to clean a column in a dataframe without creating five intermediate temporary variables.

Consistency versus dependencies

Now, you might ask why we don't just stick to base R if it's already there. The trade-off is simple: dependencies. If you're writing a tiny script for yourself, stringr is a no-brainer. If you're building a heavy-duty R package that will be used by thousands of people, adding a dependency like stringr (and by extension, stringi) adds a bit of overhead to the installation process.

But for 95% of data engineering tasks, the mental overhead of remembering whether sub() or gsub() is the one you need is a much higher cost than loading a library. I'd rather spend two seconds loading stringr than ten minutes wondering why my base R regex is returning a numeric index when I wanted a string.




📋 Practical Task

Extracting Valid Email Domains from Log Entries

You have been handed a vector of raw system logs. Each entry contains a timestamp, a log level, and a user email, but the format is inconsistent. Some emails are wrapped in brackets, others are just floating in the text.

Your Goal: Use stringr to extract only the domain name (the part after the @ symbol) from each log entry. Ensure you are extracting the domain specifically, not the entire email address.

library(stringr)

log_entries <- c(
  "2023-10-01 12:00:01 INFO [user@company.com] Login successful",
  "2023-10-01 12:05:22 WARN contact: support@service.net - timeout",
  "2023-10-01 12:10:05 ERROR admin@internal.org failed to connect",
  "2023-10-01 12:15:00 INFO [guest@gmail.com] Page viewed"
)

# Your code here:
# 1. Use str_extract() with a regex that targets the domain.
# 2. Hint: Use a lookbehind for the '@' symbol.
# 3. The domain usually consists of alphanumeric characters, dots, and hyphens.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.