我试图了解如何编写使用 dplyr 快速的 Rcpp 汇总函数。这样做的动机是 dplyr 似乎没有等效函数,但是,为了简单起见,我将使用仅取向量的最后一个元素的示例。
在下面的代码中,我考虑了三个不同的函数来获取向量的最后一个元素,并使用 tapply 和 dplyr group_by/summarise 应用它们。
library(dplyr)
library(microbenchmark)
library(Rcpp)
n <- 5000
df <- data.frame(grp = factor(rep(1:n, 2)), valn = rnorm(2L*n), stringsAsFactors = F)
dplyr_num_last_element <- function() df %>% group_by(grp) %>% summarise(valn = last(valn))
dplyr_num_last_element_r <- function() df %>% group_by(grp) %>% summarise(valn = last_r(valn))
dplyr_num_last_element_rcpp <- function() df %>% group_by(grp) %>% summarise(val = last_rcpp(valn))
tapply_num_last_element <- function() tapply(df$valn, df$grp, FUN = last)
tapply_num_last_element_r <- function() tapply(df$valn, df$grp, FUN = last_r)
tapply_num_last_element_rcpp <- function() tapply(df$valn, df$grp, FUN = last_rcpp)
last_r <- function(x) {
x[1]
}
cppFunction('double last_rcpp(NumericVector x) {
int n = x.size();
return x[n-1];
}')
microbenchmark(dplyr_num_last_element(), dplyr_num_last_element_r(), dplyr_num_last_element_rcpp(), tapply_num_last_element(), tapply_num_last_element_r(), tapply_num_last_element_rcpp(), times = 10)
Unit: milliseconds
expr min lq mean median uq max neval
dplyr_num_last_element() 6.895850 7.088472 8.264270 7.766421 9.089424 11.00775 10
dplyr_num_last_element_r() 205.375404 214.481520 220.995218 220.107130 225.971179 238.62544 10
dplyr_num_last_element_rcpp() 211.593443 216.000009 222.247786 221.984289 228.801007 230.50220 10
tapply_num_last_element() 97.082102 99.528712 101.955668 101.717887 104.370319 109.26982 10
tapply_num_last_element_r() 6.101055 6.550065 7.386442 7.069754 7.589164 9.98025 10
tapply_num_last_element_rcpp() 14.173171 15.145711 16.102816 15.400562 16.053229 22.00147 10
我的一般问题是:
1) 为什么 dplyr_num_last_element_r 平均需要 220 毫秒,而 tapply_num_last_element_r 需要 7 毫秒。
2) 有没有办法编写我自己的最后一个函数来与 dplyr 一起使用,但它需要更多的 7ms 时间吗?
谢谢!
最佳答案
我有一些与你不同的结果。
注意我改了 last_r
返回最后一个元素并使用 dplyr::last
(因为还有 data.table::last
)。
library(dplyr)
library(microbenchmark)
library(Rcpp)
n <- 5000
df <- data.frame(
grp = factor(rep(1:n, 2)),
valn = rnorm(2L*n),
stringsAsFactors = FALSE
)
last_r <- function(x) {
tail(x, 1)
}
cppFunction('double last_rcpp(NumericVector x) {
int n = x.size();
return x[n-1];
}')
dplyr_num_last_element <- function() df %>% group_by(grp) %>% summarise(valn = dplyr::last(valn))
dplyr_num_last_element_r <- function() df %>% group_by(grp) %>% summarise(valn = last_r(valn))
dplyr_num_last_element_rcpp <- function() df %>% group_by(grp) %>% summarise(val = last_rcpp(valn))
tapply_num_last_element <- function() tapply(df$valn, df$grp, FUN = dplyr::last)
tapply_num_last_element_r <- function() tapply(df$valn, df$grp, FUN = last_r)
tapply_num_last_element_rcpp <- function() tapply(df$valn, df$grp, FUN = last_rcpp)
library(data.table)
dt <- data.table(df)
DT_num_last_element_r <- function() {
setkey(dt, grp)
dt[, last_r(valn), grp]
}
microbenchmark(
DT_num_last_element_r(),
dplyr_num_last_element(),
dplyr_num_last_element_r(),
dplyr_num_last_element_rcpp(),
tapply_num_last_element(),
tapply_num_last_element_r(),
tapply_num_last_element_rcpp(),
times = 20
)
基准:
Unit: milliseconds
expr min lq mean median uq max neval
DT_num_last_element_r() 53.956258 55.76482 57.08700 57.33898 58.50556 59.03580 20
dplyr_num_last_element() 224.289272 228.97531 235.87757 233.73353 237.56040 293.77219 20
dplyr_num_last_element_r() 178.778382 182.11143 187.40303 184.34760 187.00788 246.64526 20
dplyr_num_last_element_rcpp() 107.510245 109.64476 111.56974 112.50635 113.63999 114.92428 20
tapply_num_last_element() 55.999728 58.68948 60.68782 59.78769 63.78408 66.06941 20
tapply_num_last_element_r() 54.591615 57.31017 58.29962 58.16951 59.98568 63.08996 20
tapply_num_last_element_rcpp() 9.558151 10.66994 14.76226 11.54004 12.64156 73.87743 20
我的结果更加连贯。你能用这些小的变化进行测试吗?
这在 Windows 10、R 3.4.0(启用 JIT 编译器)上。
关于r - 自定义 rcpp last 函数使用 dplyr group_by 变慢并与 tapply 相比进行总结,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46083998/