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


121

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

我最近问是否有mdplydplyr中的等价物,哈德利(Hadley)建议他们可能正在根据酿造某种东西do。我想它也可以在这里工作。
baptiste 2014年

4
最终dplyr会有类似rowwise()这由每个单排将组
哈德利

@hadley thx,它的行为不应该只是adply不使用分组时的行为吗?因为它紧密集成的功能称为group_byNOTsplit_by
Stephen Henderson

@StephenHenderson不,因为您还需要某种方式来对整个表进行操作。
hadley 2014年

1
@HowYaDoing是的,但是该方法不能一概而论。例如,没有psum,pmean或pmedian。
Stephen Henderson

Answers:


202

从dplyr 0.2开始(我认为)rowwise()已实现,因此此问题的答案变为:

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

不可rowwise替代

五年(!)之后,这个答案仍然吸引了大量流量。自从给出以来rowwise,尽管很多人似乎觉得它很直观,但越来越不推荐这样做。帮自己一个忙,并使用tidyverse材料 Jenny Bryan的R中面向行的工作流中进行学习,以很好地解决这个问题。

我发现的最直接的方法是基于Hadley使用pmap以下示例之一:

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

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

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


我已将其(从上面)更改为理想的答案,因为我认为这是预期的用法。
Stephen Henderson

1
是否可以添加动态形成的datatframe的值?因此,在此数据框中,列名未知。如果列名已知,我可以添加。
阿伦·拉贾

stackoverflow.com/questions/28807266/…刚刚找到了答案。在这种情况下,他们使用相关性而不是总和。但是,相同的概念。
阿伦·拉贾

13
如果它不起作用,请确保您实际上在使用dplyr :: mutate而不是plyr :: mutate-让我发疯了
jan-glx 2015年

谢谢Y牛,这也让我有点痛苦。如果同时包含plyrdplyr软件包,则mutate除非明确提供scope,否则几乎可以肯定使用了错误的代码dplyr::mutate
克里斯·沃思

22

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

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吗?
斯蒂芬·亨德森2014年

4
必须承认,我再次检查了是否没有magicPony包裹。太糟糕了
rsoren '16

21

您需要按行分组:

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

这就是在中1所做的adply


似乎应该有一个更简单或更“更”的语法。
斯蒂芬·亨德森

@StephenHenderson,可能是,我不是dplyr专家。希望其他人会带来更好的东西。注意我用清理了一下1:n()
BrodieG 2014年

我怀疑您是对的,但我觉得没有分组的默认行为应该像group_by(1:n())行为。如果没有人在早上有任何其他想法,我会在您的想法上打勾;)
Stephen Henderson 2014年

另外,请注意,这在某种程度上违反了以下文档的说明n:“此功能是针对每个数据源而专门实现的,只能在摘要中使用。”,尽管它似乎起作用。
BrodieG 2014年

您能否以某种方式通过索引号引用Sepal.Length和Petal.Length?如果您有很多变量,那就方便了。像... Max.len = max([c(1,3)])吗?
拉斯姆斯·拉森

19

更新2017-08-03

写完这些之后,哈德利再次改变了一些东西。现在,以前在purrr中使用的功能现在在称为purrrlyr的新混合包中,描述为:

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

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

原始帖子

Hadley经常改变想法,但我认为我们应该切换到purrr中的功能以获得逐行功能。至少,他们提供相同的功能,并具有几乎相同的接口adplyplyr

有两个相关功能,by_rowinvoke_rows。我的理解是,by_row当您要遍历行并将结果添加到data.frame时可以使用。invoke_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,如果返回a ,则得到带有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

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

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,而第一个没有。

最后,如果我们的输出大于vector或等于data.frame行1的长度1 ,那么对于以下情况使用行还是列很重要.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


2
by_row已过时,调用它说, “使用的组合:tidyr ::窝(); dplyr ::发生变异(); purrr ::地图()” github.com/hadley/purrrlyr/blob/...
momeara

大量的r。
qwr

14

扩展了BrodieG的答案,

如果函数返回多个行,然后代替mutate()do()必须使用。然后将其重新组合在一起,可rbind_all()dplyr包装中使用。

dplyrversion中dplyr_0.1.21:n()group_by()子句中使用对我不起作用。希望哈德利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版本是最快的


1

像这样吗

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

1
是的,这是一个非常具体的答案。但是我的例子和问题试图弄清楚是否有dplyr任何标量函数的通用解决方案。
斯蒂芬·亨德森

通常,应将函数向量化-如果它是古怪的函数,则可以编写wacky.function <- function(col.1, col.2){...},然后编写iris.wacky <- wacky.function(iris$Sepal.Length, iris$Petal.Length)
2014年

通常我应该猜他们,但是我认为当您使用诸如dplyrplyr或说data.table您应该尝试使用它们的惯用法时,您的代码就不会成为难以共享的样式组合了。因此是一个问题。
斯蒂芬·亨德森

plyr文档的第一行是“ plyr是一组工具,可以解决一系列常见问题:您需要将一个大问题分解为可管理的部分,对每个部分进行操作,然后将所有部分放回一起。” 对于哪个基本列操作是最好的工具,这似乎是一个非常不同的问题。这也可以解释为什么没有“自然的” plyr/ dplyr命令来执行此操作。
colcarroll 2014年

5
要屠杀一个著名的名言:“ 如果您拥有的只是一把犁刀,您最终也将把它用作锤子和螺丝刀
thelatemail 2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.