使用purrr将函数映射到嵌套列表的第二层

14

我想更好地理解R中的函数式编程。 我想使用 purrr ,但我将使用 rapply 来演示我想要理解的内容。 首先,以下是一个简单的例子:

您可以使用 map 获取 mtcars 数据集每列的平均值:

library(tidyverse)
mtcars %>% map_dbl(mean)

   mpg        cyl       disp         hp       drat         wt       qsec  
 20.090625   6.187500 230.721875 146.687500   3.596563   3.217250  17.848750 
    vs         am       gear       carb 
 0.437500   0.406250   3.687500   2.812500 

我该如何使用purrrmean映射到按cyl分割的mtcars数据集上?

library(tidyverse)
mtcars_split <- mtcars %>% split(.$cyl) 
mtcars_split %>% map(mean)
$`4`
[1] NA

$`6`
[1] NA

$`8`
[1] NA

Warning messages:
1: In mean.default(.x[[i]], ...) :
  argument is not numeric or logical: returning NA
2: In mean.default(.x[[i]], ...) :
  argument is not numeric or logical: returning NA
3: In mean.default(.x[[i]], ...) :
  argument is not numeric or logical: returning NA

我理解为什么这不起作用:split会创建一个列表,现在我正在尝试将mean map到该新列表的每个元素上,这些元素是data.frame。 这次mapping的尝试相当于(如果需要,请更正我):

mean(mtcars_split[1])
mean(mtcars_split[2])
mean(mtcars_split[3])

显然这样是不起作用的 - 你不能仅仅对一个 data.framemean。我真正想要的是做到这一点的东西:

mtcars_split[[1]] %>% map(mean)
mtcars_split[[2]] %>% map(mean)
mtcars_split[[3]] %>% map(mean)

问题是,我只是无法理解如何在purrr 中实现这一点。在寻找解决此问题(看起来非常基础)的方法时,我发现了rapply,它似乎可以实现我想要的功能,但是不属于 purrr 的范畴(而且输出的格式也不完全符合我的要求,但那不重要):

rapply(mtcars_split, mean, how = "unlist")
      4.mpg       4.cyl      4.disp        4.hp      4.drat        4.wt 
 26.6636364   4.0000000 105.1363636  82.6363636   4.0709091   2.2857273 
     4.qsec        4.vs        4.am      4.gear      4.carb       6.mpg 
 19.1372727   0.9090909   0.7272727   4.0909091   1.5454545  19.7428571 
  6.cyl      6.disp        6.hp      6.drat        6.wt      6.qsec 
  6.0000000 183.3142857 122.2857143   3.5857143   3.1171429  17.9771429 
       6.vs        6.am      6.gear      6.carb       8.mpg       8.cyl 
  0.5714286   0.4285714   3.8571429   3.4285714  15.1000000   8.0000000 
     8.disp        8.hp      8.drat        8.wt      8.qsec        8.vs 
353.1000000 209.2142857   3.2292857   3.9992143  16.7721429   0.0000000 
       8.am      8.gear      8.carb 
  0.1428571   3.2857143   3.5000000 
rapply 递归运用显然是我的答案关键-我认为我需要嵌套的map——一个用于提取mtcars_split中三个data.frame的每一列,然后另一个用于对每个提取的列运行mean。然而,我还没有能够使它起作用。 我认为Jenny Bryan在她的purrr教程中已经解决了这个问题,她使用了一个map()在一个map()内,但我无法理解她在做什么。她指出这个例子可能在教程早期没有充分解释,我已经在这里要求她详细说明,但还没有得到答复(我知道她很忙!)。

你可以使用 dplyrgroup_by - F. Privé
2
mtcars %>% split(.$cyl) %>% map(map, mean) - missuse
3
如果你正在使用 data.frames,那么你应该只在列表列中使用 purrr 进行操作;否则,通常有更好的方法可以使用 dplyr 和 tidyr 进行操作,例如 mtcars %>% group_by(cyl) %>% summarise_all(mean)。然而,有时候你需要迭代遍历嵌套列表的第二层级别,这时你可以使用 modify_depth。将 map 放在 map 内部是可能的,但如果你使用缩写的 ~/.x 样式的匿名函数,可能会让人感到困惑。 - alistaire
1
当然可以,但是在你使用split之后,它会变成一个由三个数据框组成的列表。这可能不是一个完美的例子,但它展示了我想要理解的内容。 - twgardner2
1
是的,我理解了。我问这个问题并不是为了处理数据框架。就像我上面说的,我非常习惯在矩形数据结构上使用dplyr来完成这个任务。mtcars仅仅是为了演示这个问题。我想知道如何在实际嵌套列表中工作。与其将我正在处理的内容强制转换为最小化的工作示例,我想知道如何映射到在cyl上分割mtcars后得到的嵌套数据框。谢谢你指向modify_depth。我会去看看的。 - twgardner2
显示剩余4条评论
1个回答

44
这类问题的解决方案总是相同的:
将问题拆解,针对单个情况进行解决,然后从内向外重新组合。
正如您所观察到的,mtcars %>% split(.$cyl)会给您提供一个列表,其中包含了多个列表(即数据框的列表)。您需要在内部列表上执行mean函数。
因此,我们首先针对一个列表来操作:
mtcars_split[[1]] %>% map_dbl(mean)
# Or, equivalently:
map_dbl(mtcars_split[[1]], mean)

这个方案可行。我们已经将问题分解并成功地解决了一个单独的案例:换句话说,给定一个列表x和一个转换f,我们通过执行f(x[[1]])(相当于x[[1]] %>% f())为x[[1]]解决了该问题。

现在是将其推广到所有情况的时候了。我们已经知道如何将元素x[[1]]的变换推广到整个列表x中:在该列表上使用map

x %>% map(~ .x %>% f())
# or, equivalently:
x %>% map(~ f(.x))
# or, equivalently:
map(x, ~ f(.x))
# or, finally:
map(x, f)

让我们用mtcars_splitmap_dbl(mean)替换xf,并且执行完全相同的操作

mtcars_split %>% map(~ .x %>% map_dbl(mean))
# or, equivalently:
mtcars_split %>% map(~ map_dbl(.x, mean))

这个可以像上面的例子一样简化:

mtcars_split %>% map(map_dbl, mean)

谢谢您的解释,@Konrad。我一直在尝试嵌套映射,这样分解真的很有帮助。请问能否详细说明如何将 map(map_dbl, mean) 等效于 map(~ map_dbl(.x, mean))?我不确定我是否理解了它。 - user51462
3
仅仅因为purrr包的函数明确支持两者,它们才是等效的。否则它们并不等效。purrr的map函数会检查第一个参数的类型以确定该如何处理。如果它是一个函数(就像第二个例子中那样),则定义为map(xs, f, args)map(xs, function (x) f(x, args))相同。如果第一个参数是一个公式,则它将公式转换为等效的函数调用,即map(xs, ~ f(.x, args))再次相同。 - Konrad Rudolph

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