首页 文章

使用dplyr将函数应用于表的每一行?

提问于
浏览
98

当使用 plyr 时,我经常发现将 adply 用于标量函数是很有用的,我必须将其应用于每一行 .

例如

data(iris)
library(plyr)
head(
     adply(iris, 1, transform , Max.Len= max(Sepal.Length,Petal.Length))
    )
  Sepal.Length Sepal.Width Petal.Length Petal.Width Species Max.Len
1          5.1         3.5          1.4         0.2  setosa     5.1
2          4.9         3.0          1.4         0.2  setosa     4.9
3          4.7         3.2          1.3         0.2  setosa     4.7
4          4.6         3.1          1.5         0.2  setosa     4.6
5          5.0         3.6          1.4         0.2  setosa     5.0
6          5.4         3.9          1.7         0.4  setosa     5.4

现在我正在使用 dplyr ,我想知道是否有一个整洁/自然的方式来做到这一点?因为这是 NOT 我想要的:

library(dplyr)
head(
     mutate(iris, Max.Len= max(Sepal.Length,Petal.Length))
    )
  Sepal.Length Sepal.Width Petal.Length Petal.Width Species Max.Len
1          5.1         3.5          1.4         0.2  setosa     7.9
2          4.9         3.0          1.4         0.2  setosa     7.9
3          4.7         3.2          1.3         0.2  setosa     7.9
4          4.6         3.1          1.5         0.2  setosa     7.9
5          5.0         3.6          1.4         0.2  setosa     7.9
6          5.4         3.9          1.7         0.4  setosa     7.9

