Quick summary
Summarize this blog with AI
Introduction
strsplit() returns a list, with one character vector for every input string. That list preserves record boundaries even when records split into different numbers of pieces. This guide distinguishes literal delimiters, regex separators, empty fields and parser limitations.
Tested with R 4.5.1. Run the following code blocks in order. All inputs are included.
Inspect the list before flattening it
A literal dot must use fixed matching or regex escaping. An unescaped dot in a regex means an arbitrary character.
texts <- c("red.blue", "green.blue.yellow")
parts <- strsplit(texts, ".", fixed = TRUE)
print(parts)
stopifnot(identical(parts, list(c("red", "blue"), c("green", "blue", "yellow"))))
stopifnot(identical(parts[[1]], c("red", "blue")))
stopifnot(identical(lengths(parts), c(2L, 3L)))
Use [[1]] for the first record’s pieces. unlist combines all records and discards that grouping unless you retain an index. Validate the number of fields before assigning pieces to a fixed set of columns.
Choose regex boundaries deliberately
A character class can accept multiple separators. A lookaround splits at a boundary without consuming either neighboring character.
multiple <- strsplit("R,Python;SQL Java", "[,;[:space:]]+")[[1]]
stopifnot(identical(multiple, c("R", "Python", "SQL", "Java")))
boundary <- strsplit("a1b2c3", "(?<=\\D)(?=\\d)", perl = TRUE)[[1]]
print(boundary) # "a" "1b" "2c" "3"
stopifnot(identical(boundary, c("a", "1b", "2c", "3")))
This lookaround splits only between a non-digit and a following digit. It does not split in both directions, so 1b stays together. Two backslashes in R source pass one to the regex engine.
Preserve record boundaries and validate field counts
For this simple delimiter contract, quoted delimiters are not permitted. Reject a malformed record instead of guessing which field is missing.
records <- c("A|10|East", "B|20|West", "C|30")
fields <- strsplit(records, "|", fixed = TRUE)
valid <- lengths(fields) == 3L
print(valid) # TRUE TRUE FALSE
parsed <- do.call(rbind, fields[valid])
colnames(parsed) <- c("id", "amount_text", "region")
stopifnot(identical(dim(parsed), c(2L, 3L)))
stopifnot(identical(parsed[, "id"], c("A", "B")))
rejected <- records[!valid]
stopifnot(identical(rejected, "C|30"))
CSV quoting and escaped delimiters require a format-aware reader such as read.csv; splitting each comma is not a CSV parser. Keep an empty result as an explicit no-data case before calling rbind on zero valid records.
Test empty fields and missing values
The base function retains leading and interior empty pieces but does not return a final empty piece for a trailing delimiter.
edge <- strsplit(c(",a,", "a,,b", "", NA_character_), ",", fixed = TRUE)
stopifnot(identical(edge, list(c("", "a"), c("a", "", "b"), character(0), NA_character_)))
stopifnot(identical(strsplit(character(0), ",", fixed = TRUE), list()))
words <- strsplit("Bears beat Battlestar Galactica", " ", fixed = TRUE)[[1]]
matched <- grep("^b", words, ignore.case = TRUE, value = TRUE)
print(matched) # "Bears" "beat" "Battlestar"
stopifnot(identical(matched, c("Bears", "beat", "Battlestar")))
Do not remove empty tokens unless an empty field is meaningless for your format. Case-insensitive matching includes Bears as well as beat and Battlestar. Token matching can identify words; it does not, by itself, establish the sentiment of a sentence.