How to combine mutate and str_match correctly?

Viewed 430

Let's say I want to split a string column to separate columns. For that I use mutate and str_match (or str_replace) from stringr package, but the result is not as wanted.

Set up data frame and split the columns:

df <-
  data.frame(strings = c('a_b_c', 'ab_cd_ef', 'abc_def_ghi')) %>%
  mutate(string = stringr::str_match(strings, '([a-z]+)_([a-z]+)_([a-z]+)')) 

df
      strings    string.1 string.2 string.3 string.4
1       a_b_c       a_b_c        a        b        c
2    ab_cd_ef    ab_cd_ef       ab       cd       ef
3 abc_def_ghi abc_def_ghi      abc      def      ghi

When looking the the column names I only see two columns. This also makes it complicated to refer the columns. I assume it has something to do with the matrix format of the output from the str_match function.

df %>% ncol
[1] 2

df %>% colnames
[1] "strings" "string"

Is there an easy way how to have this new columns behaving like normal data.frame columns? If possible, with a renaming step. This is something I want to have:

df %>% ncol
[1] 5

df %>% colnames
[1] "strings" "string_1" "string_2" "string_3" "string_4"

df
      strings    string_1 string_2 string_3 string_4
1       a_b_c       a_b_c        a        b        c
2    ab_cd_ef    ab_cd_ef       ab       cd       ef
3 abc_def_ghi abc_def_ghi      abc      def      ghi

2 Answers

We can use cSplit

library(splitstackshape)
cSplit(df, "strings", "_", drop = FALSE)

Or using separate from tidyr

library(tidyr)
library(stringr)
df %>%
    separate(strings, into = str_c('string_', 1:3), remove = FALSE)

To answer the original question in its most generic form: str_match() produces a character matrix. We can turn it into a tibble using as_tibble with the .name_repair parameter to pick column names — which, thanks to tidyr magic, also works under mutate():

library(tidyverse)

df <-
  data.frame(strings = c('a_b_c', 'ab_cd_ef', 'abc_def_ghi'))

df %>%
  mutate(stringr::str_match(strings, '([a-z]+)_([a-z]+)_([a-z]+)') %>%
         as_tibble(.name_repair = ~ c("matched", "prefix", "midfix", "suffix")))
      strings     matched prefix midfix suffix
1       a_b_c       a_b_c      a      b      c
2    ab_cd_ef    ab_cd_ef     ab     cd     ef
3 abc_def_ghi abc_def_ghi    abc    def    ghi

If you want to discard the matched column (as it doesn't bring any additional information w.r.t. strings in this particular example), you can do so in a final pipeline step e.g. %>% select(-matched), either inside or outside the mutate() as you like.

Related