拆分应用组合 returns 多个变量的函数
Split-apply-combine with function that returns multiple variables
我需要将 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
在计算上非常昂贵,所以我希望它 只被调用一次 而不是单独调用a
和 b
列。在 dplyr
中有没有办法做到这一点?
因为你的函数 returns 是一个数据框,你可以在 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
我们可以使用data.table
library(data.table)
setDT(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
do
不一定会提高速度。在此 post 中,我将介绍一种设计执行相同任务的函数的方法,然后进行基准测试以比较每种方法的性能。
这是定义函数的另一种方法。
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
方法(m2
和 m4
)实际上比对应方法(m1
和 m3
)慢。在这种情况下,应用 myfun
(m1
) 和 myfun2
(m3
) 比使用 do
更快。 myfun2
(m3
) 比 myfun
(m1
) 略快。但是,不定义任何函数(m5
)实际上比所有函数定义的方法(m1
到 m4
)更快,这表明对于这种特殊情况,实际上没有必要定义一个函数。最后,如果没有必要留在tidyverse
,或者数据集的大小是巨大的。我们可以考虑 data.table
方法 (m6
),它比此处列出的所有 tidyverse
解决方案快得多。
我需要将 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
在计算上非常昂贵,所以我希望它 只被调用一次 而不是单独调用a
和 b
列。在 dplyr
中有没有办法做到这一点?
因为你的函数 returns 是一个数据框,你可以在 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
我们可以使用data.table
library(data.table)
setDT(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
do
不一定会提高速度。在此 post 中,我将介绍一种设计执行相同任务的函数的方法,然后进行基准测试以比较每种方法的性能。
这是定义函数的另一种方法。
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
方法(m2
和 m4
)实际上比对应方法(m1
和 m3
)慢。在这种情况下,应用 myfun
(m1
) 和 myfun2
(m3
) 比使用 do
更快。 myfun2
(m3
) 比 myfun
(m1
) 略快。但是,不定义任何函数(m5
)实际上比所有函数定义的方法(m1
到 m4
)更快,这表明对于这种特殊情况,实际上没有必要定义一个函数。最后,如果没有必要留在tidyverse
,或者数据集的大小是巨大的。我们可以考虑 data.table
方法 (m6
),它比此处列出的所有 tidyverse
解决方案快得多。