将函数列表应用于值列表

Posted

技术标签:

【中文标题】将函数列表应用于值列表【英文标题】:Apply list of functions to list of values 【发布时间】:2015-08-25 21:13:00 【问题描述】:

参考 this question,我试图找出的最简单方法。基本上,一个嵌套的lapply。例如,这里我们将sdmean应用到内置数据集trees

funs <- list(sd=sd, mean=mean)
sapply(funs, function(x) sapply(trees, x))

得到:

              sd     mean
Girth   3.138139 13.24839
Height  6.371813 76.00000
Volume 16.437846 30.17097

但我希望避免内部 function 并有类似的东西:

sapply(funs, sapply, X=trees)

这不起作用,因为 X 匹配第一个 sapply 而不是第二个。我们可以使用functional::Curry

sapply(funs, Curry(sapply, X=trees))

但我希望也许有一种聪明的方法可以通过我缺少的位置和名称匹配来做到这一点。

【问题讨论】:

hadley 就这个话题写了一整章:adv-r.had.co.nz/Functional-programming.html#lists-of-functions,因为我并不比他聪明,所以我知道没有更好的方法了 不是更简单,但如果你想要一个整洁的 data.frame 最后很好:library(purrr) ; map_df(funs, ~map_df(trees, .x), .id = 'statistic') 【参考方案1】:

由于mapply 使用省略号... 来传递向量(原子或列表)而不是sapply, lapply, etc ... 中的命名参数(X),如果您使用mapply 代替,则不需要命名参数X = trees sapply :

funs <- list(sd = sd, mean = mean)

x <- sapply(funs, function(x) sapply(trees, x))

y <- sapply(funs, mapply, trees)

> y
              sd     mean
Girth   3.138139 13.24839
Height  6.371813 76.00000
Volume 16.437846 30.17097
> identical(x, y)
[1] TRUE

您只需一封信就能得到您想要的东西! :)

请注意,我使用了funs 的列表,因为我无法创建函数数据框,因此出现错误。

> R.version.string
[1] "R version 3.1.3 (2015-03-09)"

【讨论】:

很聪明,以后一定会用这个;我认为关键特性是mapply 恰好接受函数参数作为第一个参数,所以这一切都有效。【参考方案2】:

您基本上需要某种匿名函数,因为没有其他方法可以将命名参数与两个不同的sapply 调用区分开来。您已经展示了一个显式匿名函数和Curry 方法。你也可以使用magrittr

 library(magrittr)
 sapply(funs, . %>%  sapply(trees, .))
 # or .. funs %>% sapply(. %>%  sapply(trees, .))

但关键是你需要 something 在那里进行拆分。 “问题”是sapply 调度到lapply,这是一个internal function,似乎决定将变化的值作为函数调用的开始。您需要一些东西来重新排序参数,并且由于参数名称集相同,如果没有帮助函数来处理歧义,就不可能将其分开。

mapply 函数确实允许您将列表传递给“MoreArgs”,从而可以绕过命名参数冲突。这旨在将您应该矢量化的项目与固定的项目分开。这样就可以了

mapply(sapply, funs, MoreArgs=list(X=trees))
#               sd     mean
# Girth   3.138139 13.24839
# Height  6.371813 76.00000
# Volume 16.437846 30.17097

【讨论】:

不错的一个 MoreArgs。我猜magrittr 可能是funs %&gt;% sapply(. %&gt;% sapply(X=trees))?看到. 作为管道中的第一个元素,肯定有点双重考虑。 是的,我也添加了这一点,尽管我认为第一个版本更清晰。老实说,我认为最好的方法就是像第一次那样使用显式匿名函数:sapply(funs, function(x) sapply(trees, x)) 同意;我再次编辑以删除额外的.,但不是 100% 确定我遵循自己的逻辑... @BrodieG 干得好,摆脱了多余的.。它可以工作并且看起来“简单”,但我个人认为程序员可能很难快速阅读和理解它在做什么。 sapply 电话自然有一定的节奏,这打破了。但对每个人来说都是他自己的。【参考方案3】:

使用purrr 的另一种方法是:

require(purrr)

funs <- list(sd=sd, mean=mean)
trees %>% map_df(~invoke_map(funs, ,.), .id="id")

重要提示:注意invoke_map 的第二个空参数以按位置匹配。请参阅?purrr::invoke_map 示例。

给你:

Source: local data frame [3 x 3]

      id        sd     mean
   <chr>     <dbl>    <dbl>
1  Girth  3.138139 13.24839
2 Height  6.371813 76.00000
3 Volume 16.437846 30.17097

这种方法为您提供包含原始列的列 id,而不是行名。

【讨论】:

当使用 purrr 0.2.2(可能还有更早的版本——我还没有检查过)时,需要使用invoke_map_df(),而不是invoke_map(),才能得到显示的结果. @egnha,这很奇怪。对我来说,它适用于 purrr_0.2.2。使用invoke_map_df 会导致Error: cannot convert object to a data frame...您使用的是什么版本的R? 这令人费解。我正在使用 R 3.3.0;在一个新的会话中运行代码,只使用 purrr(并且 R 没有加载任何初始化文件)。从道德上讲,invoke_map_df 是正确应用的invoke_map*(并且在我的机器上正常工作),因为map_df 通过绑定行来创建数据框(除非我误解了某些内容)。 您使用的是什么版本的 dplyr? (map_df 使用 dplyr::bind_rows【参考方案4】:

虽然没有@Floo0 提出的解决方案那么有启发性也没有那么优雅,但这是使用tidyr 和dplyr 的另一种方法:

library(dplyr)
library(tidyr)

fns <- funs(sd = sd, mean = mean)
trees %>% 
    gather(property, value, everything()) %>% 
    group_by(property) %>% 
    summarise_all(fns)

#   A tibble: 3 x 3
#   property        sd     mean
#      <chr>     <dbl>    <dbl>
# 1    Girth  3.138139 13.24839
# 2   Height  6.371813 76.00000
# 3   Volume 16.437846 30.17097

这一系列操作在传达意图方面做得不错,但代价是额外的冗长。

【讨论】:

以上是关于将函数列表应用于值列表的主要内容,如果未能解决你的问题,请参考以下文章

为什么内置函数abs()不能用于Python列表,但却能正确地用于NumPy数组和pandas数列(因为它会被向量化)?

pandas使用isin函数和all函数判断dataframe特定数列中是否包含指定列表中的全部内容

pandas使用isin函数和any函数判断dataframe特定数列中是否包含至少一个指定列表中的内容

R中带有运算符的用户指定函数

如何将字典键应用于值项目到火花数据集中的列?

4.14 每日一练