6

另一个关于大数据的新手问题。我正在使用包含时间序列数据的大型数据集(3.5m 行)。我想创建一个data.table包含第一次出现唯一标识符的列。

df 是 a data.tabledf$timestamp是 class 中的日期POSIXct,并且df$id是唯一的数字标识符。我正在使用以下代码:

# UPDATED - DATA KEYED
setkey(df, id)
sub_df<-df[,(min(timestamp)), by=list(id)] # Finding first timestamp for each unique ID

这就是问题所在。我正在汇总超过 80k 的唯一 ID。R 窒息。我可以做些什么来优化我的方法?

4

4 回答 4

6

这是一个小代码来测试哪些行为需要很多时间

require(data.table)
dt <- data.table(sample(seq(as.Date("2012-01-01"), as.Date("2013-12-31"), 
          by="days"), 1e5, replace=T), val=sample(1e4, 1e5, replace = T))

FUN1 <- function() {
    out <- dt[, min(dt$V1), by=val]  # min of entire V1 for each group i.e. wrong
}

FUN2 <- function() {
    out <- dt[, min(V1), by=val]     # min of V1 within group as intended
}

require(rbenchmark)
> benchmark(FUN1(), FUN2(), replications = 1, order="elapsed")
#     test replications elapsed relative user.self sys.self user.child sys.child
# 2 FUN2()            1   0.271    1.000     0.242    0.002          0         0
# 1 FUN1()            1  38.378  141.616    32.584    4.153          0         0

很明显,它的速度非常FUN2()快。请记住,在这两种情况下,都没有设置 KEY

于 2013-01-29T19:53:46.700 回答
5

正如@Arun 所提到的,真正的关键(不是双关语)是使用正确的data.table语法而不是setkey.

df[, min(timestamp), by=id]

虽然 80k 唯一 ID 听起来很多,但使用 的key功能data.table可以使其成为可管理的前景。

setkey(df, id)

然后像以前一样处理。对于它的价值,您通常可以使用键的令人愉快的副作用,即排序。

set.seed(1)
dat <- data.table(x = sample(1:10, 10), y = c('a', 'b'))

    x y
 1:  3 a
 2:  4 b
 3:  5 a
 4:  7 b
 5:  2 a
 6:  8 b
 7:  9 a
 8:  6 b
 9: 10 a
10:  1 b

setkey(dat, y, x)

     x y
 1:  2 a
 2:  3 a
 3:  5 a
 4:  9 a
 5: 10 a
 6:  1 b
 7:  4 b
 8:  6 b
 9:  7 b
10:  8 b

那么这个min或另一个更复杂的函数只是一个子集操作:

dat[, .SD[1], by=y]
于 2013-01-29T19:42:40.457 回答
4

作为对Arun答案的补充,这里有一个与 OP(350 万行,80K ID)大小相似的数据集,表明键控/非键控聚合并没有太大的不同。所以加速可能是由于避免了$运营商。

set.seed(10)
eg <- function(x) data.table(id=sample(8e4,x,replace=TRUE),timestamp=as.POSIXct(runif(x,min=ISOdatetime(2013,1,1,0,0,0) - 60*60*24*30, max=ISOdatetime(2013,1,1,0,0,0)),origin="1970-01-01"))
df <- eg(3.5e6)
dfk <- copy(df)
setkey(dfk,id)
require(microbenchmark)
microbenchmark(
    unkeyed = df[,min(timestamp),by=id][,table(weekdays(V1))]
    ,keyed = dfk[,min(timestamp),by=id][,table(weekdays(V1))]
    ,times=5
)
#Unit: seconds
#     expr      min       lq   median       uq      max
#1   keyed 7.330195 7.381879 7.476096 7.486394 7.690694
#2 unkeyed 7.882838 7.888880 7.924962 7.927297 7.931368

从马修编辑。

实际上以上几乎完全与 type 有关POSIXct

> system.time(dfk[,min(timestamp),by=id])
   user  system elapsed 
   8.71    0.02    8.72 
