【问题标题】:How to define multiple variables with lapply?如何用 lapply 定义多个变量?
【发布时间】:2018-07-13 21:35:12
【问题描述】:

我想将具有多个具有不同值的变量的函数应用于列表。我知道如何用一个不断变化的变量来做到这一点

sapply(c(1:10), function(x) x * 2)
# [1]  2  4  6  8 10 12 14 16 18 20

但不是两个。我首先手动向您展示我想要的(实际上我使用 lapply()sapply() 在 SO 中更概括):

# manual
a <- sapply(c(1:10), function(x, y=2) x * y)
b <- sapply(c(1:10), function(x, y=3) x * y)
c <- sapply(c(1:10), function(x, y=4) x * y)
c(a, b, c)
# [1]  2  4  6  8 10 12 14 16 18 20  3  6  9 12 15 18 21 24 27 30  4  8 12 
# [24]  16 20 24 28 32 36 40

这是我尝试定义xy 的尝试。

# attempt
X <- list(x = 1:10, y = 2:4)
sapply(c(1:10, 2:4), function(x, y) x * y)
# Error in FUN(X[[i]], ...) : argument "y" is missing, with no default

解决方案基准

library(microbenchmark)
microbenchmark(sapply = as.vector(sapply(1:10, function(x, y) x * y, 2:4)), 
               mapply = mapply( FUN = function(x, y) x * y, 1:10, rep( x = 2:4, each = 10)),
               sapply2 = as.vector(sapply(1:10, function(y) sapply(2:4, function(x) x * y))),
               outer = c(outer(1:10, 2:4, function(x, y) x * y)))
# Unit: microseconds
# expr        min       lq      mean   median       uq      max neval
# sapply   34.212  36.3500  62.44864  39.1295  41.9090 2304.542   100
# mapply   62.008  65.8570  87.82891  70.3470  76.5480 1283.342   100
# sapply2 196.714 203.9835 262.09990 223.6550 232.2080 3344.129   100
# outer     7.698  10.4775  13.02223  12.4020  13.4715   53.883   100

