关于大数据的另一个新手问题。我正在使用带有时间序列数据的大型数据集(3.5米行)。我想创建一个data.table
,其中的列会在第一次显示唯一标识符时找到。
df是data.table
,df$timestamp
是类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很窒息。我能做些什么来优化我的方法?
答案 0 :(得分:6)
这里有一些代码可以测试哪种行为需要 LOT 时间
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
答案 1 :(得分:5)
正如@Arun所提到的,真正的密钥(没有双关语)是使用正确的data.table
语法而不是setkey
。
df[, min(timestamp), by=id]
虽然80k的独特ID听起来很多,但使用key
的{{1}}功能可以使其成为一个可管理的前景。
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
答案 2 :(得分:4)
作为Arun的answer的补充,这里的数据集大小与OP(3.5M行,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
马修编辑。
实际上,上述内容几乎与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
,然后你可以看到键入和ad hoc之间的区别。
从Blue Magister编辑:
考虑到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
答案 3 :(得分: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
避免了POSIXct类型的min()
,这是重复的非常缓慢的部分。这与data.table
无关。
使用Blue M答案中的dfk
:
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
输出的答案的编辑。