通过累积分组变量将数据框转换为列表

时间:2016-02-17 01:48:22

标签: r

我的数据:

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}错过的dplyrdata.table是否有更快的方法?

2 个答案:

答案 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和某些版本的splitrbinddo.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的数据集。