how to automate the creation of a variable with tidyr?

Viewed 58

Consider this simple example:

mytib <- tibble(city_name1 = c('nyc', 'DC'),
       city_name2 = c('US', 'US'),
       planet_name1 = c('earth', 'moon'),
       planet_name2 = c('solar', 'solar'))

# A tibble: 2 x 4
  city_name1 city_name2 planet_name1 planet_name2
  <chr>      <chr>      <chr>        <chr>       
1 nyc        US         earth        solar       
2 DC         US         moon         solar    

I am trying to automate the creation of a few simple variables that use tidyr. Specifically, I am simply adding the two character variables _name1 and _name2 for each topic. As below:

> mytib %>% 
+   tidyr::unite(complete_city,
+                c('city_name1', 'city_name2'), remove = FALSE) %>% 
+   tidyr::unite(complete_planet,
+                c('planet_name1', 'planet_name2'), remove = FALSE) 
# A tibble: 2 x 6
  complete_city city_name1 city_name2 complete_planet planet_name1 planet_name2
  <chr>         <chr>      <chr>      <chr>           <chr>        <chr>       
1 nyc_US        nyc        US         earth_solar     earth        solar       
2 DC_US         DC         US         moon_solar      moon         solar 

The issue is that I would like to automate this. That is, looping over the topics city and planet. How can I do that? I tried the following with no success:

for(topic in c('city', 'planet')){
  
 mytib <-  mytib %>% 
    tidyr::unite('complete_{{topic}}',
                 c('{{topic}}_name1', '{{topic}}_name2'), remove = FALSE)
}

Thanks!

2 Answers

We could use a custom function together with purrr::reduce():

library(dplyr)
library(tidyr)
library(purrr)

mytib <- tibble(city_name1 = c('nyc', 'DC'),
                city_name2 = c('US', 'US'),
                planet_name1 = c('earth', 'moon'),
                planet_name2 = c('solar', 'solar'))


myunite <- function(df, x) {
  tidyr::unite(df, !! paste0("complete_", x),
               paste0(x, "_name", 1:2),
               # alternatively: starts_with(x), 
               remove = FALSE)
}

mytib %>% 
  reduce(c("planet", "city"), myunite, .init = .)
#> # A tibble: 2 x 6
#>   complete_city city_name1 city_name2 complete_planet planet_name1 planet_name2
#>   <chr>         <chr>      <chr>      <chr>           <chr>        <chr>       
#> 1 nyc_US        nyc        US         earth_solar     earth        solar       
#> 2 DC_US         DC         US         moon_solar      moon         solar

Created on 2021-01-29 by the reprex package (v0.3.0) Another approach is to rename some variables and use dplyr::across() instead of tidyr::unite.

mytib %>% 
  rename_with(~ gsub("1$", "", .)) %>% 
  mutate(across(matches("_[A-z]*$"), 
                ~ str_c(get(cur_column()),"_",get(str_c(cur_column(), 2))),
                .names = "complete_{col}"))
#> # A tibble: 2 x 6
#>   city_name city_name2 planet_name planet_name2 complete_city_n~
#>   <chr>     <chr>      <chr>       <chr>        <chr>           
#> 1 nyc       US         earth       solar        nyc_US          
#> 2 DC        US         moon        solar        DC_US           
#> # ... with 1 more variable: complete_planet_name <chr>

Created on 2021-01-29 by the reprex package (v0.3.0)

  1. Create a list of all the variable prefixes that you want to unite
my_names <- names(mytib) %>% 
  stringr::str_sub(., 1, -2) %>% 
  unique(.)
  1. Create a function to unite the named variables. You can get fancier with the creation of new_name if you want
my_unite <- function(name, dat, remove = FALSE) {
  new_name = paste0("complete_", name)
  tidyr::unite(dat, !!new_name, starts_with(name), remove = remove) %>% 
    select(!!new_name, starts_with(name))
}
  1. Combine the names and the function and bind everything back together.
purrr::map_dfc(my_names, my_unite, dat = mytib)
Related