> dfk[,timestamp:=as.double(timestamp)]  # discard POSIXct type to demonstrate
> system.time(dfk[,min(timestamp),by=id])
   user  system elapsed 
   0.14    0.02    0.15     # that's more like normal data.table speed

恢复到 POSIXct 并使用 Rprof 显示它在min()该类型内部的 97%(即与 无关data.table):

$by.total
                total.time total.pct self.time self.pct
system.time           8.70    100.00      0.00     0.00
[.data.table          8.64     99.31      0.12     1.38
[                     8.64     99.31      0.00     0.00
min                   8.46     97.24      0.46     5.29
Summary.POSIXct       8.00     91.95      0.86     9.89
do.call               5.86     67.36      0.26     2.99
check_tzones          5.46     62.76      0.20     2.30
unique                5.26     60.46      2.04    23.45
sapply                3.74     42.99      0.46     5.29
simplify2array        2.38     27.36      0.16     1.84
NextMethod            1.28     14.71      1.28    14.71
unique.default        1.10     12.64      0.92    10.57
lapply                1.10     12.64      0.76     8.74
unlist                0.60      6.90      0.28     3.22
FUN                   0.24      2.76      0.24     2.76
match.fun             0.22      2.53      0.22     2.53
is.factor             0.18      2.07      0.18     2.07
parent.frame          0.14      1.61      0.14     1.61
gc                    0.06      0.69      0.06     0.69
duplist               0.04      0.46      0.04     0.46
[.POSIXct             0.02      0.23      0.02     0.23

注意 的对象大小dfk

> object.size(dfk)
40.1 Mb

data.table对于这个小尺寸,什么都不应该花 7 秒!它需要大 100 倍(4GB),并且没有缺陷j然后您才能看到 keyed by 和 ad hoc by 之间的区别。

蓝色魔导师编辑:

考虑到 Matthew Dowle 的回答,键控/非键控命令之间存在差异。

df <- eg(3.5e6)
df[,timestamp := as.double(timestamp)]
dfk <- copy(df)
setkey(dfk,id)
require(microbenchmark)
microbenchmark(
    unkeyed = df[,min(timestamp),by=id][,table(weekdays(as.POSIXct(V1,origin="1970-01-01")))]
    ,keyed = dfk[,min(timestamp),by=id][,table(weekdays(as.POSIXct(V1,origin="1970-01-01")))]
    ,times=10
)
#Unit: milliseconds
#     expr      min       lq   median       uq       max
#1   keyed 340.3177 346.8308 348.7150 354.7337  358.1348
#2 unkeyed 886.1687 888.7061 901.1527 945.6190 1036.3326
于 2013-01-29T19:58:53.257 回答
2

这是一个 data.table 版本

dtBy <- function(dt)
    dt[, min(timestamp), by=id]

有点老派,这是一个返回每个组的最小值的函数

minBy <- function(x, by) {
    o <- order(x)
    by <- by[o]
    idx <- !duplicated(by)
    data.frame(by=by[idx], x=x[o][idx])
}

并且对于 BlueMagister 的样本数据似乎有合理的表现

> system.time(res0 <- dtBy(dt))
   user  system elapsed 
 11.165   0.216  11.894 
> system.time(res1 <- minBy(dt$timestamp, dt$id))
   user  system elapsed 
  4.784   0.036   4.836 
> all.equal(res0[order(res0$id),], res1[order(res1$by),],
+           check.attributes=FALSE)
[1] TRUE

从马修编辑(大部分)

是的,这是因为minBy避免min()了 POSIXct 类型,这是重复的非常慢的部分。这无关data.table

使用dfk来自 Blue M 的答案:

dfk[,timestamp:=as.double(timestamp)]  # discard POSIXct type to demonstrate

system.time(res0 <- dtBy(dfk))
   user  system elapsed 
   0.16    0.02    0.17 
system.time(res1 <- minBy(dfk$timestamp, dfk$id))
   user  system elapsed 
   4.87    0.04    4.92 

现在,与 data.table 相比,老式方法看起来非常慢。所有的时间都花在min()了 POSIXct 类型上。请参阅编辑 Arun 的Rprof输出答案。

于 2013-01-29T20:30:28.383 回答