Can I use factors as search and replace values in mutate?

Viewed 59

I have a list of values that I want to substitute with values from another list, wherever they appear. So for example, wherever "white" appears as a hair color, I want to use "light"; wherever "auburn" appears I want to use "brown. I can accomplish that with:

find_text = c("white", "auburn", "none")
replace_text = c("light", "brown", "bald")

starwars %>%
  filter(gender == "feminine") %>% 
  select(c("name", "hair_color", "species")) %>%
  mutate(hair_color = str_replace(hair_color, find_text[1], replace_text[1]),
         hair_color = str_replace(hair_color, find_text[2], replace_text[2]),
         hair_color = str_replace(hair_color, find_text[3], replace_text[3]),
         )

I thought I could use fct_recode() but that also appears to require a single named string. Is there a cleaner way to go about this?

2 Answers

We can use a named vector to replace with str_replace. Here, it is assumed that we want to replace substring i.e. match and replace a substring rather than a fixed string replacement

library(dplyr)
library(stringr)
starwars %>%
   filter(gender == "feminine") %>% 
   select(c("name", "hair_color", "species")) %>%
   mutate(hair_color_new = str_replace_all(hair_color, 
          set_names(replace_text, find_text)))
# A tibble: 17 x 4
#   name               hair_color species    hair_color_new
#   <chr>              <chr>      <chr>      <chr>         
# 1 Leia Organa        brown      Human      brown         
# 2 Beru Whitesun lars brown      Human      brown         
# 3 Mon Mothma         auburn     Human      brown         
# 4 Shmi Skywalker     black      Human      black         
# 5 Ayla Secura        none       Twi'lek    bald          
# 6 Adi Gallia         none       Tholothian bald          
# 7 Cordé              brown      Human      brown         
# 8 Luminara Unduli    black      Mirialan   black         
# 9 Barriss Offee      black      Mirialan   black         
#10 Dormé              brown      Human      brown         
#11 Zam Wesell         blonde     Clawdite   blonde        
#12 Taun We            none       Kaminoan   bald          
#13 Jocasta Nu         white      Human      light         
#14 R4-P17             none       Droid      bald          
#15 Shaak Ti           none       Togruta    bald          
#16 Rey                brown      Human      brown         
#17 Padmé Amidala      brown      Human      brown    

If we want fixed matches, then recode is also useful

starwars %>%
    filter(gender == "feminine") %>% 
    select(c("name", "hair_color", "species"))  %>% 
    mutate(hair_color_new = recode(hair_color, !!! set_names(replace_text, find_text)))
# A tibble: 17 x 4
#   name               hair_color species    hair_color_new
#   <chr>              <chr>      <chr>      <chr>         
# 1 Leia Organa        brown      Human      brown         
# 2 Beru Whitesun lars brown      Human      brown         
# 3 Mon Mothma         auburn     Human      brown         
# 4 Shmi Skywalker     black      Human      black         
# 5 Ayla Secura        none       Twi'lek    bald          
# 6 Adi Gallia         none       Tholothian bald          
# 7 Cordé              brown      Human      brown         
# 8 Luminara Unduli    black      Mirialan   black         
# 9 Barriss Offee      black      Mirialan   black         
#10 Dormé              brown      Human      brown         
#11 Zam Wesell         blonde     Clawdite   blonde        
#12 Taun We            none       Kaminoan   bald          
#13 Jocasta Nu         white      Human      light         
#14 R4-P17             none       Droid      bald          
#15 Shaak Ti           none       Togruta    bald          
#16 Rey                brown      Human      brown         
#17 Padmé Amidala      brown      Human      brown      

Here's an option using join.

library(tidyverse)

replacetext <- data.frame(
  find_text = c("white", "auburn", "none"), 
  replace_text = c("light", "brown", "bald"), 
  stringsAsFactors = F)

starwars %>%
  filter(gender == "female") %>% 
  select(c("name", "hair_color", "species")) %>% 
  left_join(replacetext, by = c("hair_color" = "find_text")) %>% 
  mutate(replace_text = coalesce(replace_text, hair_color)) %>% 
  select(-hair_color) %>% 
  rename(hair_color = replace_text)
Related