ℕʘʘḆḽḘ
ℕʘʘḆḽḘ

Reputation: 19375

how to dynamically create or call variables and functions in dplyr?

Consider this simple example:

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

func_planet <- function(var){str_to_upper(var)}
func_city <- function(var){str_to_lower(var)}


mytib %>% 
  mutate(new_planet= map_chr(planet_name1, ~func_planet(.x)))

# A tibble: 2 x 3
  city_name1 planet_name1 new_planet
  <chr>      <chr>        <chr>     
1 nyc        earth        EARTH     
2 DC         moon         MOON      

Easy enough. But now assume I want to loop over different prefixes (city instead of planet). Is it possible to automate this in the tidyverse?

See the complication in the code below: we are creating a variable, calling a function and referencing a variable dynamically.

for(var in c('planet', 'city')){
 mytib %>% 
    mutate(glue('new_{var}' := map(!!glue('{var}_name1', ~glue('func_{var}')(.x)))
}

Upvotes: 1

Views: 89

Answers (3)

Dunois
Dunois

Reputation: 1843

Building up a bit on @tmfmnk 's answer, perhaps this is what you're looking for:

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

func_planet <- function(var){str_to_upper(var)}
func_city <- function(var){str_to_lower(var)}

funcnames <- c("planet", "city")

map2(.x = funcnames,
     .y = paste0("func_", funcnames),  
     ~ mytib %>% mutate(across(.cols = starts_with(.x), .fns = as.list(.y), .names = "{col}_new"))) %>%
  reduce(full_join)

# Joining, by = c("city_name1", "planet_name1")
# # A tibble: 2 x 4
# city_name1 planet_name1 planet_name1_new city_name1_new
# <chr>      <chr>        <chr>            <chr>         
#   1 nyc        earth        EARTH            nyc           
#   2 DC         moon         MOON             dc  



mytib1 <- tibble(city_name1 = c('nyc', 'DC'),
                planet_name1 = c('earth', 'moon'), 
                fruit_name1 = c('SUNFLOWER', 'WALDO'))

func_planet <- function(var){str_to_upper(var)}
func_city <- function(var){str_to_lower(var)}
func_fruit <- function(var){str_count(var)}

funcnames <- c("planet", "city", "fruit")


map2(.x = funcnames,
     .y = paste0("func_", funcnames),  
     ~ mytib1 %>% mutate(across(.cols = starts_with(.x), .fns = as.list(.y), .names = "{col}_new"))) %>%
  reduce(full_join)

# # A tibble: 2 x 6
# city_name1 planet_name1 fruit_name1 planet_name1_new city_name1_new fruit_name1_new
# <chr>      <chr>        <chr>       <chr>            <chr>                    <int>
#   1 nyc        earth        SUNFLOWER   EARTH            nyc                          9
#  2 DC         moon         WALDO       MOON             dc                           5

You should be able to loop over whatever function names you want to, as long as they have a corresponding prefix in a pre-existing column in the tibble/data.frame.

Upvotes: 1

tmfmnk
tmfmnk

Reputation: 39858

A different approach could be:

map2(.x = c("planet", "city"),
     .y = c(func_planet, func_city),
     ~ mytib %>%
      mutate(across(starts_with(.x), .y, .names = "{col}_new"))) %>%
 reduce(full_join)

  city_name1 planet_name1 planet_name1_new city_name1_new
  <chr>      <chr>        <chr>            <chr>         
1 nyc        earth        EARTH            nyc           
2 DC         moon         MOON             dc  

Or if you want to glue the names in the vector:

map2(.x = c("planet", "city"),
     .y = c(func_planet, func_city),
     ~ mytib %>%
      mutate(across(starts_with(.x), .y, .names = glue("new_", {.x})))) %>%
 reduce(full_join)

  city_name1 planet_name1 new_planet new_city
  <chr>      <chr>        <chr>      <chr>   
1 nyc        earth        EARTH      nyc     
2 DC         moon         MOON       dc     

Upvotes: 4

r2evans
r2evans

Reputation: 160417

Sopmething like this?

mytib %>% 
#   mutate(across(city_name1:planet_name1, list(new = function(z) map_chr(z, ~ func_planet(.x)))))
# # A tibble: 2 x 4
#   city_name1 planet_name1 city_name1_new planet_name1_new
#   <chr>      <chr>        <chr>          <chr>           
# 1 nyc        earth        NYC            EARTH           
# 2 DC         moon         DC             MOON            

across will work with character vectors as well, as in

mytib %>% 
  mutate(across(c("city_name1", "planet_name1"), list(new = function(z) map_chr(z, ~ func_planet(.x)))))

Upvotes: 1

Related Questions