【发布时间】:2018-08-09 11:44:49
【问题描述】:
我正在尝试找出循环遍历 data.frame myData 的最佳方法,按两列分组 c1 和 c2。
具体来说,我想遍历c1 和c2 的每个独特组合,并将某个customFunction 应用于myData 中的其他列。这个customFunction 依赖于someStatsFunction,它输出一个data.frame。
我通常会使用函数plyr::ddply,但我的真实数据集有超过 1800 万行,这并不奇怪这需要太长时间。所以我决定改变使用dplyr::group_by和dplyr::do的管道方法。尽管使用dplyr 可以加快问题的速度(参见下面的最小示例),但仍然需要相当长的时间。我听说data.table 框架可以加快速度(参见示例here),但我不知道如何使用它。我想知道是否有人可以使用data.table 翻译下面的问题,以便我也可以对其进行基准测试。
library(plyr)
library(dplyr)
library(rbenchmark)
someStatsFunction <- function (x) {
data.frame(name = 'something', mean = mean(x), sd = sd(x), statx = sqrt(mean(abs(x)))/sd(x)^2)
}
customFunction <- function (data) {
if (!all(sort(data$time) == data$time)) {
stop('Column \'time\' is not ordered')
}
someStatsFunction(data$response)
}
myData <- data.frame(c1 = rep(rep(1:50, each = 30), 10), c2 = rep(rep(1:30, 50), 10), response = rnorm(30 * 50 * 10), time = 1:(30 * 50 * 10))
benchmark('testPlyr' = {
testPlyr <- plyr::ddply(myData, .(c1, c2), customFunction)
},
'testDplyr' = {
testDplyr <- myData %>% dplyr::group_by(c1,c2) %>% dplyr::do(customFunction(.))
},
replications = 3,
columns = c('test', 'replications', 'elapsed', 'relative', 'user.self', 'sys.self'))
这是我得到的输出:
test replications elapsed relative user.self sys.self
2 testDplyr 3 7.416 1.00 7.368 0.060
1 testPlyr 3 8.378 1.13 8.364 0.012
谢谢,
D
更新在@minem 的answer之后
首先,我对上面的示例进行了一些修复,因为代码不正确。
其次,我扩展了上面的最小可重现示例,以更好地反映(稍微)我的情况。 someStatsFunction 可能依赖于 data.table 中的多个列,并根据从这些多列派生的一些重要的统计信息组合来处理一堆数字。我还增加了myData 的大小(因此,如果与原始示例相比,下面的示例现在需要更长的时间)。无论如何,我想我设法复制了从plyr 或dplyr 获得的输出。它使用 data.table 运行得更快,这真的很酷(参见下面的基准测试)。但是,代码似乎有点笨拙:
library(plyr)
library(dplyr)
library(data.table)
library(rbenchmark)
someStatsFunction <- function (y, x) {
x <- as.integer(x)
mod <- coef(summary(lm(y ~ x)))
data.frame(stats1 = 'something',
intercept = mod[1],
slope = mod[2],
meanx = mean(x),
statx = sqrt(mean(abs(x)))/sd(y)^2)
}
customFunction <- function (data) {
if (!all(sort(data$time) == data$time)) {
stop('Column \'time\' is not ordered')
}
someStatsFunction(y = data$response, x = data$time)
}
myData <- data.frame(c1 = rep(rep(1:50, each = 30), 1095), c2 = rep(rep(1:30, 50), 1095), response = rnorm(30 * 50 * 1095), time = rep(seq(as.Date('1981-01-01'), as.Date('1983-12-31'), by = '1 day'), each = 50*30))
benchmark('testPlyr' = {
testPlyr <- plyr::ddply(myData, .(c1, c2), customFunction)
},
'testDplyr' = {
testDplyr <- myData %>% dplyr::group_by(c1,c2) %>% dplyr::do(customFunction(.))
},
'testDtb' = {
vNames <- c('stats1', 'intercept', 'slope', 'meanx', 'statx')
dt <- as.data.table(myData)
testDtb <- dt[order(time)][,
(vNames) := as.list(someStatsFunction(response, time)),
by = .(c1, c2)][,
head(.SD, 1), by = .(c1, c2)][,
c('response', 'time') := NULL, ]
},
replications = 3,
columns = c('test', 'replications', 'elapsed', 'relative', 'user.self', 'sys.self'))
这是我得到的输出:
test replications elapsed relative user.self sys.self
2 testDplyr 3 28.209 3.101 20.841 7.317
3 testDtb 3 9.098 1.000 10.958 0.385
1 testPlyr 3 28.224 3.102 21.741 7.167
速度有了如此显着的提高。但是,在应用 someStatsFunction 之前,我必须先对数据进行排序(即消除在 customFunction 处的 if 语句的需要),然后使用 myData 中的列 response 和 time 运行函数.此外,来自
dt[order(time)][, (vNames) := as.list(someStatsFunction(response, time)), by = .(c1, c2)]
给出一个不返回 1500 个值的表(即 c1 和 c2 的 30*50 组合),而是多次重复 c1 和 c2 的组合。此外,它确实返回了原始的 response 和 time 列,尽管我只希望将 c1 和 c2 的唯一组合绑定到来自 someStatsFunction 的统计信息(如使用 plyr 和/或dplyr),因此是我的最终代码
testDtb <- dt[order(time)][,
(vNames) := as.list(someStatsFunction(response, time)),
by = .(c1, c2)][,
head(.SD, 1), by = .(c1, c2)][,
c('response', 'time') := NULL, ]
我是否可以通过更简化的方式获得相同的输出?
【问题讨论】:
-
您的示例数据太小,时间不重要。也许把它写成依赖于#rows 和#groups。另外,我想你可以简化它(没有理由有两个分组变量 c1 和 c2)
-
谢谢弗兰克,我在 c1 和 c2 中使用了这个简单的示例,因为在我的原始数据中它们是坐标(经纬度)。我基本上需要对每对坐标的环境时间序列应用一个复杂的函数。是否将坐标粘贴到一个单独的分组变量中(出于循环的目的),然后在输出中将它们拆分回来加快速度?或者这只会创建额外的不必要的代码?另外,我有兴趣了解
data.table替代方案是否会大大加快速度...... -
我不认为将它们结合起来会提高速度;我只是说我们可能不需要解决这个问题是额外的复杂性......我认为(但可能是错误的)只有 #groups 很重要(在加载 data.table 后可计算为
uniqueN(setDT(mydata), by=c("c1", "c2")))。顺便说一句,如果您想知道优化了哪些分组计算,可以查看?GForce。 -
完美,感谢您的建议。 data.table 的概念对我来说还是很新的,所以我还不太清楚如何将上面的例子翻译成 data.table 框架。但如果我有一个工作,我会尝试使用 data.table 示例编辑我的帖子。
-
setDT(myData)[order(time), someStatsFunction(response, time), by=.(c1, c2)]之类的东西会达到你想要的效果
标签: r performance dplyr data.table plyr