【问题标题】:r - apply a function on data n number of timesr - 对数据应用函数 n 次
【发布时间】:2019-03-28 00:14:22
【问题描述】:

我想每次使用函数的输出在向量上应用相同的函数一定次数。

一个简单的例子,一个简单的功能只是为了演示:

# sample vector
a <- c(1,2,3)

# function to be applied n times
f1 <- function(x) {
  x^2 + x^3
 }

我想在a、n 上申请f1 的次数,例如这里说3 次。

我听说purrr::reduce 或purrr::map() 可能是一个好主意,但无法实现。

如果n = 3 的期望输出将等于f1(f1(f1(a)))。

【问题讨论】:

  • 你应该让你的函数return 一些东西。
  • @apitsch,它确实是隐含的。也就是说,如果功能块中的最后一行代码是x^2 + x^3,这相当于return(x^2 + x^3)和tmp &lt;- x^2 + x^3 ; return(tmp)。
  • @r2evans 感谢您告诉我。很高兴学到新东西。
  • BTW:@apitsch,这个可以咬你:如果函数做了部分赋值比如mtcars$cyl &lt;- mycars$cyl + 1,那么返回的值不是mtcars,而是@ 987654335@,一个常见的错误。您可以看到分配是不可见返回通过将调用包装在括号中分配的值,如(在您的控制台上)(mtcars$cyl &lt;- mycars$cyl + 1)。出于这个原因,许多人(正如您所建议的那样)认为明确的 return(...) 语句可能是一件好事。
  • @r2evans return 仅在特殊情况下才需要...在一般情况下,返回函数内计算的最后一个表达式。仅当您想跳出函数并在此之前返回一个值时,您才需要显式的 return 语句。 return 不影响返回值的“可见性/不可见性”:例如,赋值返回已赋值但这样做不可见的值。试试fun &lt;- function(x) return(a&lt;-x) -- 现在fun(3)==3 等于TRUE 但fun(3) 不会在屏幕上显示任何值。但是fun &lt;- function(x) (a&lt;-x) 的行为会有所不同。

标签: r function iteration


【解决方案1】:

我们使用Reduce(没有外部库要求,一般性能不错)。我将稍微修改函数以接受第二个(忽略的)参数:

f1 <- function(x, ign) x^2 + x^3

Reduce(f1, 1:3, init = a)
# [1] 1.872000e+03 6.563711e+09 1.102629e+14

这就是正在发生的事情。 Reduce:

使用二元函数连续组合给定向量的元素和可能给定的初始值。

第一个参数是要使用的函数,它应该接受两个参数。第一个是在此归约中previous 函数执行的值。在第一次调用该函数时,它使用提供的 init= 值。

  • 第一次通话:

    f1(c(1,2,3), 1)
    # [1]  2 12 36
    
  • 第二次通话:

    f1(c(2,12,36), 2)
    # [1]    12  1872 47952
    
  • 第三次调用:

    f1(c(12,1872,47952), 3)
    # [1] 1.872000e+03 6.563711e+09 1.102629e+14
    

第二个参数1:3 仅用于其长度。任何合适的长度都可以。

如果你不想仅仅为了这个减少而重新定义f1,你总是可以这样做

Reduce(function(a,ign) f1(a), ...)

基准测试:

