Search code examples
rdplyrprocessing-efficiencymagrittr

'mutate' to add two columns with a single fn-call in tidyverse in R


This is an R Version 3.4.4 question

A voting function voteOnBase, takes 2 arguments and returns a 2-element list: the WINNER and the VOTE.COUNT. I want to use it to add those two columns to notVotedYet, a tibble. The following code runs correctly.

 library(tidyverse)

 withVotes <- notVotedYet %>%
    group_by(BASE) %>%
    mutate(WINNER     = voteOnBase(BASE, CODES)[[1]],
           VOTE.COUNT = voteOnBase(BASE, CODES)[[2]])

However, it calls voteOnBase twice on the same inputs. How can I eliminate the extra function call but still add the same two columns?


Solution

  • Not easy to answer without some example data and output, but I would suggest writing voteOnBase() to return a tibble, not a list. Then you can store the result in a list column and create the columns using unnest().

    To illustrate: here's a function, square_it() which like yours, takes 2 arguments and returns 2 elements - but as columns in a tibble.

    square_it <- function(x, y) {
      tibble(x = x^2, y = y^2)
    }
    

    We can use the iris dataset to pass the arguments. We use pmap() to specify the variables and the function. The list column is named sq:

    iris %>% 
      as_tibble() %>% 
      mutate(sq = pmap(list(Sepal.Length, Sepal.Width), square_it))
    
    # A tibble: 150 x 6
       Sepal.Length Sepal.Width Petal.Length Petal.Width Species sq              
              <dbl>       <dbl>        <dbl>       <dbl> <fct>   <list>          
     1          5.1         3.5          1.4         0.2 setosa  <tibble [1 x 2]>
     2          4.9         3            1.4         0.2 setosa  <tibble [1 x 2]>
     3          4.7         3.2          1.3         0.2 setosa  <tibble [1 x 2]>
     4          4.6         3.1          1.5         0.2 setosa  <tibble [1 x 2]>
     5          5           3.6          1.4         0.2 setosa  <tibble [1 x 2]>
     6          5.4         3.9          1.7         0.4 setosa  <tibble [1 x 2]>
     7          4.6         3.4          1.4         0.3 setosa  <tibble [1 x 2]>
     8          5           3.4          1.5         0.2 setosa  <tibble [1 x 2]>
     9          4.4         2.9          1.4         0.2 setosa  <tibble [1 x 2]>
    10          4.9         3.1          1.5         0.1 setosa  <tibble [1 x 2]>
    # ... with 140 more rows
    

    Just add %>% unnest(sq) to that code, to generate the columns x and y:

    iris %>% 
      as_tibble() %>% 
      mutate(sq = pmap(list(Sepal.Length, Sepal.Width), square_it)) %>%
      unnest(sq)
    
    # A tibble: 150 x 7
       Sepal.Length Sepal.Width Petal.Length Petal.Width Species     x     y
              <dbl>       <dbl>        <dbl>       <dbl> <fct>   <dbl> <dbl>
     1          5.1         3.5          1.4         0.2 setosa   26.0 12.2 
     2          4.9         3            1.4         0.2 setosa   24.0  9   
     3          4.7         3.2          1.3         0.2 setosa   22.1 10.2 
     4          4.6         3.1          1.5         0.2 setosa   21.2  9.61
     5          5           3.6          1.4         0.2 setosa   25   13.0 
     6          5.4         3.9          1.7         0.4 setosa   29.2 15.2 
     7          4.6         3.4          1.4         0.3 setosa   21.2 11.6 
     8          5           3.4          1.5         0.2 setosa   25   11.6 
     9          4.4         2.9          1.4         0.2 setosa   19.4  8.41
    10          4.9         3.1          1.5         0.1 setosa   24.0  9.61
    # ... with 140 more rows