ddply 和 group_by 的更快替代品
Faster alternatives to ddply and group_by
我试图找出循环 data.frame、myData
的最佳方法,按两列 c1
和 c2
分组。
具体来说,我想遍历 c1
和 c2
的每个唯一组合,并将某个 customFunction
应用于 myData
中的其他列。 customFunction
依赖于 someStatsFunction
,它输出 data.frame
.
我通常会使用函数 plyr::ddply
,但我的真实数据集有超过 1800 万行,这花费的时间太长也就不足为奇了。所以我决定将方法更改为使用 dplyr::group_by
和 dplyr::do
的管道。尽管使用 dplyr
可以加快问题的速度(参见下面的最小示例),但它仍然需要相当长的时间。我听说 data.table
framework can speed things a lot (see example 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
UPDATE 在@minem 的
之后
首先,我对上面的示例做了一些修复,因为代码不正确。
其次,我扩展了上面的最小可重现示例,以更好地(稍微)反映我的情况。 someStatsFunction
可能依赖于 data.table 的多个列,并且 c运行ch 一堆数字基于从这些多个列派生的一些非平凡的统计组合。我还增加了 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
语句的需要),然后 运行 使用列 response
和 time
在 myData
。此外,
的原始输出
dt[order(time)][, (vNames) := as.list(someStatsFunction(response, time)), by = .(c1, c2)]
给出的 table 不是 return 1500 个值(即 c1
和 c2
的 30*50 组合),而是重复 [=18] 的组合=] 和 c2
多次。此外,它 return 原始的 response
和 time
列,尽管我只想要 c1
和 c2
的独特组合绑定到来自 someStatsFunction
(如使用 plyr
and/or dplyr
的输出),因此我的最终代码是
testDtb <- dt[order(time)][,
(vNames) := as.list(someStatsFunction(response, time)),
by = .(c1, c2)][,
head(.SD, 1), by = .(c1, c2)][,
c('response', 'time') := NULL, ]
有没有办法以更简单的方式实现相同的输出?
尝试:
dt <- as.data.table(myData)
rr <- dt[, .(
lon = c1,
lat = c2,
name = 'something',
mean = mean(response),
sd = sd(response),
statx = sqrt(abs(response)) / sd(response) ^ 2
), keyby = .(c1, c2)]
rr
# c1 c2 lon lat name mean sd statx
# 1: 1 1 1 1 something 0.23841637 0.9384408 0.3253456
# 2: 1 1 1 1 something 0.23841637 0.9384408 0.2421654
# 3: 1 1 1 1 something 0.23841637 0.9384408 0.5321797
# 4: 1 1 1 1 something 0.23841637 0.9384408 0.4136648
# 5: 1 1 1 1 something 0.23841637 0.9384408 1.5863249
# ---
# 14996: 50 30 50 30 something -0.04082032 0.7156352 2.3970053
# 14997: 50 30 50 30 something -0.04082032 0.7156352 0.8375551
# 14998: 50 30 50 30 something -0.04082032 0.7156352 1.7826972
# 14999: 50 30 50 30 something -0.04082032 0.7156352 1.0293926
# 15000: 50 30 50 30 something -0.04082032 0.7156352 0.1376940
感谢 @chinsoon12 提供的 ,我能够得到我想要的结果:
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' = {
testDtb <- setDT(myData)[order(time), someStatsFunction(response, time), by=.(c1, c2)]
},
replications = 3,
columns = c('test', 'replications', 'elapsed', 'relative', 'user.self', 'sys.self'))
这是我得到的基准测试结果:
test replications elapsed relative user.self sys.self
2 testDplyr 3 68.383 3.976 48.120 20.392
3 testDtb 3 17.201 1.000 17.232 0.008
1 testPlyr 3 57.938 3.368 49.676 8.304
如果您想了解不同方法的结果是否相同,请查看:
all.equal(testDplyr, testDtb)
# [1] TRUE
all.equal(testDplyr, testPlyr)
# [1] TRUE
我试图找出循环 data.frame、myData
的最佳方法,按两列 c1
和 c2
分组。
具体来说,我想遍历 c1
和 c2
的每个唯一组合,并将某个 customFunction
应用于 myData
中的其他列。 customFunction
依赖于 someStatsFunction
,它输出 data.frame
.
我通常会使用函数 plyr::ddply
,但我的真实数据集有超过 1800 万行,这花费的时间太长也就不足为奇了。所以我决定将方法更改为使用 dplyr::group_by
和 dplyr::do
的管道。尽管使用 dplyr
可以加快问题的速度(参见下面的最小示例),但它仍然需要相当长的时间。我听说 data.table
framework can speed things a lot (see example 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
UPDATE 在@minem 的
首先,我对上面的示例做了一些修复,因为代码不正确。
其次,我扩展了上面的最小可重现示例,以更好地(稍微)反映我的情况。 someStatsFunction
可能依赖于 data.table 的多个列,并且 c运行ch 一堆数字基于从这些多个列派生的一些非平凡的统计组合。我还增加了 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
语句的需要),然后 运行 使用列 response
和 time
在 myData
。此外,
dt[order(time)][, (vNames) := as.list(someStatsFunction(response, time)), by = .(c1, c2)]
给出的 table 不是 return 1500 个值(即 c1
和 c2
的 30*50 组合),而是重复 [=18] 的组合=] 和 c2
多次。此外,它 return 原始的 response
和 time
列,尽管我只想要 c1
和 c2
的独特组合绑定到来自 someStatsFunction
(如使用 plyr
and/or dplyr
的输出),因此我的最终代码是
testDtb <- dt[order(time)][,
(vNames) := as.list(someStatsFunction(response, time)),
by = .(c1, c2)][,
head(.SD, 1), by = .(c1, c2)][,
c('response', 'time') := NULL, ]
有没有办法以更简单的方式实现相同的输出?
尝试:
dt <- as.data.table(myData)
rr <- dt[, .(
lon = c1,
lat = c2,
name = 'something',
mean = mean(response),
sd = sd(response),
statx = sqrt(abs(response)) / sd(response) ^ 2
), keyby = .(c1, c2)]
rr
# c1 c2 lon lat name mean sd statx
# 1: 1 1 1 1 something 0.23841637 0.9384408 0.3253456
# 2: 1 1 1 1 something 0.23841637 0.9384408 0.2421654
# 3: 1 1 1 1 something 0.23841637 0.9384408 0.5321797
# 4: 1 1 1 1 something 0.23841637 0.9384408 0.4136648
# 5: 1 1 1 1 something 0.23841637 0.9384408 1.5863249
# ---
# 14996: 50 30 50 30 something -0.04082032 0.7156352 2.3970053
# 14997: 50 30 50 30 something -0.04082032 0.7156352 0.8375551
# 14998: 50 30 50 30 something -0.04082032 0.7156352 1.7826972
# 14999: 50 30 50 30 something -0.04082032 0.7156352 1.0293926
# 15000: 50 30 50 30 something -0.04082032 0.7156352 0.1376940
感谢 @chinsoon12 提供的
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' = {
testDtb <- setDT(myData)[order(time), someStatsFunction(response, time), by=.(c1, c2)]
},
replications = 3,
columns = c('test', 'replications', 'elapsed', 'relative', 'user.self', 'sys.self'))
这是我得到的基准测试结果:
test replications elapsed relative user.self sys.self
2 testDplyr 3 68.383 3.976 48.120 20.392
3 testDtb 3 17.201 1.000 17.232 0.008
1 testPlyr 3 57.938 3.368 49.676 8.304
如果您想了解不同方法的结果是否相同,请查看:
all.equal(testDplyr, testDtb)
# [1] TRUE
all.equal(testDplyr, testPlyr)
# [1] TRUE