library(microbenchmark)
r <- Reduce(function(a,b) call("f1", a), 1:3, init=quote(a))
triple_f1 <- function(a) f1(f1(f1(a)))
microbenchmark::microbenchmark(
  base = Reduce(function(a,ign) f1(a), 1:3, a),
  accum = a %>% accumulate(~ .x %>% f1, .init = f1(a)) %>% extract2(3),
  reduc = purrr::reduce(1:3, function(a,ign) f1(a), .init=a),
  whil = { 
    i <- 1
    a <- c(1,2,3)
      while (i < 10) {
        i <- i + 1
        a <- f1(a)
      }
    },
  forloop = {
    out <- a
    for(i in seq_len(3)) out <- f1(out)
  },
  evaluated = {
    r <- Reduce(function(a,b) call("f1", a), 1:3, init=quote(a))
    eval(r)
  },
  precompiled = eval(r),
  anotherfun = triple_f1(a)
)
# Unit: microseconds
#         expr      min        lq       mean    median        uq      max neval
#         base    5.101    7.3015   18.28691    9.3010   10.8510  848.302   100
#        accum  294.201  328.4015  381.21204  356.1520  402.6510  823.602   100
#        reduc   27.000   38.1005   57.55694   45.2510   54.2005  747.401   100
#         whil 1717.300 1814.3510 1949.03100 1861.8510 1948.9510 2931.001   100
#      forloop 1110.001 1167.1010 1369.87696 1205.5010 1292.6500 9935.501   100
#    evaluated    6.702   10.2505   22.18598   13.3015   15.5510  715.301   100
#  precompiled    2.300    3.2005    4.69090    4.0005    4.5010   26.800   100
#   anotherfun    1.400    2.0515   12.85201    2.5010    3.3505 1017.801   100

【讨论】:

  • 绝对是我要找的东西
【解决方案2】:
i <- 1

while (i < 10) {
  i <- i + 1
  x <- f(x)
}

【讨论】:

    【解决方案3】:

    这是accumulate的选项

    library(tidyverse)
    n <- 3
    a %>% 
      accumulate(~ .x %>%
                     f1, .init = f1(a)) %>%
      extract2(n)
    #[1] 1.872000e+03 6.563711e+09 1.102629e+14
    

    注意:accumulate 类似于 base R 选项 Reduce 和 accumulate = TRUE

    检查 OP 的输出

    f1(f1(f1(a)))
    #[1] 1.872000e+03 6.563711e+09 1.102629e+14
    

    或者使用for 循环(不使用外部库)

    out <- a
    for(i in seq_len(n)) out <- f1(out)
    out
    #[1] 1.872000e+03 6.563711e+09 1.102629e+14
    

    【讨论】:

    • 可以在不保留中间结果的情况下做到这一点。 purrr::map 可能吗?
    • @adl map 将函数分别应用于每个元素,而不是递归地将函数应用于每个元素的输出
    • 它位于magrittr。谢谢你的回答
    • 你不能用accumulate %&gt;% extract2代替purrr::reduce吗?
    • @r2evans 我知道reduce 在这种情况下更好。我正在考虑获取vectors 的`list`,然后OP可以决定是选择一个元素还是多个
    【解决方案4】:

    这是使用Reduce 的另一种方法:

    搭建舞台

    a <- 1:3
    f1 <- function(x) x^2 + x^3
    

    构造调用并评估它

    N <- 3   # how many times?
    r <- Reduce(function(a,b) call("f1", a), rep(NA, N), init=a)
    # f1(f1(f1(1:3)))
    eval(r)
    # [1] 1.872000e+03 6.563711e+09 1.102629e+14
    

    备选方案 2

    # N defined as above
    Reduce(function(x,y) y(x), replicate(N,f1), init=a)
    # [1] 1.872000e+03 6.563711e+09 1.102629e+14
    

    替代方案 3(使用类全局变量递归)

    doit <- function(N) {
      i <- 0
      function(fun, x){
        i <<- i +1
        if(i < N) Recall(fun, fun(x)) else fun(x)
      }
    }
    doit(3)(f1, a)
    # [1] 1.872000e+03 6.563711e+09 1.102629e+14
    

    ...甚至

    doit <- function(N, fun, x) (function(fun, x) 
        if((N <<- N - 1) > 0) 
          Recall(fun, fun(x)) else 
            fun(x))(fun, x)
    doit(3, f1, a)
    # [1] 1.872000e+03 6.563711e+09 1.102629e+14
    

    【讨论】:

    • 您正在利用向量长度和重复次数相同的事实,我没有从 OP 中假设。
    • 这是另一种方式Reduce(function(a,b) get("f1")(a), a, init = a)
    猜你喜欢
    • 2016-05-15
    • 2013-02-23
    • 1970-01-01
    • 2021-05-13
    • 2017-04-30
    • 1970-01-01
    • 2015-12-12
    • 1970-01-01
    • 2018-03-18
    相关资源
    最近更新 更多