R
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Data Structures
-
Section 4: Data Manipulation
-
Section 5: Visualization and Statistics
-
Section 6: Advanced R
-
Section 7: Practical Projects
-
Section 8: Interview Practice
-
Section 9: More Practice Exercises
-
Section 10: Shiny Apps in Depth
-
Section 11: More Data Wrangling
-
Section 12: Tidyverse Deep Dive
-
Section 13: Statistical Modeling Deep Dive
-
Section 14: Machine Learning in R
-
Section 15: R Visualization Deep Dive
-
Section 16: R Package Development Deep Dive
-
Section 17: R for Reproducible Research
-
Section 18: R and Databases
-
Section 19: R Performance Optimization
-
Section 20: Bioinformatics and Specialized R
-
Section 21: More Shiny Practice
-
Section 22: More Practice Exercises
-
Section 23: R Data Structures Deep Dive
-
Section 24: More Interview and Review
-
Section 25: R for Business Analytics
-
Section 26: R Text Mining and NLP
-
Section 27: R Spatial Data Analysis
-
Section 28: R Deep Learning
-
Section 29: Advanced Statistical Techniques
-
Section 30: R Object Systems Deep Dive
-
Section 31: R Environments and Metaprogramming
-
Section 32: R for Finance
-
Section 33: R for Clinical and Health Data
-
Section 34: More Shiny Advanced Practice
-
Section 35: R Data Cleaning Deep Dive
-
Section 36: R Reporting Automation
-
Section 37: More Practical Projects Round 2
-
Section 38: R Ecosystem and Career
-
Section 39: More Visualization Practice
-
Section 40: R for Experimentation
-
Section 41: R for Genomics and Bioinformatics Deep Dive
-
Section 42: R for Marketing Analytics
-
Section 43: R Data Import/Export Deep Dive
-
Section 44: R String Processing Deep Dive
-
Section 45: R for Actuarial and Insurance Analytics
-
Section 46: R Testing and Quality Assurance Deep Dive
-
Section 47: R Version Control and Collaboration
-
Section 48: R Advanced Functional Programming
-
Section 49: R for Supply Chain and Operations
-
Section 50: More Practice Exercises Round 3
-
Section 51: R Dashboards and BI Integration
-
Section 52: R Data Governance and Ethics
-
Section 53: More Modeling Practice
-
Section 54: R Final Capstone Projects
-
Section 55: R for Sports Analytics
-
Section 56: More Interview Practice Round 2
-
Section 57: R Networking and APIs
-
Section 58: R for Environmental Science
-
Section 59: R Notebook and Documentation Practices
-
Section 60: More Data Wrangling Mastery
-
Section 61: R for A/B Testing at Scale
-
Section 62: R Package Ecosystem Deep Dive
173: Regular Expressions in R with stringr
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.
There are no comments for now.