30

I have a dataframe with a column of strings and want to extract substrings of those into a new column.

Here is some sample code and data showing I want to take the string after the final underscore character in the id column in order to create a new_id column. The id column entry always has 2 underscore characters and it's always the final substring I would like.

df = data.frame( id = I(c("abcd_123_ABC","abc_5234_NHYK")), x = c(1.0,2.0) )

require(dplyr)

df = df %>% dplyr::mutate(new_id = strsplit(id, split="_")[[1]][3])

I was expecting strsplit to act on each row in turn.

However, the new_id column only contains ABC in each row, whereas I would like ABC in row 1 and NHYK in row 2. Do you know why this fails and how to achieve what I want?

zx8754
  • 52,746
  • 12
  • 114
  • 209
PM.
  • 644
  • 1
  • 11
  • 15

5 Answers5

35

You could use stringr::str_extract:

library(stringr)

 df %>%
   dplyr::mutate(new_id = str_extract(id, "[^_]+$"))


#>              id x new_id
#> 1  abcd_123_ABC 1    ABC
#> 2 abc_5234_NHYK 2   NHYK

The regex says, match one or more (+) of the characters that aren't _ (the negating [^ ]), followed by end of string ($).

Sam Firke
  • 21,571
  • 9
  • 87
  • 105
29

An alternative without regex and keeping in the tidyverse style is to use tidyr::separate(). Note, this does remove the input column by default (remove=FALSE to prevent it).

## using your example data
df = data.frame( id = I(c("abcd_123_ABC","abc_5234_NHYK")), x = c(1.0,2.0) )

## separate knowing you will have three components
df %>% separate(id, c("first", "second", "new_id"), sep = "_") %>% select(-first, -second)
## returns
  new_id x
1    ABC 1
2   NHYK 2
vincentmajor
  • 1,076
  • 12
  • 20
12

Use dplyr::rowwise:

df %>% dplyr::rowwise() %>% dplyr::mutate(new_id = strsplit(id, split="_")[[1]][3])

Further alternatives are discussed here:

http://www.expressivecode.org/2014/12/17/mutating-using-functions-in-dplyr/

Philipp Merkle
  • 2,555
  • 2
  • 11
  • 22
  • 2
    Note that this will be slower than typical `dplyr` as it can't benefit from vectorized operations. Still, +1 for the tip. – vincentmajor Jun 07 '17 at 21:20
6

This can be done using str_split by specifying the simplify argument.

Simplify unlists the split strings and allows element selection using an index. In this case where there is always 2x "_", we can always take the third element.

# Create df
df = data.frame( id = I(c("abcd_123_ABC","abc_5234_NHYK")), x = c(1.0,2.0) )

# Create new_id using dplyr only
df <- df %>% 
  mutate(new_id = str_split(id, "_", simplify = TRUE)[ , 3])

See https://github.com/tidyverse/stringr/issues/265

lifedroid
  • 164
  • 2
  • 7
1

Here's one way to use strsplit in a general way to do what you're looking for.

library(dplyr)
df = data.frame( id = I(c("abcd_123_ABC","abc_5234_NHYK")), x = c(1.0,2.0) )

temp <- seq(from=3, by=3, length.out = length(df))
dfn <- df %>% dplyr::mutate(new_id = unlist(strsplit(id, split="_"))[temp])

> dfn
             id x new_id
1  abcd_123_ABC 1    ABC
2 abc_5234_NHYK 2   NHYK
Lloyd Christmas
  • 1,016
  • 6
  • 15