tidyr separate_wider_delim() in R: Split Column by Delimiter Into Cols

The separate_wider_delim() function in tidyr 1.3 splits one column into multiple columns based on a delimiter. It is the modern, type-safe replacement for the deprecated separate().

⚡ Quick Answer
df |> separate_wider_delim(col, delim = "_", names = c("a","b","c"))
df |> separate_wider_delim(col, delim = ",", names_sep = "_")
df |> separate_wider_delim(col, delim = "-", names = c("y","m","d"), too_few = "align_start")
df |> separate_wider_delim(col, delim = "_", names = c("a","b"), too_many = "merge")
df |> tidyr::separate(col, into = c("a","b"), sep = "_") # superseded

Need explanation? Read on for examples and pitfalls.

📊 Is separate_wider_delim() the right tool?
STARTsplit by delimiter into multiple columnsseparate_wider_delim()split by character positionseparate_wider_position()split by regex groupsseparate_wider_regex()split into ROWS not columnsseparate_longer_delim()old code with separateworks but supersededcomplex JSON-like nestingtidyr::unnest_wider()

What separate_wider_delim() does in one sentence

separate_wider_delim(data, cols, delim, names) splits the values of cols by delim and puts the parts into NEW columns named in names. Replaces the older separate() with type-safe column count handling.

Syntax

separate_wider_delim(data, cols, delim, names = NULL, names_sep = NULL, too_few = "error", too_many = "error", cols_remove = TRUE).

Run live
Run live, no install needed. Every R block on this page runs in your browser. Click Run, edit the code, re-run instantly. No setup.
RSplit a date string
library(tidyr) library(dplyr) df <- tibble(date_str = c("2024-01-15","2024-03-20")) df |> separate_wider_delim(date_str, delim = "-", names = c("year","month","day")) #> year month day #> 2024 01 15 #> 2024 03 20

  
Tip
separate_wider_delim() ERRORS by default if rows have too few or too many parts after splitting. Pass too_few = "align_start" or too_many = "merge" to control this.

Five common patterns

1. Standard split

RID into prefix-number
df <- tibble(id = c("user_1","user_2","admin_3")) df |> separate_wider_delim(id, delim = "_", names = c("role","num"))

  

2. Handle uneven splits with too_few

RSome rows have fewer parts
df <- tibble(x = c("a-b-c","d-e","f")) df |> separate_wider_delim(x, delim = "-", names = c("p1","p2","p3"), too_few = "align_start") #> Aligns to the LEFT, fills missing with NA

  

3. Merge extra parts

RSome rows have too many parts
df <- tibble(x = c("a-b","c-d-e")) df |> separate_wider_delim(x, delim = "-", names = c("p1","p2"), too_many = "merge") #> Merges extra parts into the last column

  

4. Auto-name with names_sep

RAuto-generated names with prefix
df |> separate_wider_delim(x, delim = ",", names_sep = "_") #> Columns x_1, x_2, x_3, ...

  

5. Keep original column

Rcols_remove = FALSE
df |> separate_wider_delim(x, delim = "-", names = c("a","b"), cols_remove = FALSE) #> Both x AND a, b in result

  
Key Insight
separate_wider_delim() is type-safe by default: too_few and too_many are explicit choices. This is the main improvement over separate(), which silently produced inconsistent results.

separate_wider_delim() vs separate() vs str_split

Function Output Status
separate_wider_delim() Multiple columns Recommended (1.3+)
separate_wider_position() Multi cols by position Recommended
separate_wider_regex() Multi cols by regex Recommended
tidyr::separate() Multiple columns Superseded
stringr::str_split() List of vectors Manual workflow

When to use which:

  • separate_wider_delim for delimiter-based wider split.
  • separate (old) only in legacy code.
  • str_split for vector-level work outside data frames.

A practical workflow

Use separate_wider_delim for parsing structured strings into columns.

RInteractive R
log_data |> separate_wider_delim( raw_message, delim = " | ", names = c("timestamp","level","module","msg"), too_many = "merge" )

  

Parse log entries into structured columns. too_many = "merge" handles cases where the message itself contains the delimiter.

Common pitfalls

Pitfall 1: too_few = "error" by default. If any row has fewer parts than expected, separate_wider_delim errors. Switch to "align_start" or "align_end" to tolerate.

Pitfall 2: forgetting names. You must provide column names via names (or use names_sep for auto-naming).

Warning
separate_wider_delim() requires tidyr 1.3+ (Jan 2023). Earlier versions only have the superseded separate(). Check version with packageVersion("tidyr").

Try it yourself

Try it: Split a full_name column into first and last. Save to ex_split.

RYour turn: split full names
df <- tibble(full_name = c("Alice Smith","Bob Jones","Carol Lee")) ex_split <- df |> # your code here ex_split #> Expected: 3 rows with first and last columns

  
Click to reveal solution
RSolution
ex_split <- df |> separate_wider_delim(full_name, delim = " ", names = c("first","last")) ex_split #> first last #> 1 Alice Smith #> 2 Bob Jones #> 3 Carol Lee

  

Explanation: Split each full_name on " " into first and last columns.

After mastering separate_wider_delim, look at:

  • separate_wider_position(): split by character position
  • separate_wider_regex(): split by regex groups
  • separate_longer_delim(): split into ROWS instead of columns
  • unite(): opposite (combine columns into one)
  • tidyr::separate(): superseded predecessor

FAQ

What does separate_wider_delim do in tidyr?

It splits one column into multiple new columns based on a delimiter. Replaces the older separate() with type-safe handling of uneven splits.

What is the difference between separate_wider_delim and separate?

separate (old) is superseded. separate_wider_delim has explicit too_few / too_many handling, making errors visible instead of silent.

How do I handle rows with different numbers of parts?

Use too_few = "align_start" or "align_end" for fewer parts; too_many = "merge" to put extras in the last column. Without these, mismatch errors.

Can I keep the original column?

Yes. Pass cols_remove = FALSE to keep the input column alongside the new ones.

What if my delimiter is a regex special character?

Pass it as a literal string. separate_wider_delim treats delim as a literal substring, not regex.