Split-apply-combine与返回多个变量的函数

时间:2017-08-20 04:10:43

标签: r dplyr plyr

我需要将myfun应用于数据框的子集,并将结果作为返回的数据框中的新列包含在内。在过去,我使用ddply。但是在dplyr中,我相信summarise用于此,就像这样:

myfun<- function(x,y) {
  df<- data.frame( a= mean(x)*mean(y), b= mean(x)-mean(y) )           
  return (df)
}

mtcars %>%
  group_by(cyl) %>%
  summarise(a = myfun(cyl,disp)$a, b = myfun(cyl,disp)$b)

以上代码有效,但我将使用的myfun计算成本非常高,所以我希望它只被称为一次而不是{{1} }和a列。有没有办法在b中执行此操作?

3 个答案:

答案 0 :(得分:3)

由于您的函数返回一个数据框,您可以在group_by %>% do内调用您的函数,该函数将函数应用于每个单独的组并且 rbind 将返回的数据框组合在一起:

mtcars %>% group_by(cyl) %>% do(myfun(.$cyl, .$disp))

# A tibble: 3 x 3
# Groups:   cyl [3]
#    cyl         a         b
#  <dbl>     <dbl>     <dbl>
#1     4  420.5455 -101.1364
#2     6 1099.8857 -177.3143
#3     8 2824.8000 -345.1000

答案 1 :(得分:3)

do不一定会提高速度。在这篇文章中,我将介绍一种设计执行相同任务的函数的方法,然后进行基准测试以比较每种方法的性能。

这是定义函数的另一种方法。

myfun2 <- function(dt, x, y){
  x <- enquo(x)
  y <- enquo(y)

  dt2 <- dt %>%
    summarise(a = mean(!!x) * mean(!!y), b = mean(!!x) - mean(!!y))
  return(dt2)
}

请注意myfun2的第一个参数是dt,它是输入数据框。通过这样做,myfun2可以成功实现作为管道操作的一部分。

mtcars %>%
  group_by(cyl) %>%
  myfun2(x = cyl, y = disp)
# A tibble: 3 x 3
    cyl         a         b
  <dbl>     <dbl>     <dbl>
1     4  420.5455 -101.1364
2     6 1099.8857 -177.3143
3     8 2824.8000 -345.1000

通过这样做,我们每次想要创建新列时都不必调用my_fun。所以这种方法可能比my_fun更有效。

以下是使用microbenchmark的效果比较。我比较的方法如下所列。我运行了1000次模拟。

m1: OP's original way to apply `myfun`  
m2: Psidom's method, using `do`to apply `myfun`.  
m3: My approach, using `myfun2`  
m4: Using `do` to apply `myfun2`  
m5: Z.Lin's suggestion, directly calculating the values without defining a function.
m6: akrun's `data.table` approach with `myfun`

以下是基准测试的代码。

microbenchmark(m1 = (mtcars %>%
                       group_by(cyl) %>%
                       summarise(a = myfun(cyl, disp)$a, b = myfun(cyl, disp)$b)),
               m2 = (mtcars %>% 
                       group_by(cyl) %>% 
                       do(myfun(.$cyl, .$disp))),
               m3 = (mtcars %>%
                       group_by(cyl) %>%
                       myfun2(x = cyl, y = disp)),
               m4 = (mtcars %>%
                       group_by(cyl) %>%
                       do(myfun2(., x = cyl, y = disp))),
               m5 = (mtcars %>% 
                       group_by(cyl) %>% 
                       summarise(a = mean(cyl) * mean(disp), b = mean(cyl) - mean(disp))),
               m6 = (as.data.table(mtcars)[, myfun(cyl, disp), cyl]),
               times = 1000)

这是基准测试的结果。

Unit: milliseconds
 expr       min        lq      mean    median        uq        max neval
   m1  7.058227  7.692654  9.429765  8.375190 10.570663  28.730059  1000
   m2  8.559296  9.381996 11.643645 10.500100 13.229285  27.585654  1000
   m3  6.817031  7.445683  9.423832  8.085241 10.415104 193.878337  1000
   m4 21.787298 23.995279 28.920262 26.922683 31.673820 177.004151  1000
   m5  5.337132  5.785528  7.120589  6.223339  7.810686  23.231274  1000
   m6  1.320812  1.540199  1.919222  1.640270  1.935352   7.622732  1000

结果显示,do方法(m2m4)实际上比其对应方m1m3慢。在这种情况下,应用myfunm1)和myfun2m3)比使用do更快。 myfun2m3)比myfunm1)快得多。但是,没有定义任何函数(m5)实际上比所有函数定义的方法(m1m4)更快,这表明对于这种特殊情况,实际上没有必要定义一个功能。最后,如果没有必要留在tidyverse,或者数据集的大小是巨大的。我们可以考虑使用data.table方法(m6),这比此处列出的所有tidyverse解决方案快得多。

答案 2 :(得分:1)

我们可以使用data.table

library(data.table)
#  as.data.table(mtcars)[, myfun(cyl, disp), cyl] 
#    cyl         a         b
#1:   6 1099.8857 -177.3143
#2:   4  420.5455 -101.1364
#3:   8 2824.8000 -345.1000