使用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 

从dplyr 0.2(我认为) rowwise()被实现,所以这个问题的答案变成:

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

你需要逐行分组:

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

这就是1所做的。

惯用的方法是创build一个适当的vector化函数。

R提供适合的pmax ,但是它也提供了Vectorize作为mapply的包装,允许你创build一个vector化的任意函数的任意版本。

 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包会为您写入函数。

扩展BrodieG的答案,

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

dplyr版本dplyr_0.1.2 ,在group_by()子句中使用1:n()不适用于我。 希望Hadley很快会实现rowwise()

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

testing性能,

 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 

更新2017-08-03

写完这些之后,哈德雷又变了一些东西。 过去在呜呜声中的函数现在是一个新的混合包,称为呜呜声 ,描述如下:

purrrlyr包含一些函数,位于purrr和dplyr的交集。 他们已经从呜呜声中删除,以使包装更轻,因为它们已经被其他解决scheme所取代。

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

原帖

哈德利经常改变我们应该使用什么的思想,但我认为我们应该切换到purrr的function,以获得逐行function。 至less,他们提供相同的function,并具有几乎相同的界面,从plyr adply

有两个相关的函数, by_rowinvoke_rows 。 我的理解是,当你想遍历行并将结果添加到by_row时,你使用by_row。 当循环访问data.frame的行并将每个col作为parameter passing给函数时,将使用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 s:

 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控制。 有三个选项:列表,行,列。 当我们的输出长度为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 ,那么我们是否使用行或列作为data.frame是很重要的:

 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, ...)function,则可以使用by_row

像这样的东西?

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