在R中加速sqldf

时间:2014-07-08 22:05:28

标签: r algorithm sqldf

我在R中有一个程序,我已经运行了大约一天,它只完成了大约10%。缓慢的主要来源是必须使用R包sqldf(SELECT ...)从长度约为100万的数据集中进行数千次sqldf次调用。我的select语句目前采用以下形式:

sqldf(SELECT V1, V2, FROM mytable WHERE cast(start as real) <= sometime and cast(realized as real) > sometime)

sometime只是表示unix时间戳的整数,而startrealizedmytable的列,这些列也填充了unix时间戳条目。然而我还知道|realized - start| < 172800始终是,这是一个相当短的时期,因为数据集跨越一年。我的想法是,我应该能够利用这个事实告诉R只检查每个调用中时间+ - 172800的数据帧。

包裹sqldf不适合在这里使用吗?我应该使用data.frame的传统[,]遍历吗?是否有一种简单的方法可以将这一事实纳入加速计划?我的直觉是打破数据框架,对向量进行排序,然后构建自定义函数,遍历并自行选择相应的条目,但如果这是最好的方法,我正在寻找一些肯定。

1 个答案:

答案 0 :(得分:3)

首先,慢速部分可能是cast(...),因此在每个查询中,为什么不将startrealized作为时间戳,而不是每个记录执行两次,并更改查询以适应该目标。

其次,data.table选项的速度仍然快了约100倍(但是最后看到关于使用sqldf建立索引的位)。

library(sqldf)
library(data.table)

N <- 1e6
# sqldf option
set.seed(1)
df <- data.frame(start=as.character(as.POSIXct("2000-01-01")+sample(0:1e6,N,replace=T)),
                 realized=as.character(as.POSIXct("2000-01-01")+sample(0:1e6,N,replace=T)),
                 V1=rnorm(N), V2=rpois(N,4))

sometime <- "2000-01-05 00:00:00"
query <- "SELECT V1, V2 FROM df WHERE start <= datetime('%s') and realized > datetime('%s')"
query <- sprintf(query,sometime,sometime)          
system.time(result.sqldf <- sqldf(query))
#    user  system elapsed 
#   12.17    0.03   12.23 

# data.table option
set.seed(1)
DT <- data.table(start=as.POSIXct("2000-01-01")+sample(0:1e6,N,replace=T),
                 realized=as.POSIXct("2000-01-01")+sample(0:1e6,N,replace=T),
                 V1=rnorm(N), V2=rpois(N,4))
setkey(DT,start,realized)
system.time(result.dt <- DT[start<=as.POSIXct(sometime) & realized > as.POSIXct(sometime),list(V1,V2)])
#    user  system elapsed 
#    0.15    0.00    0.15 

请注意,两个结果集的排序方式不同。

编辑根据以下来自@ G.Grothendieck(sqldf包的作者)的评论。

这变成了对包的真正好的比较...

# code from G. Grothendieck comment
sqldf()      # opens connection
sqldf("create index ix on df(start, realized)")
query <- fn$identity("SELECT V1, V2 FROM main.df WHERE start <= '$sometime' and realized > '$sometime'")
system.time(result.sqldf <- sqldf(query))
sqldf()      # closes connection
#    user  system elapsed 
#    1.28    0.00    1.28 

因此,在这种情况下,创建索引会使sqldf的速度提高约10倍。索引创建速度很慢,但您只需要执行一次。 data.table中的“key”创建(对表进行物理排序)非常快,但在这种情况下并没有提高性能(仅约为2倍)。

使用system.time()进行基准测试有点冒险(1个数据点),因此最好使用microbenchmark(...)。请注意,为了实现此目的,我们必须运行上方的代码并保持连接打开(例如,删除最后一次调用sqldf()。)

f.dt    <- function() result.dt <- DT[start<=as.POSIXct(sometime) & realized > as.POSIXct(sometime),list(V1,V2)]
f.sqldf <- function() result.sqldf <- sqldf(query)
library(microbenchmark)
microbenchmark(f.dt(),f.sqldf())
# Unit: milliseconds
#       expr      min        lq    median       uq       max neval
#     f.dt() 110.9715  184.0889  200.0634  265.648  833.4041   100
#  f.sqldf() 916.8246 1232.6155 1271.6862 1318.049 1951.5074   100

因此,我们可以看到,在这种情况下,data.table使用密钥比使用索引的sqldf快约6倍。实际时间取决于结果集的大小,因此您可能希望比较这两个选项。