【问题标题】:Applying a function to every row of a table using dplyr?使用 dplyr 将函数应用于表的每一行?
【发布时间】:2014-03-16 02:32:11
【问题描述】:

在使用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,我想知道是否有一种整洁/自然的方式来做到这一点?因为这不是我想要的:

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

【问题讨论】:

  • 我最近询问在 dplyr 中是否有类似的 mdply,哈德利建议他们可能正在酝酿基于 do 的东西。我想它也可以在这里工作。
  • 最终 dplyr 会有类似rowwise() 的东西,它会按每一行分组
  • @hadley thx,当您不使用分组时,它不应该表现得像adply吗?因为其紧密集成的功能称为group_by而不是split_by
  • @StephenHenderson 不,因为你还需要一些方法来对整个桌子进行操作。
  • @HowYaDoing 是的,但该方法并不通用。例如,没有 psum、pmean 或 pmian。

标签: r plyr dplyr


【解决方案1】:

这样的?

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

【讨论】:

  • 是的,谢谢,这是一个非常具体的答案。但是我的示例和问题试图弄清楚是否有任何标量函数的通用dplyr 解决方案。
  • 一般来说,函数应该是向量化的——如果是一个古怪的函数,你可以写wacky.function &lt;- function(col.1, col.2){...},然后写iris.wacky &lt;- wacky.function(iris$Sepal.Length, iris$Petal.Length)
  • 我通常应该猜到它们,但我认为当您使用 dplyrplyr 或说 data.table 之类的东西时,您应该尝试使用它们的习语,这样您的代码就不会变成难以分享的风格组合。因此问题。
  • plyr 文档的第一行是“plyr 是一组解决常见问题的工具集:您需要将一个大问题分解为可管理的部分,对每个部分进行操作并然后将所有部分重新组合在一起。”这似乎是一个非常不同的问题,基本列操作是最好的工具。这也可以解释为什么没有“自然的”plyr/dplyr 命令来执行此操作。
  • 一句名言:“如果你只有一把plyr,你最终也会用它来做锤子和螺丝刀
【解决方案2】:

你需要按行分组:

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

这就是1adply 中所做的。

【讨论】:

  • 似乎应该有更简单或“更好”的语法。
  • @StephenHenderson,可能有,我不是dplyr 专家。希望其他人会带来更好的东西。注意我用1:n() 清理了一下。
  • 我怀疑你是对的,但我觉得没有分组的默认行为应该像 group_by(1:n()) 行为。如果早上没有人有任何其他想法,我会勾选你的;)
  • 另外,请注意这在某种程度上违反了n 的文档:“这个函数是为每个数据源专门实现的,只能在摘要中使用。”虽然它似乎有效.
  • 你能以某种方式通过索引号引用 Sepal.Length 和 Petal.Length 吗?如果你有很多变量确实会很方便。像 ... Max.len = max( [c(1,3)] ) ?
【解决方案3】:

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

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 包可以为您编写函数。

【讨论】:

  • thx,这是一个很好的答案,正如你所说,是优秀的通用 R 风格 - 惯用语,但我认为它并不能真正解决我的问题是否有 dplyr 方式......没有 dplyr 会更简单,例如with(df, Coalesce(a,b)) 也许,这是一种答案 - 不要使用 dplyr 吗?
  • 不得不承认我仔细检查了没有magicPony 包。太糟糕了
【解决方案4】:

扩展 BrodieG 的答案,

如果函数返回多行,则必须使用mutate() 而不是do()。然后要将它们重新组合在一起,请使用 dplyr 包中的 rbind_all()

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

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

测试性能,

library(plyr)    # plyr_1.8.4.9000
library(dplyr)   # dplyr_0.8.0.9000
library(purrr)   # purrr_0.2.99.9000
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))}
do_fn2 <- function(a){data.frame(a=a, b=runif(d2_count))}

op <- microbenchmark(
        plyr_version = plyr::adply(d1, 1, do_fn),
        dplyr_version = d1 %>%
            dplyr::group_by(1:nrow(d1)) %>%
            dplyr::do(do_fn(.)) %>%
            dplyr::bind_rows(),
        purrr_version = d1 %>% purrr::pmap_dfr(do_fn2),
        times=50)

结果如下:

Unit: milliseconds
          expr       min        lq      mean    median        uq       max neval
  plyr_version 1227.2589 1275.1363 1317.3431 1293.5759 1314.4266 1616.5449    50
 dplyr_version  977.3025 1012.6340 1035.9436 1025.6267 1040.5882 1449.0978    50
 purrr_version  609.5790  629.7565  643.8498  644.2505  656.1959  686.8128    50

这说明新的purrr版本最快

