在我的工作中,我总是用R编写用户定义的函数,如下所示:

f <- function(x){
  x ^ 2
}

f(10)
# [1] 100

我最近遇到了在R中调用函数的另一种方法:

(function(x) x ^ 2)(10)

[1] 100

我不确定发生了什么,所以经过一番搜索,我找到了a wonderful answer provided by Allan Cameron个像我这样的非程序员都能理解的东西:

R解析器将其识别为意思是"使用这些参数调用函数".

这澄清了我对what's的理解,但不是why,或者我是否应该 Select 一种语法而不是另一种(除了个人偏好).

我经常在各种模拟和模型中使用UDF,这些模拟和模型会生成大量数据,有时还会运行一段时间,所以我总是希望进行优化.除了只是一种替代语法之外,我还想看看是否有功能、编程或基于机器的理由来编写一种格式而不是另一种格式.在比较了一些简单的函数(如下所示)之后,似乎我的"通常"编写方式(f <- function(x)(...))在几种类型的简化函数上要快得多,对于这些简单的例子,速度大约是两倍.

除了句法/个人偏好之外,is there a reason or a use-case/relevant example of when the "new-to-me" way of writing a function (100) would be superior to the "usual" way (101?

换句话说:为什么这个选项存在/为什么有人会使用这个语法?

searchinga fewwaysthisthisthisthis之后,我找不到任何东西--事实上,我在网上几乎找不到关于"替代"语法的任何东西.


比较

# Function 1
f1 <- function(x) {
  x <- as.numeric(x)
  x[x < 10] <- x[x < 10] ^ 2 / pi
  x
}

# Function 2
f2 <- Vectorize(function(x) {
  paste0("num_", 1:x)
  })

# Function 3
f3 <- Vectorize(function(x){
  if(x < 10)
    x < x + 1
    while(x <10) {
      x <- x+1
    }
  x
})

比较

microbenchmark::microbenchmark(
  `f1` =  f1(c("10", 20, "5")),
  `(function (x)(f1))` = (function(x) {
                            x <- as.numeric(x)
                            x[x < 10] <- x[x < 10] ^ 2 / pi
                            x
                          })(c("10", 20, "5")),
  `f2` =  f2(c(5,10)),
  `(function (x)(f2))` = Vectorize((function(x) paste0("num_", 1:x)))(c(5,10)),
  `f3` = f3(1:15),
  `(function (x)(f3))` = Vectorize((function(x){
                            if(x < 10)
                              x < x + 1
                               while(x <10) {
                                  x <- x+1
                                  }
                             x}))(1:15),
  times = 1e4
)

结果

Unit: microseconds
               expr    min      lq      mean  median      uq      max neval
                 f1  2.446  3.9700  5.220236  5.1720  6.0460  113.914 10000
 (function (x)(f1))  3.270  5.3725  6.741182  6.6260  7.7385   46.308 10000
                 f2 26.388 30.1885 34.328340 32.7105 37.1725  227.455 10000
 (function (x)(f2)) 53.808 60.6005 71.588443 65.7905 75.2055 5997.770 10000
                 f3 30.294 34.5735 42.077120 37.5160 42.4010 6121.705 10000
 (function (x)(f3)) 58.492 65.1845 78.551417 70.5040 80.6610 6945.062 10000

推荐答案

这是一个匿名函数,正如 comments 中提到的那样.关于这一点,有几点:

  • "命名函数"只是一个分配给对象的匿名函数.就像pi是对象一样,mean(函数)也是对象.(致谢:@G.Grothendieck)变量符号(pimean)只是对对象在内存中存储位置的引用(松散汇总),不多也不少.

  • 你有没有见过sapply或类似的词被用在下面的用法上?

    sapply(mtcars, function(z) sum(z %in% c(4,6,21)))
    

    在这种情况下,可以很容易地将anon-func存储在变量中并在那里使用:

    func <- function(z) sum(z %in% c(4,6,21))
    sapply(mtcars, func)
    #  mpg  cyl disp   hp drat   wt qsec   vs   am gear carb 
    #    2   18    0    0    0    0    0    0    0   12   11 
    

    我们可以以相同的方式使用该命名函数和无名函数:

    func(mtcars$cyl)
    # [1] 18
    (function(z) sum(z %in% c(4,6,21)))(mtcars$cyl)
    # [1] 18
    
  • New to R是定义函数的一种稍微小一些的(代码高尔夫)方式:

    (\(z) sum(z %in% c(4,6,21)))(mtcars$cyl)
    # [1] 18
    

    这两种方法(function(x) ...\(x) ...)是相同的,两者都没有优势(除了我的肌肉记忆更难快速地打出\(x)).

  • 您的基准测试对我来说是有意义的:当您使用匿名函数时,执行时间中包含的是解析该函数的时间.每次基准测试执行带有anon-func的表达式时,它都会解析函数体,(在幕后)将其存储在new内存位置中,然后执行它.这里没有有效的机制来检测它正在解析的表达式与之前的表达式是否相同,因此每次解析特定的表达式时,它都被解析为"新的"并存储为"唯一的",尽管它的解析产生的结果与基准测试中最后n个复制的解析相同.

    当您使用命名函数(f1(..))时,因为f1的解析发生在once并且在基准测试之外,所以解析时间永远不会进入计时.

    当在sapply内使用anon-func时,它被解析一次,然后在that callsapply的每次执行中重复使用.例如,如果我调用

    sapply(mtcars, function(z) sum(z %in% c(4,6,21)))
    sapply(mtcars, function(z) sum(z %in% c(4,6,21)))
    

    虽然我们可以看到anon-func在这两种情况下都是相同的,但是每次调用sapply都会导致anon-func被解析两次,每sapply次解析一次.在这样的情况下,将函数定义为变量并在sapply中将其调用为命名函数可以节省micro的成本.在这种情况下说"微"有点夸张……考虑到其他方面的情况,我认为我们不能轻易衡量执行时间的显著差异,但...

    bench::mark(
      anon  = sapply(mtcars, function(z) sum(z %in% c(4,6,21))), 
      named = sapply(mtcars, func),
      iterations = 100000
    )
    # # A tibble: 2 × 13
    #   expression      min   median `itr/sec` mem_alloc `gc/sec` n_itr  n_gc total_time result     memory     time       gc      
    #   <bch:expr> <bch:tm> <bch:tm>     <dbl> <bch:byt>    <dbl> <int> <dbl>   <bch:tm> <list>     <list>     <list>     <list>  
    # 1 anon           27µs     32µs    29343.   12.92KB     9.39 99968    32      3.41s <int [11]> <Rprofmem> <bench_tm> <tibble>
    # 2 named        27.1µs   32.6µs    28943.    3.27KB     8.98 99969    31      3.45s <int [11]> <Rprofmem> <bench_tm> <tibble>
    

    这里的anon=项措施slightly faster这一事实既有点令人费解,也可能证实了这种情况下节省的时间如此之少,以至于与其他因素相比相形见绌.

R相关问答推荐

x[[1]]中的错误:脚注越界

从开始时间和结束时间导出时间

如何自定义Shapviz图?

pickerInput用于显示一条或多条geom_hline,这些线在图中具有不同 colored颜色

在使用ggroove后,将图例合并在gplot中

如何编辑gMarginal背景以匹配绘图背景?

一小时满足条件的日期的 Select

从所有项的 struct 相同的两级列表中,将该第二级中的所有同名项绑定在一起

TreeNode打印 twig 并为其上色

在带有`R`中的`ggmosaic`的马赛克图中使用图案而不是 colored颜色

调换行/列并将第一行(原始数据帧的第一列)提升为标题的Tidyr类似功能?

如何筛选截止年份之前最后一个测量年度的所有观测值以及截止年份之后所有年份的所有观测值

使用ggplot2绘制具有边缘分布的坡度图

R中的Desolve:返回的导数数错误

把代码写成dplyr中的group_by/摘要更简洁吗?

使用显式二元谓词子集化sfc对象时出错

将字符变量出现次数不相等的字符框整形为pivot_wider,而不删除重复名称或嵌套字符变量

基于已有ID列创建唯一ID

使用条件格式R替换字符串中的字符

将一个二次函数叠加到一个被封装为facet的ggplot2对象的顶部