【发布时间】:2019-01-29 19:59:44
【问题描述】:
我有一个矩阵,其中包含一个分组列和一个用于汇总值的列。
我想将矩阵拆分成组,总结一些值(通过保持相同长度的向量),unsplit然后将它们分配到新列?
什么是最高效和基本的 R-onic 方法?
目前的获胜者是 lapply 函数,但我想知道是否还有其他我缺少的函数? stats::aggregate 之类的东西保持相同的结构?
我想留在基础 R 中并保留矩阵,所以没有 dplyr 或 data.table ;)。
Edit1:我包含了聚合 + 合并和由 @IceCreamToucan 提供支持的 sapply 解决方案。 (感谢那)。对于 aggregate,这不是一个非常公平的比较,因为我首先转换为 data.frames,然后再转换回矩阵。
Edit2: 使用更大的矩阵和 100 个组 ave 优于其他函数。感谢@Gregor 的这个。
set.seed(104)
smpl = sample(1:100, size = 10000, T)
mat0 <- as.matrix(data.frame(
group=smpl,
sum=sample(seq(0,100,10), length(smpl), T)
))
mat1 <- cbind(mat0, "sums"=0)
library(microbenchmark)
check <- function(values) {
all(sapply(values[-1], function(x) all.equal(values[[1]], x)))}
mf = microbenchmark(#check = check,
forloop = {
mat <- mat1
for (z in unique(mat[,'group'])) {
mat[mat[,'group'] == z,'sums'] = sum(mat[mat[,'group'] == z,'sum'])
}
mat
},
lapply = {
mat <- mat1
mat[,'sums'] <- unlist(lapply(unique(mat[,'group']), function(i) {
sums = sum(mat[mat[,'group'] == i,'sum'])
rep(sums, length(mat[mat[,'group'] == i,'sum']))
}))
mat
},
sapply = {
mat <- mat1
mat <- mat[order(mat[,'group']),]
mat[,'sums'] <- rep(sapply(split(mat[, 'sum'], mat[, 'group']), sum),
table(mat[, 'group']))
mat
},
ave = {
mat <- mat1
mat[,'sums'] <- ave(x = mat[, 'sum'], mat[, 'group'], FUN = sum)
mat[order(mat[,'group']),]
},
aggregate = {
matA <- mat0
matA <- matA[order(matA[,'group']),]
res = aggregate(sum ~ group, FUN = sum, data = matA)
matdf = data.frame(matA)
base::merge(res, matdf, by ="group")
}
)
mf
Unit: milliseconds expr min lq mean median uq max neval cld forloop 19.94083 25.73131 25.95823 25.97898 26.58043 38.68300 100 bc lapply 15.96057 21.44226 24.23693 21.88130 22.41287 311.00252 100 bc sapply 21.89081 22.41981 23.42291 22.70492 23.04978 37.41853 100 b ave 11.79256 12.08868 12.51119 12.27613 12.52803 18.20577 100 a aggregate 26.54753 27.31484 29.09592 27.71163 28.71937 54.75284 100 c
【问题讨论】:
-
您可以将
sums列为rep(sapply(split(mat[, 'sum'], mat[, 'group']), sum), table(mat[, 'group']))。我认为这比仅使用数据框更令人困惑,但如果您与矩阵结婚,我会这样做。 -
有趣。当然,你可以包含它。
-
ave比 for 循环快,但仍比lapply慢 4-5 倍。ave(x = mat1[, 'sum'], mat1[, 'group'], FUN = sum) -
...虽然时间以微秒为单位,但我想知道这个例子是否足够大以至于很好。如果您真的担心性能,我建议您做一个示例,例如,至少 100 个组,每个组有几行。
-
而
ave在内部只是split和lapply;)