【问题标题】:In R, apply a function to the rows of a data frame and return a data frame在 R 中,将函数应用于数据框的行并返回数据框
【发布时间】:2018-04-06 16:07:05
【问题描述】:

我正在尝试将自写函数应用于数据框的行。

library(dplyr) # only used for data_frame
DF = data_frame(x = c(50, 49, 20), y = c(132, 124, 130), z = c(0.82, 1, 0.63))

     x     y     z
   <dbl> <dbl> <dbl>
1    50   132  0.82
2    49   124  1.00
3    20   130  0.63

实际数据框有数千行,这只是一个示例。

我的函数非常复杂并且做了很多事情,最后我为 DF 的每一行得到一个新行。 假设为简单起见,该函数将 1 添加到第 1 列,将 2 添加到第 2 列,将 3 添加到第 3 列(这当然可以向量化,但是我的函数,我们称之为 Funct,可以做更多的事情)。 所以:

Funct = function(DF) {
   DF[1]= DF[1]+1
   DF[2] = DF[2]+2
   DF[3] = DF[3]+3
   return(DF)
}

我如何以最有效的方式应用此功能以最终获得带有输出的新数据框:

> DF
     x     y     z
   <dbl> <dbl> <dbl>
1    51   134  3.82
2    50   126  4.00
3    21   132  3.63

【问题讨论】:

  • 如果所有数据框都是同一类型,为什么不使用矩阵?我相信您的问题是XY Problem。在简化您的实际用例时,您忽略了重要的细节。

标签: r dataframe vectorization


【解决方案1】:

apply 对于数据帧来说是一个糟糕的选择,因为它是为矩阵设计的,因此会在迭代之前将数据帧输入强制转换为矩阵。除了偶尔会进行昂贵的转换(之后必须反转)之外,真正的问题是 R 中的矩阵只能处理一种类型,而数据帧可以为每个变量具有不同的类型。因此,虽然它可以很好地处理这里的数据,但是当数字被强制转换为字符时,您通常会在看不到的矩阵中发生类型强制,因为另一列是一个因素。如果您真的想使用apply,请事先明确强制转换为矩阵,这样您就可以看到它正在处理什么,并且您将避免很多烦人的错误。

但有比apply 更好的选择:相反,并行迭代变量(列),然后将结果列表强制返回到数据框。 purrr::pmap_dfr 将处理这两个部分:

library(tidyverse)

DF = data_frame(x = c(50, 49, 20), 
                y = c(132, 124, 130), 
                z = c(0.82, 1, 0.63))

DF %>% 
    pmap_dfr(~list(x = ..1 + 1,
                   y = ..2 + 2,
                   z = ..3 + 3))
#> # A tibble: 3 x 3
#>       x     y     z
#>   <dbl> <dbl> <dbl>
#> 1   51.  134.  3.82
#> 2   50.  126.  4.00
#> 3   21.  132.  3.63

你可以用

在基础 R 中做同样的事情
do.call(rbind, do.call(Map, 
                       c(function(...){
                           data.frame(x = ..1 + 1,
                                      y = ..2 + 2,
                                      z = ..3 + 3)
                       }, 
                       DF)
))
#>    x   y    z
#> 1 51 134 3.82
#> 2 50 126 4.00
#> 3 21 132 3.63

...虽然不是很漂亮。

请注意,如果可能,矢量化解决方案会快得多。

DF %>% 
    mutate(x = x + 1,
           y = y + 2,
           z = z + 3)
#> # A tibble: 3 x 3
#>       x     y     z
#>   <dbl> <dbl> <dbl>
#> 1   51.  134.  3.82
#> 2   50.  126.  4.00
#> 3   21.  132.  3.63

【讨论】:

    【解决方案2】:

    只需使用apply...

    DF2 <- as.data.frame(t(apply(DF, 1, Funct)))
    
    DF2
       x   y    z
    1 51 134 3.82
    2 50 126 4.00
    3 21 132 3.63
    

    【讨论】:

    • 这很好用,因为所有列都是numeric(如提供的示例中所示)。如果用户过度简化了数据并且框架中还有其他类型,那么这个解决方案将不起作用(很好)。
    • 这是处理数千行的最有效方式吗?像 mapply 这样的东西怎么样 - 我不确定我确切知道如何使用它
    • @r2evans,是的,数据框只是数字
    • 作为一名 R 金牌会员用户,@DavidArenburg 在他的简历中将其列为第一名:如果您正在使用 data.frames,请忘记有一个名为 apply 的函数——无论您做什么- 不要使用它。
    • @OmryAtia mapply 如果您有多个变量的函数,但您使用的是一个变量的函数(尽管是向量),则可以使用,这是 apply 的每次迭代传递给 @ 987654327@。我原以为这是一种非常有效的方法,尽管如果您将 DF 转换为矩阵而不是数据框会最快。
    【解决方案3】:

    如果这是完美的numeric,你可以侥幸逃脱

    as.data.frame(t(apply(as.matrix(DF), 1, `+`, c(1,2,3))))
    as.data.frame(t(apply(DF, 1, Funct))) # better, per AndrewGustar's answer
    

    这可能是您能做到的最快速度。但是,如果您在数据中包含 numeric 以外的任何内容(例如,integer 或 *gasp* character),则使用 apply 将导致转换出 numeric,而不是您想要的。 (我在第一个示例中包含as.matrix 以演示apply 中实际发生的情况,而不是您的代码中实际上需要它。这种矩阵转换是apply 可能对非同质帧产生问题的原因。)

    正如在其他 cmets 中所述,如果您的数据确实是全部 -numeric,则通过将其转换为 matrix 并按此方式处理,您将获得显着的性能(以及相关的存储)改进。

    对于异构类框架(或者如果您只是想对未来的变化保持稳健),试试这个:

    do.call(rbind, by(DF, seq_len(nrow(DF)), Funct))
    # # A tibble: 3 × 3
    #       x     y     z
    # * <dbl> <dbl> <dbl>
    # 1    51   134  3.82
    # 2    50   126  4.00
    # 3    21   132  3.63
    

    编辑

    如果在聚合每一行时需要包含所有数据:

    1. 将整个DF 作为另一个参数传递,例如Funct(DF1, DFall)。这将被称为by(DF, seq_len(nrow(DF)), Funct, DFall=DF)

    2. 如果您对所有行的访问只是一个可以计算一次并作为附加参数传递给Funct 的聚合(想想Funct(DF1, DFall)),那么执行一次计算,然后像上面一样传递它代替整个框架;

    3. 否则,使用for 循环。所提供的解决方案(我现在也想不到)都不能促进这种观点。

    【讨论】:

    • 这是一个很好的通用解决方案。如果我的函数必须在 DF 上按行工作并引用它的所有行(正如我所说,Funct 比我写的要复杂得多)怎么办。 do.call 函数按行调用它,其余行不在环境中
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-08-27
    • 2019-11-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-02-12
    相关资源
    最近更新 更多