Mapping / iterating over different sized lists in R

Viewed 757

I have two different length lists and would like to generate all the permutations to run through a function in R. I can do it with a for loop (see below) but I don't like using rbind and cbind. I haven't been able to get it to work with *apply or purrr functions like map2 because they complain about unequal lengths.

What is the cleanest tidyverse-friendly way to do this?

Simplified example below:

myfun = function(a,b){
    return(a*b)
}
xvalues = c(1,2,3)
yvalues = c(10,20,30,40)

merged = c()
for (x in xvalues){
    z = myfun(x,yvalues)
    merged = rbind(merged,(cbind(x,yvalues,z)))
}

df = data.frame(merged)

This generates the desired dataframe:

   x yvalues   z
1  1      10  10
2  1      20  20
3  1      30  30
4  1      40  40
5  2      10  20
6  2      20  40
7  2      30  60
8  2      40  80
9  3      10  30
10 3      20  60
11 3      30  90
12 3      40 120
2 Answers

You can use the cross family of functions, such as cross_df to generate the product set of lists in situations where you want to iterate over all combinations. This lets you then use map functions as normal:

library(tidyverse)
myfun = function(a,b){
  return(a*b)
}
xvalues = c(1,2,3)
yvalues = c(10,20,30,40)

cross_df(list(x = xvalues, y = yvalues)) %>%
  mutate(z = map2_dbl(x, y, myfun))
#> # A tibble: 12 x 3
#>        x     y     z
#>    <dbl> <dbl> <dbl>
#>  1     1    10    10
#>  2     2    10    20
#>  3     3    10    30
#>  4     1    20    20
#>  5     2    20    40
#>  6     3    20    60
#>  7     1    30    30
#>  8     2    30    60
#>  9     3    30    90
#> 10     1    40    40
#> 11     2    40    80
#> 12     3    40   120

Of course in this case myfun is vectorised so using map is not quite necessary.

cross_df(list(x = xvalues, y = yvalues)) %>%
  mutate(z = myfun(x, y))
#> # A tibble: 12 x 3
#>        x     y     z
#>    <dbl> <dbl> <dbl>
#>  1     1    10    10
#>  2     2    10    20
#>  3     3    10    30
#>  4     1    20    20
#>  5     2    20    40
#>  6     3    20    60
#>  7     1    30    30
#>  8     2    30    60
#>  9     3    30    90
#> 10     1    40    40
#> 11     2    40    80
#> 12     3    40   120

Created on 2019-06-28 by the reprex package (v0.3.0)

With base R, we can use expand.grid

transform(expand.grid(x= xvalues, yvalues = yvalues), z = myfun(x, yvalues))
#   x yvalues   z
#1  1      10  10
#2  2      10  20
#3  3      10  30
#4  1      20  20
#5  2      20  40
#6  3      20  60
#7  1      30  30
#8  2      30  60
#9  3      30  90
#10 1      40  40
#11 2      40  80
#12 3      40 120

Or using data.table

library(data.table)
CJ(x= xvalues, yvalues)[, z := myfun(x, yvalues)][]
Related