How to Split Strings in R with 'strsplit'

R Published Updated 2 mins read By Leon Wei
How to Split Strings in R with 'strsplit' cover image

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.

Primary references

Interview Prep

Begin Your SQL, Python, and R Journey

Master 230 interview-style coding questions and build the data skills needed for analyst, scientist, and engineering roles.

Related Articles

All Articles