【讨论】:

    【解决方案5】:

    由于dplyr 0.2(我认为)rowwise()已经实现,所以这个问题的答案变成了:

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

    rowwise 替代

    五年(!)之后,这个答案仍然获得了大量流量。既然给出了,rowwise 就越来越不被推荐了,尽管很多人似乎觉得它很直观。帮自己一个忙,浏览 Jenny Bryan 的 Row-oriented workflows in R with the tidyverse 材料,以更好地了解这个主题。

    我找到的最直接的方法是基于 Hadley 使用pmap 的示例之一:

    iris %>% 
      mutate(Max.Len= purrr::pmap_dbl(list(Sepal.Length, Petal.Length), max))
    

    使用这种方法,您可以在 pmap 中为函数 (.f) 提供任意数量的参数。

    pmap 是一种很好的概念方法,因为它反映了这样一个事实,即当您进行逐行操作时,您实际上是在使用向量列表(数据帧中的列)中的元组。

    【讨论】:

    • 我已经把这个(从上面)改成了理想的答案,因为我认为这是预期的用途。
    • 是否可以添加动态形成的数据帧的值?所以在这个数据框中,列名是未知的。如果列名已知,我可以添加。
    • stackoverflow.com/questions/28807266/… 刚刚找到了答案。在这方面,他们使用相关性而不是总和。但同样的概念。
    • 如果它不起作用,请确保您实际使用的是 dplyr::mutate 而不是 plyr::mutate - 让我抓狂
    • 谢谢 YAK,这也让我感到难过。如果您同时包含 plyrdplyr 包,则几乎可以肯定您使用了错误的 mutate,除非您明确提供范围 dplyr::mutate
    【解决方案6】:

    2017-08-03 更新

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

    purrrlyr 包含一些位于 purrr 和 dplyr 交叉点的函数。它们已从 purrr 中删除,以使包装更轻,并且已被 tidyverse 中的其他解决方案取代。

    因此,您需要安装 + 加载该软件包才能使下面的代码正常工作。

    原帖

    Hadley 经常改变他对我们应该使用什么的想法,但我认为我们应该切换到 purrr 中的函数来获得逐行功能。至少,它们提供了与 plyradply 几乎相同的功能和界面。

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

    示例

    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.frames 的列表:

    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 参数控制。有三个选项:列表、行、列。当我们的输出长度为 1 时,使用行还是列都没有关系。

    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 使用 rows 还是 cols 很重要:

    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

    【讨论】:

    【解决方案7】:

    除了@alexwhan 提供的出色答案外,请记住您需要使用ungroup() 以避免副作用。这是因为rowwise()是一个分组操作。

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

    会给你:

       Sepal.Length Sepal.Width Petal.Length Petal.Width Species Max.Len
              <dbl>       <dbl>        <dbl>       <dbl> <fct>     <dbl>
     1          5.1         3.5          1.4         0.2 setosa      5.1
     2          4.9         3            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           3.6          1.4         0.2 setosa      5  
     6          5.4         3.9          1.7         0.4 setosa      5.4
     7          4.6         3.4          1.4         0.3 setosa      4.6
     8          5           3.4          1.5         0.2 setosa      5  
     9          4.4         2.9          1.4         0.2 setosa      4.4
    10          4.9         3.1          1.5         0.1 setosa      4.9
    

    现在假设您需要继续使用dplyr 管道将lead 添加到Max.Len

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

    这将产生:

       Sepal.Length Sepal.Width Petal.Length Petal.Width Species Max.Len Lead.Max.Len
              <dbl>       <dbl>        <dbl>       <dbl> <fct>     <dbl>        <dbl>
     1          5.1         3.5          1.4         0.2 setosa      5.1           NA
     2          4.9         3            1.4         0.2 setosa      4.9           NA
     3          4.7         3.2          1.3         0.2 setosa      4.7           NA
     4          4.6         3.1          1.5         0.2 setosa      4.6           NA
     5          5           3.6          1.4         0.2 setosa      5             NA
     6          5.4         3.9          1.7         0.4 setosa      5.4           NA
     7          4.6         3.4          1.4         0.3 setosa      4.6           NA
     8          5           3.4          1.5         0.2 setosa      5             NA
     9          4.4         2.9          1.4         0.2 setosa      4.4           NA
    10          4.9         3.1          1.5         0.1 setosa      4.9           NA
    

    NA 是作为副作用产生的。这可以通过ungroup() 纠正:

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

    这将产生所需的输出:

       Sepal.Length Sepal.Width Petal.Length Petal.Width Species Max.Len lead.max.len
              <dbl>       <dbl>        <dbl>       <dbl> <fct>     <dbl>        <dbl>
     1          5.1         3.5          1.4         0.2 setosa      5.1          4.9
     2          4.9         3            1.4         0.2 setosa      4.9          4.7
     3          4.7         3.2          1.3         0.2 setosa      4.7          4.6
     4          4.6         3.1          1.5         0.2 setosa      4.6          5  
     5          5           3.6          1.4         0.2 setosa      5            5.4
     6          5.4         3.9          1.7         0.4 setosa      5.4          4.6
     7          4.6         3.4          1.4         0.3 setosa      4.6          5  
     8          5           3.4          1.5         0.2 setosa      5            4.4
     9          4.4         2.9          1.4         0.2 setosa      4.4          4.9
    10          4.9         3.1          1.5         0.1 setosa      4.9          5.4
    

    【讨论】:

      猜你喜欢
      • 2018-06-06
      • 2015-02-23
      • 2021-06-30
      • 2013-03-18
      • 2014-04-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多