6 回答

  • 158

    惯用的方法是创建一个适当的矢量化函数 .

    R 提供适合的 pmax ,但它也提供了 Vectorize 作为 mapply 的包装,允许您创建任意函数的矢量化任意版本 .

    library(dplyr)
    # use base R pmax (vectorized in C)
    iris %>% mutate(max.len = pmax(Sepal.Length, Petal.Length))
    # use vectorize to create your own function
    # for example, a horribly inefficient get first non-Na value function
    # a version that is not vectorized
    coalesce <- function(a,b) {r <- c(a[1],b[1]); r[!is.na(r)][1]}
    # a vectorized version
    Coalesce <- Vectorize(coalesce, vectorize.args = c('a','b'))
    # some example data
    df <- data.frame(a = c(1:5,NA,7:10), b = c(1:3,NA,NA,6,NA,10:8))
    df %>% mutate(ab =Coalesce(a,b))
    

    请注意,在C / C中实现矢量化会更快,但是没有 magicPony 包可以为您编写函数 .

  • 19

    扩展BrodieG的答案,

    如果函数返回多行,则必须使用 do() 而不是 mutate() . 然后将它组合回来,使用 dplyr 包中的 rbind_all() .

    dplyr version dplyr_0.1.2 中,在 group_by() 子句中使用 1:n() 对我不起作用 . 希望很快Hadley will implement rowwise() .

    iris %>%
        group_by(1:nrow(iris)) %>%
        do(do_fn) %>%
        rbind_all()
    

    测试性能,

    library(dplyr)
    library(plyr)
    library(microbenchmark)
    
    d1_count <- 1000
    d2_count <- 10
    
    d1 <- data.frame(a=runif(d1_count))
    
    do_fn <- function(row){
        data.frame(
                a=row$a,
                b=runif(d2_count))}
    
    
    op <- microbenchmark(
        dplyr_version = d1 %>%
            group_by(1:nrow(d1)) %>%
            do(do_fn) %>%
            rbind_all(),
        plyrs_version = adply(d1, 1, do_fn),
        times=10)
    

    它有以下结果:

    Unit: milliseconds
             expr      min       lq   median       uq       max neval
    dplyr_version 474.8283 509.5577 517.4723 549.9897  703.3613    10
    plyrs_version 830.1255 831.0652 862.5729 903.2783 1039.8510    10
    
  • 18

    您需要按行分组:

    iris %>% group_by(1:n()) %>% mutate(Max.Len= max(Sepal.Length,Petal.Length))
    

    这就是 1adply 中所做的 .

  • 13

    像这样的东西?

    iris$Max.Len <- pmax(iris$Sepal.Length, iris$Petal.Length)
    
  • 1

    更新2017-08-03

    写完之后,哈德利又改变了一些东西 . 过去在purrr中的函数现在在a new mixed package called purrrlyr中,描述为:

    purrrlyr包含一些位于purrr和dplyr交叉点的函数 . 它们已经从purrr中移除,以使包装更轻,并且因为它们已被替换为tidyverse中的其他解决方案 .

    因此,您需要安装load包以使下面的代码工作 .

    原帖

    Hadley经常改变他们对我们应该使用的东西的想法,但我认为我们应该切换到 purrr 中的函数来获得by行功能 . 至少,它们提供相同的功能,并且与 plyradply 具有几乎相同的界面 .

    有两个相关的函数, by_rowinvoke_rows . 我的理解是,当你想循环遍历行并将结果添加到data.frame时,你使用 by_row . 当循环遍历data.frame的行并将每个col作为参数传递给函数时,将使用 invoke_rows . 我们只会使用第一个 .

    例子

    library(tidyverse)
    
    iris %>% 
      by_row(..f = function(this_row) {
        browser()
      })
    

    这让我们可以看到内部结构(所以我们可以看到我们正在做什么),这与使用 adply 进行操作相同 .

    Called from: ..f(.d[[i]], ...)
    Browse[1]> this_row
    # A tibble: 1 × 5
      Sepal.Length Sepal.Width Petal.Length Petal.Width Species
             <dbl>       <dbl>        <dbl>       <dbl>  <fctr>
    1          5.1         3.5          1.4         0.2  setosa
    Browse[1]> Q
    

    默认情况下, by_row 根据输出添加列表列:

    iris %>% 
      by_row(..f = function(this_row) {
          this_row[1:4] %>% unlist %>% mean
      })
    

    得到:

    # A tibble: 150 × 6
       Sepal.Length Sepal.Width Petal.Length Petal.Width Species      .out
              <dbl>       <dbl>        <dbl>       <dbl>  <fctr>    <list>
    1           5.1         3.5          1.4         0.2  setosa <dbl [1]>
    2           4.9         3.0          1.4         0.2  setosa <dbl [1]>
    3           4.7         3.2          1.3         0.2  setosa <dbl [1]>
    4           4.6         3.1          1.5         0.2  setosa <dbl [1]>
    5           5.0         3.6          1.4         0.2  setosa <dbl [1]>
    6           5.4         3.9          1.7         0.4  setosa <dbl [1]>
    7           4.6         3.4          1.4         0.3  setosa <dbl [1]>
    8           5.0         3.4          1.5         0.2  setosa <dbl [1]>
    9           4.4         2.9          1.4         0.2  setosa <dbl [1]>
    10          4.9         3.1          1.5         0.1  setosa <dbl [1]>
    # ... with 140 more rows
    

    如果我们返回一个 data.frame ,我们得到一个 data.frame 的列表:

    iris %>% 
      by_row( ..f = function(this_row) {
        data.frame(
          new_col_mean = this_row[1:4] %>% unlist %>% mean,
          new_col_median = this_row[1:4] %>% unlist %>% median
        )
      })
    

    得到:

    # A tibble: 150 × 6
       Sepal.Length Sepal.Width Petal.Length Petal.Width Species                 .out
              <dbl>       <dbl>        <dbl>       <dbl>  <fctr>               <list>
    1           5.1         3.5          1.4         0.2  setosa <data.frame [1 × 2]>
    2           4.9         3.0          1.4         0.2  setosa <data.frame [1 × 2]>
    3           4.7         3.2          1.3         0.2  setosa <data.frame [1 × 2]>
    4           4.6         3.1          1.5         0.2  setosa <data.frame [1 × 2]>
    5           5.0         3.6          1.4         0.2  setosa <data.frame [1 × 2]>
    6           5.4         3.9          1.7         0.4  setosa <data.frame [1 × 2]>
    7           4.6         3.4          1.4         0.3  setosa <data.frame [1 × 2]>
    8           5.0         3.4          1.5         0.2  setosa <data.frame [1 × 2]>
    9           4.4         2.9          1.4         0.2  setosa <data.frame [1 × 2]>
    10          4.9         3.1          1.5         0.1  setosa <data.frame [1 × 2]>
    # ... with 140 more rows
    

    我们如何添加函数的输出由 .collate param控制 . 无论我们是使用行还是列,都很重要 .

    iris %>% 
      by_row(.collate = "cols", ..f = function(this_row) {
        this_row[1:4] %>% unlist %>% mean
      })
    
    iris %>% 
      by_row(.collate = "rows", ..f = function(this_row) {
        this_row[1:4] %>% unlist %>% mean
      })
    

    两者都产生:

    # A tibble: 150 × 6
       Sepal.Length Sepal.Width Petal.Length Petal.Width Species  .out
              <dbl>       <dbl>        <dbl>       <dbl>  <fctr> <dbl>
    1           5.1         3.5          1.4         0.2  setosa 2.550
    2           4.9         3.0          1.4         0.2  setosa 2.375
    3           4.7         3.2          1.3         0.2  setosa 2.350
    4           4.6         3.1          1.5         0.2  setosa 2.350
    5           5.0         3.6          1.4         0.2  setosa 2.550
    6           5.4         3.9          1.7         0.4  setosa 2.850
    7           4.6         3.4          1.4         0.3  setosa 2.425
    8           5.0         3.4          1.5         0.2  setosa 2.525
    9           4.4         2.9          1.4         0.2  setosa 2.225
    10          4.9         3.1          1.5         0.1  setosa 2.400
    # ... with 140 more rows
    

    如果我们输出一行有1行的data.frame,那么我们使用的只是轻微的:

    iris %>% 
      by_row(.collate = "cols", ..f = function(this_row) {
        data.frame(
          new_col_mean = this_row[1:4] %>% unlist %>% mean,
          new_col_median = this_row[1:4] %>% unlist %>% median
          )
      })
    
    iris %>% 
      by_row(.collate = "rows", ..f = function(this_row) {
        data.frame(
          new_col_mean = this_row[1:4] %>% unlist %>% mean,
          new_col_median = this_row[1:4] %>% unlist %>% median
        )
      })
    

    两者都给:

    # A tibble: 150 × 8
       Sepal.Length Sepal.Width Petal.Length Petal.Width Species  .row new_col_mean new_col_median
              <dbl>       <dbl>        <dbl>       <dbl>  <fctr> <int>        <dbl>          <dbl>
    1           5.1         3.5          1.4         0.2  setosa     1        2.550           2.45
    2           4.9         3.0          1.4         0.2  setosa     2        2.375           2.20
    3           4.7         3.2          1.3         0.2  setosa     3        2.350           2.25
    4           4.6         3.1          1.5         0.2  setosa     4        2.350           2.30
    5           5.0         3.6          1.4         0.2  setosa     5        2.550           2.50
    6           5.4         3.9          1.7         0.4  setosa     6        2.850           2.80
    7           4.6         3.4          1.4         0.3  setosa     7        2.425           2.40
    8           5.0         3.4          1.5         0.2  setosa     8        2.525           2.45
    9           4.4         2.9          1.4         0.2  setosa     9        2.225           2.15
    10          4.9         3.1          1.5         0.1  setosa    10        2.400           2.30
    # ... with 140 more rows
    

    除了第二个具有名为 .row 的列而第一个没有 .

    最后,如果我们的输出长度为长度1,无论是 vector 还是带有行的 data.frame ,那么我们是否使用 .collate 的行或列来确定:

    mtcars[1:2] %>% by_row(function(x) 1:5)
    mtcars[1:2] %>% by_row(function(x) 1:5, .collate = "rows")
    mtcars[1:2] %>% by_row(function(x) 1:5, .collate = "cols")
    

    分别产生:

    # A tibble: 32 × 3
         mpg   cyl      .out
       <dbl> <dbl>    <list>
    1   21.0     6 <int [5]>
    2   21.0     6 <int [5]>
    3   22.8     4 <int [5]>
    4   21.4     6 <int [5]>
    5   18.7     8 <int [5]>
    6   18.1     6 <int [5]>
    7   14.3     8 <int [5]>
    8   24.4     4 <int [5]>
    9   22.8     4 <int [5]>
    10  19.2     6 <int [5]>
    # ... with 22 more rows
    
    # A tibble: 160 × 4
         mpg   cyl  .row  .out
       <dbl> <dbl> <int> <int>
    1     21     6     1     1
    2     21     6     1     2
    3     21     6     1     3
    4     21     6     1     4
    5     21     6     1     5
    6     21     6     2     1
    7     21     6     2     2
    8     21     6     2     3
    9     21     6     2     4
    10    21     6     2     5
    # ... with 150 more rows
    
    # A tibble: 32 × 7
         mpg   cyl .out1 .out2 .out3 .out4 .out5
       <dbl> <dbl> <int> <int> <int> <int> <int>
    1   21.0     6     1     2     3     4     5
    2   21.0     6     1     2     3     4     5
    3   22.8     4     1     2     3     4     5
    4   21.4     6     1     2     3     4     5
    5   18.7     8     1     2     3     4     5
    6   18.1     6     1     2     3     4     5
    7   14.3     8     1     2     3     4     5
    8   24.4     4     1     2     3     4     5
    9   22.8     4     1     2     3     4     5
    10  19.2     6     1     2     3     4     5
    # ... with 22 more rows
    

    所以,底线 . 如果需要 adply(.margins = 1, ...) 功能,可以使用 by_row .

  • 13

    截至dplyr 0.2(我认为) rowwise() 已实现,因此这个问题的答案变为:

    iris %>% 
      rowwise() %>% 
      mutate(Max.Len= max(Sepal.Length,Petal.Length))
    

相关问题