我的数据:
set.seed(4)
mydf <- data.frame(var1 = rep(LETTERS[1:4], each=3), var2 = runif(12), grp = rep(1:4, each=3))
var1 var2 grp
1 A 0.585800305 1
2 A 0.008945796 1
3 A 0.293739612 1
4 B 0.277374958 2
5 B 0.813574215 2
6 B 0.260427771 2
7 C 0.724405893 3
8 C 0.906092151 3
9 C 0.949040221 3
10 D 0.073144469 4
11 D 0.754675027 4
12 D 0.286000621 4
我想得到一个数据框列表,其中列表的第一个元素有grp = 1,列表的第二个元素有grp&lt; = 2,依此类推。这可以通过for
循环完成:
results<-NULL
for(i in 1:max(mydf$grp)) {results[[i]] <- mydf[mydf$grp <= i,] }
results
使用{I}错过的dplyr
或data.table
是否有更快的方法?
答案 0 :(得分:2)
尝试lapply
声明:
set.seed(4)
mydf <- data.frame(
var1 = rep(LETTERS[1:4], each=3),
var2 = runif(12),
grp = rep(1:4, each=3))
ptm <- proc.time()
results<-NULL
for(i in 1:max(mydf$grp)) {results[[i]] <- mydf[mydf$grp <= i,] }
results
proc.time() - ptm
# user system elapsed
# 0.029 0.001 0.057
ptm <- proc.time()
q <- lapply(unique(mydf$grp), function(x) mydf[mydf$grp <= x,])
proc.time() - ptm
# user system elapsed
# 0.007 0.001 0.034
答案 1 :(得分:1)
这花了我很多年,但我终于得到了一些有用的东西。首先,当数据非常小时(如示例),原始for
循环几乎不可能被击败。然而,随着数据变得越来越大,故事发生了巨大变化。 (请查看我的更大样本数据集的结尾。)
cumsum(group_size( ... ))
使用dplyr
的{{1}}函数,您可以获得变量每个级别长度的向量。只要它们按顺序排列(如果没有,则可以使用group_size
),可以在此处调用arrange
以获取所需每个子集的最终行的索引。然后你需要做的就是循环子集化。
这里的优点是你不需要为每次迭代重复评估cumsum
中的每个值:你可以只用整数进行子集。
保持grp
循环,
for
对于原始样本集来说速度较慢,但在大约4000行开始变得更快。
使用library(dplyr)
grps <- cumsum(group_size(group_by(mydf, grp)))
results <- NULL
for(i in 1:length(grps)){results[[i]] <- mydf[seq.int(grps[i]),]}
而不是lapply
循环
for
产生类似的结果。
grps <- cumsum(group_size(group_by(mydf, grp)))
lapply(grps, function(x){mydf[seq.int(x)]})
为了进一步加快速度,请将tbl_df
转换为mydf
。
即使采用原始方法
tbl_df
在40k行上,这种方法与原始方法一样长约40-45%。
使用results<-NULL
mydf_t <- tbl_df(mydf)
for(i in 1:max(mydf_t$grp)) {results[[i]] <- mydf_t[mydf_t$grp <= i,] }
,cumsum(group_size())
lapply
在40k行上,这种方法与原始方法一样长25-30%。在grps <- cumsum(group_size(group_by(mydf, grp)))
mydf_t <- tbl_df(mydf)
lapply(grps, function(x){mydf_t[seq.int(x),]})
循环中
for
在40k行上,这种方法与原始方法一样长20-25%。我不知道为什么 grps <- cumsum(group_size(group_by(mydf, grp)))
results <- NULL
mydf_t <- tbl_df(mydf)
for(i in 1:length(grps)){results[[i]] <- mydf_t[seq.int(i),]}
循环优于for
,但在这种情况下它始终如此。
有些事情很慢,但可能会很快:
lapply
和某些版本的split
(rbind
,do.call(rbind, ... )
,data.table::rbindlist
),似乎应该快,但不是。dplyr::bind_rows
,这比整数上的正常子集略慢。dplyr::slice
。我不确定为什么它比tbl_dt
慢得多,但它确实如此。我使用tbl_df
来比较上面的版本。一体化:
microbenchmark
在40k行上,我的机器上运行的一个样本返回:
library(microbenchmark)
microbenchmark('original' = {
results<-NULL
for(i in 1:max(mydf$grp)) {results[[i]] <- mydf[mydf$grp <= i,] }
},
'group_size for' = {
grps <- cumsum(group_size(group_by(mydf, grp)))
results <- NULL
for(i in 1:length(grps)){results[[i]] <- mydf[seq.int(grps[i]),]}
},
'group_size lapply' = {
grps <- cumsum(group_size(group_by(mydf, grp)))
lapply(grps, function(x){mydf[seq.int(x),]})
},
'original tbl_df' = {
results<-NULL
mydf_t <- tbl_df(mydf)
for(i in 1:max(mydf_t$grp)) {results[[i]] <- mydf_t[mydf_t$grp <= i,] }
},
'tbl_df group_size lapply' = {
grps <- cumsum(group_size(group_by(mydf, grp)))
mydf_t <- tbl_df(mydf)
lapply(grps, function(x){mydf_t[seq.int(x),]})
},
'tbl_df group_size for' = {
grps <- cumsum(group_size(group_by(mydf, grp)))
results <- NULL
mydf_t <- tbl_df(mydf)
for(i in 1:length(grps)){results[[i]] <-mydf_t[seq.int(grps[i]),]}
},
times = 10)
警告:如果您一次全部运行它们或将Unit: milliseconds
expr min lq mean median uq max neval
original 1273 1337 1379 1395 1414 1447 10
group_size for 1003 1111 1139 1169 1176 1211 10
group_size lapply 1132 1153 1210 1168 1287 1306 10
original tbl_df 457 474 560 598 608 671 10
tbl_df group_size lapply 252 397 401 404 422 504 10
tbl_df group_size for 279 303 375 401 421 427 10
提高到非常高的那么慢。
我将原始样本数据扩展到4k,10k和40k行。根据您的喜好调整数字。
times
警告:当set.seed(4)
mydf <- data.frame(var1 = sort(sample(LETTERS, 40000, replace = TRUE)),
var2 = runif(40000),
grp = rep(1:400, each=100))
为40k行时,上面的循环会生成一个123.5 Mb的数据集。