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

在使用 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
103623 次浏览

像这样吗?

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

您需要按行分组:

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

这就是 1adply中所做的。

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

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

布罗迪的回答是,

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

dplyr版本的 dplyr_0.1.2中,在 group_by()子句中使用 1:n()对我来说不起作用。希望 哈德利会执行 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版本是最快的

在 dplyr 0.2(我认为)实现 rowwise()时,这个问题的答案是:

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

rowwise选项

五年后来这个答案仍然得到了很多流量。自从 rowwise被提供以来,它越来越不被推荐,尽管很多人似乎觉得它很直观。帮你自己一个忙,通过珍妮布莱恩的 R 中带有 tidyverse 的面向行的工作流材料得到一个很好的处理这个话题。

我发现的最直接的方法是基于哈德利使用 pmap的一个例子:

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

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

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

更新2017-08-03

写完这篇文章后,哈德利又改了一些东西。过去在 purrr 中的函数现在在 一个名为 < strong > purrrrlyr 的新混合包中,描述如下:

Purrrlyr 包含一些位于 purrr 和 dplyr 交集处的函数。为了使包装更轻,并且因为它们已经被整洁宇宙中的其他解决方案所取代,它们已经从咕噜声中移除。

因此,您将需要安装 + 加载该包,以使下面的代码工作。

原文

Hadley 经常改变他对我们应该使用什么的想法,但是我认为我们应该切换到 咕噜中的函数来获得 by row 功能。至少,它们提供了与 皮尔中的 adply几乎相同的功能和接口。

有两个相关的函数,by_rowinvoke_rows。我的理解是,当需要遍历行并将结果添加到 data.frame 时,可以使用 by_row。当循环遍历 data.frame 的行并将每个 coll 作为参数传递给函数时,将使用 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参数控制。有三个选项: 列表,行,协议。当我们的输出长度为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,那么我们使用哪一行只是很小的问题:

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

除了@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

只是为了完整起见,我将改变这个 使用者的代码,从遗忘的 回答(也许是最好的答案)的问题: 跨多列求和。用它来解决你的问题:

iris %>%
mutate(max = select(.,c('Sepal.Length','Petal.Length')) %>%
apply(1, max, na.rm=TRUE))

结果是预料之中的。接受的回答是,逐行越来越不建议,并申请基础 R。 Uou 不需要进口额外的包像咕噜。

可以使用带有 max、 min、 sum、 mid、 mean 的 application ()函数,因此它非常方便和简单。