为什么在具有多个组的大型数据帧上拆分效率低?

时间:2016-09-17 09:53:23

标签: r performance purrr

df %>% split(.$x)
对于大量x的唯一值,

变慢。如果我们将数据帧手动拆分为较小的子集,然后对每个子集执行拆分,我们会将时间减少至少一个数量级。

library(dplyr)
library(microbenchmark)
library(caret)
library(purrr)

N      <- 10^6
groups <- 10^5
df     <- data.frame(x = sample(1:groups, N, replace = TRUE), 
                     y = sample(letters,  N, replace = TRUE))
ids      <- df$x %>% unique
folds10  <- createFolds(ids, 10)
folds100 <- createFolds(ids, 100)

正在运行microbenchmark给我们

## Unit: seconds

## expr                                                  mean
l1 <- df %>% split(.$x)                                # 242.11805

l2 <- lapply(folds10,  function(id) df %>% 
      filter(x %in% id) %>% split(.$x)) %>% flatten    # 50.45156  

l3 <- lapply(folds100, function(id) df %>% 
      filter(x %in% id) %>% split(.$x)) %>% flatten    # 12.83866  

split不适用于大型团体吗?除了手动初始子集之外还有其他选择吗?

我的笔记本电脑是2013年底的macbook pro,2.4GHz 8GB

3 个答案:

答案 0 :(得分:10)

更多的解释而不是答案。子设置大数据帧比分设置小数据帧更昂贵

> df100 = df[1:100,]
> idx = c(1, 10, 20)
> microbenchmark(df[idx,], df100[idx,], times=10)
Unit: microseconds
         expr     min      lq     mean  median      uq     max neval
    df[idx, ] 428.921 441.217 445.3281 442.893 448.022 475.364    10
 df100[idx, ]  32.082  32.307  35.2815  34.935  37.107  42.199    10

split()为每个小组支付这笔费用。

运行Rprof()

可以看出原因
> Rprof(); for (i in 1:1000) df[idx,]; Rprof(NULL); summaryRprof()
$by.self
       self.time self.pct total.time total.pct
"attr"      1.26      100       1.26       100

$by.total
               total.time total.pct self.time self.pct
"attr"               1.26       100      1.26      100
"[.data.frame"       1.26       100      0.00        0
"["                  1.26       100      0.00        0

$sample.interval
[1] 0.02

$sampling.time
[1] 1.26

所有的时间都花在拨打attr()的电话上。使用debug("[.data.frame")逐步执行代码表明,痛苦涉及到像

这样的调用
attr(df, "row.names")

这个小例子展示了R用来避免表示不存在的行名称的技巧:使用c(NA, -5L),而不是1:5

> dput(data.frame(x=1:5))
structure(list(x = 1:5), .Names = "x", row.names = c(NA, -5L), class = "data.frame")

请注意attr()返回一个向量 - row.names是动态创建的,而对于大型data.frame则会创建大量的row.names

> attr(data.frame(x=1:5), "row.names")
[1] 1 2 3 4 5

所以人们可能会认为即使是荒谬的row.names也会加快计算速度

> dfns = df; rownames(dfns) = rev(seq_len(nrow(dfns)))
> system.time(split(dfns, dfns$x))
   user  system elapsed 
  4.048   0.000   4.048 
> system.time(split(df, df$x))
   user  system elapsed 
 87.772  16.312 104.100 

分割矢量或矩阵也很快。

答案 1 :(得分:2)

这不是严格的split.data.frame问题,对于许多组,data.frame的可伸缩性存在更普遍的问题。
如果使用split.data.table,您可以获得相当快的速度。我在常规的data.table方法之上开发了这个方法,它似乎在这里很好地扩展。

system.time(
    l1 <- df %>% split(.$x)   
)
#   user  system elapsed 
#200.936   0.000 217.496 
library(data.table)
dt = as.data.table(df)
system.time(
    l2 <- split(dt, by="x")   
)
#   user  system elapsed 
#  7.372   0.000   6.875 
system.time(
    l3 <- split(dt, by="x", sorted=TRUE)   
)
#   user  system elapsed 
#  9.068   0.000   8.200 

sorted=TRUE将返回与data.frame方法相同顺序的列表,默认情况下,data.table方法将保留输入数据中存在的顺序。如果你想坚持使用data.frame,最后可以使用lapply(l2, setDF)

PS。在1.9.7中添加了split.data.table,devel版本的安装非常简单

install.packages("data.table", type="source", repos="http://Rdatatable.github.io/data.table")

Installation wiki中有关于此的更多信息。

答案 2 :(得分:0)

利用dplyr 0.8.3或更高版本的group_split进行的非常不错的作弊:

random_df <- tibble(colA= paste("A",1:1200000,sep = "_"), 
                    colB= as.character(paste("A",1:1200000,sep = "_")),
                    colC= 1:1200000)

random_df_list <- split(random_df, random_df$colC)

random_df_list <- random_df %>% group_split(colC)

减少几分钟到几秒钟的操作!