purrr map嵌套for循环的等效方法

44

什么是purrr :: map的等效操作:

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

从概念上讲,我不明白如何引用内部map函数中的外部循环。

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

5
outer(1:4, 1:6, paste, sep = '-') 会生成一个漂亮的矩阵。 cross2(1:4, 1:6) %>% map_chr(paste, collapse = '-') 会生成一个字符向量。 - alistaire
对于一个字符向量,可以使用do.call(paste, c(expand.grid(1:4, 1:6), sep = '-'))来进行转换。 - alistaire
1
我发现在连续使用”purrr”映射式函数之间使用 %>% 可以提高可读性。即将一次映射的输出列表作为另一个映射函数的输入列表。 - Kresten
4个回答

47
函数公式(~在尝试嵌套时受到了一定限制,因为不清楚你正在尝试引用哪个级别的map。(好吧,这并不正确。对于我来说,内部引用是完全清晰的,并且由于它们都使用相同的命名约定,外部变量被内部变量掩盖。)
我认为你可以通过不使用公式方法,而是使用立即/匿名(或预定义的)函数来解决它的最佳方式:
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"

3
你还可以引用一个函数并将函数名和参数分别传递给另一个函数:map(1:4, ~map_chr(1:6, paste, .x, sep = '-')) - alistaire
好观点,当其中一个级别可以是字面值时,效果要好得多。谢谢! - r2evans

30

正如@r2evans所指出的那样,你第一次调用中的.x已被屏蔽。但是你可以创建一个接受两个参数.x.y的lambda函数,并通过...参数将以前的.x分配给新的.y

在这种情况下,我会使用walk而不是map,因为你只对副作用(打印)感兴趣。

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

另一种选择是使用expand.grid来排列组合,然后使用pwalk(或在其他情况下使用pmap)迭代这些组合。

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] "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"

5

我现在正在快速浏览这个内容。

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"

并且。
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"

但是为了完全匹配你的嵌套for循环,它进行了微调,现在可以正常工作。

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))

我还没搞清楚为什么.y=.x在最后。


我认为它可能是偶然的,它能够工作。我的猜测是,使用 ~ 创建一个函数会寻找可能被识别的参数(., .x, .y, ..1等),并且最后一个参数(.y=.x)在某种程度上将外部环境中的 .x 设置为内部环境中的 .y。你可以用 ..1 替换 .y,但不能用其他变量替换。 - Brian Diggs
使用公式符号就像创建一个形式为 function (..., .x = ..1, .y = ..2, . = ..1) {} 的函数,因此您可以通过 walk 调用将参数传递给 .y,并且您可以将其传递给外部 walk 调用的 .x,因为此时没有被任何东西覆盖,这不是巧合! - moodymudskipper

0

这里是对已经非常好的答案和回答评论的补充。我想要创建一个单一的类似猫咪般的函数来实现原帖的目标。因此,我创建了一个loop_map函数,其行为类似于主要的Purrr map函数。

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)

作为奖励,我给输出命名了,这样就更容易索引它,或者以某种方式将其转换为数据框。我想通过添加循环遍历任意多个输入列表的能力,并可能使用需要...参数的函数(现在一切都必须命名)来改进此函数。如果有人有这方面的想法,请随时让我知道。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接