代码之家  ›  专栏  ›  技术社区  ›  Omry Atia

在R中,对数据帧的行应用函数并返回数据帧

  •  2
  • Omry Atia  · 技术社区  · 7 年前

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

    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
    
    3 回复  |  直到 7 年前
        1
  •  4
  •   alistaire    7 年前

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

    但有一个更好的选择 申请 :相反,在变量(列)上并行迭代,然后将结果列表强制返回到数据帧。 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
    

    您可以在base 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
  •  0
  •   Andrew Gustar    7 年前

    仅使用 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
    
        3
  •  0
  •   r2evans    7 年前

    如果这是完美的 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
    

    这可能是你能做的最快的。但是,如果您有任何 数字 在数据中(例如。, integer 或*喘息* character ),使用 apply 将导致转换为 数字 ,而不是你想要的。(我包括 as.matrix 在第一个示例中,演示 申请 ,并不是说您实际上需要在代码中使用它。这种矩阵转换是为什么 申请 对于非同质帧可能会有问题。)

    如其他评论所述,如果您的数据- 数字 ,通过将其转换为 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 作为附加论点(思考 函数(DF1,DFall) ),然后进行一次计算,然后按上述方式将其传递到整个框架的位置;

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