merov
merov

Reputation: 453

purrr map equivalent of nested for loop

What is the purrr::map equivalent of:

for (i in 1:4) {
  for (j in 1:6) {
    print(paste(i, j, sep = "-"))
  }
}

OR

lapply(1:4, function(i) 
  lapply(1:6, function(j) 
    print(paste(i, j, sep = "-"))))

Conceptually, what I'm not getting is how to refer to the outer loop in the inner map function.

map(1:4, ~ map(1:6, ~ print(paste(.x, ????, sep = "-")))

Upvotes: 45

Views: 13811

Answers (4)

Jordan Mandel
Jordan Mandel

Reputation: 498

Here is an addition to the already very good answers and answer-comments. I wanted to make a single purr-like function that accomplishes the OP's goals. So I made a loop_map function that behaves analogously to the main Purrr map functions.

loop_map <- function(why, ecks, fun) {
  
  # 2: for every call of this (the previous .f) the new .f is called for each
  # value of ecks, supplied the same value of why each time
  iterate_over_x = function(x_in,y_in,fun_in){
    return(pmap(.l = list(x = x_in), .f = fun_in ,y = y_in ) %>%
 set_names(nm = as.character(x_in))) 
  }
  
  # 1: this ".f"  argument is called once for each element of why, and is 
  # supplied one value of why and every value of ecks each time
  pmap(.l = list(y_in = why), .f = iterate_over_x, x_in = ecks, fun_in = fun) %>% 
set_names(nm = as.character(why))
  
}



my_paste <- function(x,y) {
  paste(x,y)
}



loop_map(list("a","b"),list("c","d"),my_paste)

As a bonus I named the output so that one can index it more easily, or somehow convert it to a dataframe. I would like to improve this function by adding capabilities to loop over arbitrarily many input lists, and possibly to use functions that take ... arguments (right now everything has to be named). If anyone has an idea for how to do this feel free to let me know.

Upvotes: 0

Shadowhawk
Shadowhawk

Reputation: 339

Just Running through this now.

walk(1:4,~ walk(1:6, ~ print(paste(.x, .y, sep = "-")),.y=.x)) 
[1] "1-1"
[1] "2-1"
[1] "3-1"
[1] "4-1"
[1] "5-1"
[1] "6-1"
[1] "1-2"

and

purrr::pwalk(expand.grid(1:4,1:6),~print(paste(.x, .y, sep = "-")))
[1] "1-1"
[1] "2-1"
[1] "3-1"
[1] "4-1"
[1] "1-2"

but to match your nested for loops exactly it fiddled and this works.

for (i in 1:4) {
  for (j in 1:6) {
    print(paste(i, j, sep = "-"))
  }
}
[1] "1-1"
[1] "1-2"
[1] "1-3"
[1] "1-4"
[1] "1-5"
[1] "1-6"
[1] "2-1"

purrr::pwalk(expand.grid(1:6,1:4),~print(paste(.y, .x, sep = "-")))
[1] "1-1"
[1] "1-2"
[1] "1-3"
[1] "1-4"
[1] "1-5"
[1] "1-6"
[1] "2-1"

#or even a map of this
walk(1:4,~ walk(1:6, ~ print(paste(.y, .x, sep = "-")),.y=.x))

I have yet to figure out why the .y=.x is at the end though.

Upvotes: 5

moodymudskipper
moodymudskipper

Reputation: 47300

As @r2evans points out, the .x from your first call is masked. however you can create a lambda function that takes 2 parameters .x and .y, and assign the previous .x to the new .y through the ... argument.

I'll use walk rather than map as in this case you're only interested in side effects (printing)

walk(1:4,~ walk(1:6, ~ print(paste(.x, .y, sep = "-")),.y=.x))

Another option is to use expand.grid to lay out the combinations, and then iterate on those with pwalk (or pmap in other circumstances)

purrr::pwalk(expand.grid(1:4,1:6),~print(paste(.x, .y, sep = "-")))

Output in both cases:

[1] "1-1"
[1] "2-1"
[1] "3-1"
[1] "4-1"
[1] "5-1"
[1] "6-1"
[1] "1-2"
[1] "2-2"
[1] "3-2"
[1] "4-2"
[1] "5-2"
[1] "6-2"
[1] "1-3"
[1] "2-3"
[1] "3-3"
[1] "4-3"
[1] "5-3"
[1] "6-3"
[1] "1-4"
[1] "2-4"
[1] "3-4"
[1] "4-4"
[1] "5-4"
[1] "6-4"

Upvotes: 30

r2evans
r2evans

Reputation: 160407

The use of function formulas (~) is a little limited when trying to nest like this, since it is perfectly unclear which level of map you are attempting to reference. (Well, that's not correct. It's perfectly clear to me that it is referencing inside-out, and since they both use the same nomenclature, the outer variables are being masked by the inner variables.)

I think your best way around it is to not use the formula method, instead using immediate/anonymous (or predefined) functions:

library(purrr)
str(map(1:2, function(x) map(1:3, function(y) paste(x, y, sep = "-"))))
# List of 2
#  $ :List of 3
#   ..$ : chr "1-1"
#   ..$ : chr "1-2"
#   ..$ : chr "1-3"
#  $ :List of 3
#   ..$ : chr "2-1"
#   ..$ : chr "2-2"
#   ..$ : chr "2-3"

Upvotes: 47

Related Questions