【问题标题】:How to optimize subsetting from a large dataset?如何从大型数据集中优化子集?
【发布时间】:2013-01-03 13:09:17
【问题描述】:

我有这个金融交易数据集,它相当大但小到足以保存在内存中..

R> str(trans)
'data.frame':   130000000 obs. of  5 variables:
 $ id    : int  5 5 5 5 6 11 11 11 11 11 ...
 $ kod   : int  2 3 2 3 38 2 3 6 7 6 ...
 $ ar    : int  329 329 330 330 7 329 329 329 329 329 ...
 $ belopp: num  1531 -229.3 324 -48.9 0 ...
 $ datum : int  36976 36976 37287 37287 37961 36976 36976 37236 37236 37281 ...

我需要遍历它,为每个唯一 ID 提取交易,并进行大量计算。问题是数据集的子集太慢了..

R> system.time(
+ sub <- trans[trans$id==15,]
+ )
   user  system elapsed 
   7.80    0.55    8.36


R> system.time(
+ sub <- subset(trans, id == 15)
+ )
   user  system elapsed 
   8.49    1.05    9.53 

由于该数据集中有大约 10m 的唯一 ID,因此这样的循环将持续很长时间,有什么想法可以加快速度吗?

编辑 我已经涉足了“data.tables”、索引和排序,但运气不佳..

library(data.table)
trans2 <- as.data.table(trans)
trans2 <- trans2[order(id)]
trans2 <- setkey(trans2, id)

R> system.time(
+ sub <- trans2[trans2$id==15,]
+ )
   user  system elapsed 
   7.33    1.08    8.41 

R> system.time(
+ sub <- subset(trans2, id == 15)
+ )
   user  system elapsed 
   8.66    1.12    9.78

EDIT2太棒了。

R> system.time(
+ sub <- trans2[J(15)]
+ )
   user  system elapsed 
      0       0       0 