【问题讨论】:

    标签: r function variables lapply


    【解决方案1】:

    首先,如果你的函数是矢量化的,你可以用lapply() 来做到这一点。在这种情况下,它是:

    x <- 1:10
    unlist(lapply(2:4, function(y) x*y))
    # OR
    unlist(lapply(2:4, function(x=x,y) x*y))
    

    其次,如果您需要对两个向量的每个组合应用函数,请使用outer()

    xf <- 1:10
    yf <- 2:4
    c(xf %o% yf)
    # OR spelled out for any function:
    c(outer(xf,yf,FUN = `*`))
    

    如果你使用 mapply,你可以使用参数 MoreArgs 来避免使用 rep 来构造你的参数:

    xf <- 1:10
    yf <- 2:4
    mapply(function(x,y) x*y,
           y = yf,
           MoreArgs = list(x = xf))
    

    这与我上面展示的lapply() 构造完全等价。生成的矩阵也可以使用SIMPLIFY = FALSEunlist() 转换为向量:

    unlist(mapply(function(x,y) x*y,
                  y = yf,
                  MoreArgs = list(x = xf),
                  SIMPLIFY = FALSE))
    

    哪种解决方案最方便,取决于您的实际用例。在时间方面,它们都具有可比性,在最近的 R 版本中,outer() 可能会比其他解决方案慢一点。

    基准测试

    为了展示结果如何因对象的大小和顺序而有很大差异,我提供了以下基准测试结果(下面的代码和输出)。这表明:

    1. outer() 不一定是最快的解决方案,尽管它通常是最快的解决方案之一。
    2. mapply() 中手动重复一个向量会增加如此多的开销,以至于即使是两次sapply() 调用也会更快。

    代码:警告:这将运行一段时间

    fx <- sample(1e4)
    fy <- sample(1e3)
    library(microbenchmark)
    microbenchmark(sapply = as.vector(sapply(fx, function(x, y) x * y, fy)), 
                   mapply = mapply( FUN = function(x, y) x * y, fx, rep( fy, each = 1e4)),
                   sapply2 = as.vector(sapply(fx, function(y) sapply(fy, function(x) x * y))),
                   outer = c(outer(fx, fy, function(x, y) x * y)),
                   mapply2 = mapply(function(x,y) x*y, x=fx, MoreArgs = list(y = fy)),
                   mapply3 = mapply(function(x,y) x*y, y=fy, MoreArgs = list(x = fx)),
                   times = 15)
    

    我机器上的输出:

    Unit: milliseconds
        expr         min          lq       mean      median          uq        max neval cld
      sapply    89.52318    92.98653   344.1538   117.11280   239.64887  1485.3178    15 a  
      mapply 20471.02137 22925.42757 24478.5985 24650.29055 25627.31232 28840.3494    15   c
     sapply2  7472.02251  8268.04696  9519.8016  8707.19193  9528.46181 14182.7537    15  b 
       outer    77.62331    85.94651   189.5107    91.83722   182.08506  1119.6620    15 a  
     mapply2    77.76871    79.71924   143.9484    81.24168    84.53247   971.1792    15 a  
     mapply3    65.21709    71.85662   107.9586    73.80779   124.21141   242.0760    15 a  
    

    【讨论】:

    • 感谢您的出色回答,尽管其他人也很棒。 outer() 比其他方法快得多,我提供了一个基准作为编辑。您使用我的列表方法的实现非常好。
    • @jaySf 我也进行了基准测试,并且在较大的向量上,外部变得比其他两个慢。请注意如何进行基准测试,因为在一种情况下最快的解决方案在另一种情况下并不是最快的。我已经学会了这个艰难的方式..
    • @jaySf 我已经添加了更大对象的基准测试。在那里你可以看到正确应用的 mapply 比外部快一点。您在基准测试中忘记的部分是 MoreArgs
    【解决方案2】:

    一般解决方案

    试试outer:

    c(outer(1:10, 2:4, Vectorize(function(x, y) x*y)))
    ##  [1]  2  4  6  8 10 12 14 16 18 20  3  6  9 12 15 18 21 24 27 30  4  8 12 16 20
    ## [26] 24 28 32 36 40
    

    如果函数已经矢量化

    如果函数已经向量化,就像这里一样,那么我们可以省略Vectorize

    c(outer(1:10, 2:4, function(x, y) x * y))
    ##  [1]  2  4  6  8 10 12 14 16 18 20  3  6  9 12 15 18 21 24 27 30  4  8 12 16 20
    ## [26] 24 28 32 36 40
    

    问题中显示的特定示例

    事实上,在这种特殊情况下,显示的匿名函数是默认的,所以这会起作用:

    c(outer(1:10, 2:4))
    ##  [1]  2  4  6  8 10 12 14 16 18 20  3  6  9 12 15 18 21 24 27 30  4  8 12 16 20
    ## [26] 24 28 32 36 40
    

    在这种特殊情况下,我们也可以使用:

    c(1:10 %o% 2:4)
    ##  [1]  2  4  6  8 10 12 14 16 18 20  3  6  9 12 15 18 21 24 27 30  4  8 12 16 20
    ## [26] 24 28 32 36 40
    

    如果输入是列表 X

    如果您的出发点是问题中显示的列表X,那么:

    c(outer(X[[1]], X[[2]], Vectorize(function(x, y) x * y)))
    ##  [1]  2  4  6  8 10 12 14 16 18 20  3  6  9 12 15 18 21 24 27 30  4  8 12 16 20
    ## [26] 24 28 32 36 40
    

    c(do.call("outer", c(unname(X), Vectorize(function(x, y) x*y))))
    ##  [1]  2  4  6  8 10 12 14 16 18 20  3  6  9 12 15 18 21 24 27 30  4  8 12 16 20
    ## [26] 24 28 32 36 40
    

    如果适用,前面的部分适用于缩短它。

    【讨论】:

    • 我在 Joris 的回答下的 imy 评论中提到的列表方法是献给你的。谢谢!
    【解决方案3】:

    另一个想法是使用sapply 两次。

    as.vector(sapply(2:4, function(y) sapply(1:10, function(x) x * y)))
    [1]  2  4  6  8 10 12 14 16 18 20  3  6  9 12 15 18 21 24 27 30  4  8 12 16 20 24 28 32 36 40
    

    或者我们可以使用purrr 包中的map2_intmap2_int 可以循环遍历两个长度相同的向量,并确保输出为整数。所以我们需要使用rep(a, length(b))rep(b, each = length(a)) 来确保每个元素都是配对的。 ~.x * .y 是在purrr 中指定函数的简洁方式。

    library(purrr)
    
    a <- 1:10
    b <- 2:4
    map2_int(rep(a, length(b)), rep(b, each = length(a)), ~.x * .y)
    # [1]  2  4  6  8 10 12 14 16 18 20  3  6  9 12 15 18 21 24 27 30  4  8 12 16 20 24 28 32 36 40
    

    【讨论】:

      【解决方案4】:

      使用 mapply()

      mapply() 将函数应用于多个列表或向量参数。

      rep() 还用于重复值 2、3 和 4。在 each 参数中指定 10,rep() 将重复 x 的每个元素 10 次。

      这是必要的,因为mapply() 中的第一个参数 - 1:10 - 长度为 10。

      # supply the function first, followed by the
      # arguments in the order in which they are called in `FUN`
      mapply( FUN = function(x, y) x * y
              , 1:10
              , rep( x = 2:4, each = 10)
      )
      
      # [1]   2  4  6  8 10 12 14 16 18 20  3  6  9 12 15 18 21 24 27 30  4  8 12 16 20
      # [26] 24 28 32 36 40
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-07-26
        相关资源
        最近更新 更多