Quick summary
Summarize this blog with AI
Introduction
gsub() replaces every matching substring in each input element; sub() replaces only the first. A literal match and a regular expression are different contracts. This lesson uses complete base R examples with correctly escaped strings and checks the resulting text.
Tested with R 4.5.1. Run the following code blocks in order. All inputs are included.
Choose literal replacement when the pattern is text
With fixed = TRUE, dots and other regex metacharacters lose their special meaning.
x <- c("v1.2 and v1.2", "v1x2", NA_character_)
print(gsub("v1.2", "v2.0", x, fixed = TRUE))
# "v2.0 and v2.0" "v1x2" NA
stopifnot(identical(gsub("v1.2", "v2.0", x, fixed = TRUE),
c("v2.0 and v2.0", "v1x2", NA_character_)))
stopifnot(identical(sub("cat", "dog", "cat cat", fixed = TRUE), "dog cat"))
stopifnot(identical(gsub("cat", "dog", "cat cat", fixed = TRUE), "dog dog"))
Use fixed matching for a literal user-provided search phrase. Treat a user-supplied regex as a separate, potentially expensive pattern contract; do not assume that adding it to a regex is equivalent to a literal search.
Use a regex for a stated text rule
This rule collapses runs of whitespace and trims ends. A word-boundary example changes the word cat but leaves concatenate alone.
spaced <- " one\t two three "
clean <- trimws(gsub("[[:space:]]+", " ", spaced))
stopifnot(identical(clean, "one two three"))
words <- gsub("\\bcat\\b", "dog", "Cat concatenate cat", ignore.case = TRUE, perl = TRUE)
print(words) # "dog concatenate dog"
stopifnot(identical(words, "dog concatenate dog"))
The R source contains two backslashes to pass one backslash to the regex engine. The word-boundary example explicitly uses the Perl-compatible engine. Case-insensitive replacement uses the replacement text exactly as written; it does not infer capitalization.
Reorder capture groups without inventing a date parser
A capture group can rearrange a known textual layout. The separate Date conversion checks the calendar value afterward.
raw <- c("12-25-2023", "02-29-2024", "02-30-2024", "unknown")
pattern <- "^([0-9]{2})-([0-9]{2})-([0-9]{4})$"
iso <- gsub(pattern, "\\3-\\1-\\2", raw)
print(iso) # "2023-12-25" "2024-02-29" "2024-02-30" "unknown"
parsed <- as.Date(iso, format = "%Y-%m-%d")
valid <- !is.na(parsed) & format(parsed, "%Y-%m-%d") == iso
stopifnot(identical(valid, c(TRUE, TRUE, FALSE, FALSE)))
stopifnot(identical(iso[1:2], c("2023-12-25", "2024-02-29")))
The regex alone accepts February 30 because it checks layout, not calendar rules. It also leaves an unmatched string unchanged. Check the match and parse status before treating the replacement as validated data.
Do not confuse replacement with extraction or lookup
Map month abbreviations explicitly rather than replacing Jan, Feb and Mar with the same month number. For extraction, use a match API that preserves the field you actually matched.
months <- c("Jan", "Feb", "Mar", "Unknown")
lookup <- setNames(sprintf("%02d", 1:12), month.abb)
print(unname(lookup[months])) # "01" "02" "03" NA
stopifnot(identical(unname(lookup[months]), c("01", "02", "03", NA_character_)))
text <- "ticket=ABC-123; owner=Lee"
matched <- regmatches(text, regexpr("[A-Z]{3}-[0-9]{3}", text))
stopifnot(identical(matched, "ABC-123"))
stopifnot(identical(gsub("x", "y", "", fixed = TRUE), ""))
stopifnot(length(gsub("x", "y", character(0), fixed = TRUE)) == 0)
stopifnot(is.na(gsub("x", "y", NA_character_, fixed = TRUE)))
Deleting every character outside a broad “email-like” character class cannot extract distinct email addresses reliably. Use a defined input format and parser for structured addresses. See grepl for matching masks and strsplit for tokenization.