【问题讨论】:

  • 您能描述一下您对data.tables 的尝试以及您遇到问题的地方吗?
  • @BenBarnes 当然,我尝试将 data.frame 转换为 data.table,对其进行排序并使用 setkey() 函数来索引 id i> 列。这根本没有降低子集时间..
  • 你还在使用循环吗? data.table,by 参数到 [ 会快得多,我认为:stackoverflow.com/questions/11279304/…
  • 如果您要转换为data.table 并使用setkey,则无需额外进行排序。此外,对于子集,请尝试(使用您的键控 data.table 命名,例如,transDTtransDT[J(15)]。还请考虑编辑您的问题以包含您使用的 data.table 代码。

标签: r


【解决方案1】:

Note: 已通过将计算的函数从rowSums 更改为colSums 来编辑帖子(在data.table 的情况下使用lapply

我不认为你能比data.table 更快地得到结果。这是plyrdata.table 之间的基准。当然,如果耗时的部分是你的函数,那么你可以使用doMC 并行运行plyr(假设你有很多内核或者你在集群上工作)。否则,我会坚持使用data.table。这是一个包含大量测试数据和一个虚拟函数的分析:

# create a huge data.frame with repeating id values
len <- 1e5
reps <- sample(1:20, len, replace = TRUE)
x <- data.frame(id = rep(1:len, reps))
x <- transform(x, v1 = rnorm(nrow(x)), v2 = rnorm(nrow(x)))

> nrow(x) 
[1] 1048534 # 1 million rows

# construct functions for data.table and plyr
# method 1
# using data.table
DATA.TABLE <- function() {
    require(data.table)
    x.dt <- data.table(x, key="id")
    x.dt.out <- x.dt[, lapply(.SD, sum), by=id]
}

# method 2
# using plyr
PLYR <- function() {
    require(plyr)
    x.plyr.out <- ddply(x, .(id), colSums)
}

# let's benchmark
> require(rbenchmark)
> benchmark(DATA.TABLE(), PLYR(), order = "elapsed", replications = 1)[1:5]
          test replications elapsed relative user.self
1 DATA.TABLE()           1  1.006     1.00    .992
2       PLYR()           1  67.755   67.351  67.688

在具有 100 万行的 data.frame 上,data.table 采用 0.992 seconds。与plyr 相比,使用data.table 的加速比(诚然,在计算列总和上)是68x。根据函数中的计算时间,这种加速会有所不同。但是data.table 仍然会更快。 plyr 是一种拆分-应用-组合策略。与使用 base 拆分、应用和组合自己相比,我认为您不会获得可比的加速。当然可以试试。

我运行了 1000 万行的代码。 data.table 跑了 5.893 秒。 plyr 耗时 6300 秒。

【讨论】:

  • +1 但为什么要测试sum(rowSums(w))?在 data.table 中按列执行操作很重要,永远不要按行执行,因为这样会导致页面效率低下。尝试测试lapply(.SD,sum)sum(lapply(.SD,sum)),它们应该会更快。 22 秒似乎也是 10 次跑步的总时间。任何不知道 R 或 data.table 或 plyr 的人都可能会惊讶于这仅需要 1e6 行就需要 22 秒。没有,需要 2.2s,lapply 应该会更好。
  • 我专注于Update: 那里的部分。现在我看到了上一段,但我不是 100% 清楚。
  • @Matthew,我只想表明,唯一的限制因素是函数计算所需的时间。我真的不介意该功能是否需要更多时间。目的是表明对于相同的功能,data.table 将是最快的。不确定这是否能让事情变得清晰..?
  • 是的,因为colSums 的结果是一个向量。要将其作为列,它是 as.list(colSums(.SD)) 但当然 as.list 会将所有这些小向量复制到每个组的新(小)list 中,因此通过避免这些强制,直接 lapply(.SD,sum) 应该更快。
  • 很好的答案顺便说一句,这更像是:)
【解决方案2】:

为什么不使用拆分、应用和组合策略?

类似这样的东西(没有样本数据我不知道这是否可行):

fastsplit <- function (df) {
  lista <- split(seq(nrow(df)), df$id)
  return(lista)
}

# function to split the data frame into a list by id

lista_split <- fastsplit(trans)

# now, assuming that one of the calculations is, for instance, to sum belopp
# apply the function to each subset

result1 <- lapply(lista_split, function(.indx){

  sum_bellop = sum(trans$belopp[.indx])})

# combine stage
r1 <- do.call(rbind, result1)

在提供了上面的代码之后,我想说如果你可以使用 SQL,它会更快更容易。也许 sqldf 包可以在这里帮助你?不过我从来没有尝试过。不知道快不快SQL 中的代码非常简单。要执行与上面的 R 代码相同的操作,只需使用以下内容:

select id
       , sum(belopp) as sum_bellop from trans
group by id

这将返回一个包含两列的表,id 和 belopp by id 的总和

【讨论】:

  • 看过@Arun 的回答后,我必须说我从未使用过data.table,所以我不知道我的解决方案是否比data.table 快。但是,我知道我的解决方案比 plyr 快得多。尽管如此,我还是建议你转向 SQL ..
  • 我肯定会测试拆分策略是否优于使用我最初想到的巨大循环。但是,您错误地认为我只需要对每个子集进行简单计算,实际上那里有几百行代码,因此 SQL 将无法正常工作。
  • 如果您发现拆分策略是否比 data.table 更好,请告诉我...
  • 嗨,曼诺尔。这可能会提供一些关于 data.table 与 SQL 速度的见解:stackoverflow.com/a/6594199/403310.
  • 谢谢@Matthew Dowle。我必须承认,得知 data.table 比 SQL 更快,我感到非常惊讶。我一定会看看 data.table。
猜你喜欢
  • 2019-11-21
  • 2017-09-18
  • 2018-12-14
  • 1970-01-01
  • 1970-01-01
  • 2019-09-24
  • 2016-07-08
  • 1970-01-01
  • 2019-07-18
相关资源
最近更新 更多