Composite functions in mutate_at

Viewed 268

I have a tibble:

df = tibble(one = list('a', 'b'), two = list(c('p1', 'p2', 'p3'), NA_character_), three = list(NA_character_, c('z1', 'z2', 'z3')))

df
# A tibble: 2 x 3
  one   two       three    
  <chr> <list>    <list>   
1 a     <chr [3]> <chr [1]>
2 b     <chr [1]> <chr [3]>

I would like to replace the missing values in columns two and three with the values of column one using coalesce() and then collapse each character vector (rowwise) in two and three into a single string using toString(). My expected output looks like this:

df = tibble(one = c('a', 'b'), two = list('p1, p2, p3', 'b'), three = list('a', 'z1, z2, z3'))
df
# A tibble: 2 x 3
  one   two       three    
  <chr> <list>    <list>   
1 a     <chr [1]> <chr [1]>
2 b     <chr [1]> <chr [1]>

This is what I had tried initially:

df %>% mutate_at(vars(two, three), funs(coalesce(., one) %>% map(., toString)))

I know that funs() quotes its arguments but I don't understand why it doesn't work with the pipeline. The documentation also advises that funs is being deprecated but I'm not sure what to use in its place. I was wondering if someone could shed some light on why the above doesn't work since I'm not at all familiar with the inner workings of scoped verbs.

2 Answers

We can use map2 , use coalesce and then toString.

library(dplyr)
library(purrr)

df1 <- df %>% 
        mutate_at(vars(two, three), ~map2(., one, ~toString(coalesce(.x, .y))))

df1

#  one   two       three    
#  <chr> <list>    <list>   
#1 a     <chr [1]> <chr [1]>
#2 b     <chr [1]> <chr [1]>

df1$two
#[[1]]
#[1] "p1, p2, p3"

#[[2]]
#[1] "b"

df1$three
#[[1]]
#[1] "a"

#[[2]]
#[1] "z1, z2, z3"

In the above example, we use lambda style expression using ~ to use it as a function and it is true funs has been deprecated and has been replaced with list() instead. The answer and comments in this question provides more insight into this.

With tidyverse, we can make use of pmap

library(tidyverse)
out <- df %>% 
          mutate_at(vars(two, three),
               list(~ pmap(list(., one), ~ list(...) %>% 
                        reduce(coalesce) %>%
                        toString)))

out
# A tibble: 2 x 3
#  one   two       three    
#  <chr> <list>    <list>   
#1 a     <chr [1]> <chr [1]>
#2 b     <chr [1]> <chr [1]>
out$two
#[[1]]
#[1] "p1, p2, p3"

#[[2]]
#[1] "b"

out$three
#[[1]]
#[1] "a"

#[[2]]
#[1] "z1, z2, z3"

Or using Map from base R

df[-1] <- lapply(df[-1], function(x) do.call(Map, 
     c(f = function(x, y) toString(coalesce(x, y)), list(x, df$one))))
df
# A tibble: 2 x 3
#  one   two       three    
#  <chr> <list>    <list>   
#1 a     <chr [1]> <chr [1]>
#2 b     <chr [1]> <chr [1]>
df$two
#[[1]]
#[1] "p1, p2, p3"

#[[2]]
#[1] "b"

df$three
#[[1]]
#[1] "a"

#[[2]]
#[1] "z1, z2, z3"
Related