【问题标题】:Speeding up sqldf in R在 R 中加速 sqldf
【发布时间】:2014-07-08 22:05:27
【问题描述】:

我在 R 中有一个程序,我已经运行了大约一天,它只完成了大约 10%。缓慢的主要来源是必须使用 R 包 sqldf 从长度约为 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| &lt; 172800 总是如此,这是一个相当小的时期,因为数据集跨越了一年。我的想法是,我应该能够利用这一事实告诉 R 在每个调用中仅从时间 +-172800 检查数据帧。

sqldf 包不适合在这里使用吗?我应该使用data.frame 的传统[,] 遍历吗?有没有一种简单的方法可以结合这个事实来加速程序?我的直觉是分解数据框,对向量进行排序,然后构建自定义函数来遍历并自行选择适当的条目,但如果这是最好的方法,我正在寻找一些确认。

【问题讨论】:

  • 您可能需要考虑使用data.table 包,该包允许您为表构建真正的索引,因此搜索速度更快。
  • 除了上述非常一般的建议之外,真的没有任何人可以提供帮助,因为我们没有坐在您的计算机前查看所有您的代码。
  • 我明白你的意思。我对有关 sqldf 搜索如何工作的任何信息以及它们是否是一种将我所拥有的条件纳入加速搜索的方法更感兴趣。似乎他们应该是一种告诉 R 只从特定索引中搜索的方法,我现在正在研究 data.table 包。
  • 尝试摆脱强制转换,以便索引可以工作并添加索引。 sqldf 主页上有示例。否则,请尝试 data.table。
  • data.table 不会“建立真正的索引”。见this question/answer。不过速度更快。

标签: r algorithm sqldf


【解决方案1】:

首先,慢速部分可能是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 包的作者)的 cmets。

这正在变成一个非常好的软件包比较......

# 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 中创建“键”(对表进行物理排序)非常快,但在这种情况下并没有提高性能(只有大约 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 倍。实际时间取决于结果集的大小,因此您可能需要比较这两个选项。

【讨论】:

  • data.table 可能更快,但我怀疑它快得多。该基准严重偏向于 data.table,因为(1)它使用 data.table 的索引而不是 sqldf,(2)它排除了创建索引的时间,并且可能(3)由于两者的运行顺序.如果我在 sqldf 上创建一个索引并且可比较地从时间中排除该时间,那么 data.table 需要 0.05 秒,而 sqldf 需要 0.11 秒。
  • 这里是一些改进的代码:sqldf(); sqldf("create index ix on df(start, realized)"); query &lt;- fn$identity("SELECT V1, V2 FROM main.df WHERE start &lt;= '$sometime' and realized &gt; '$sometime'"); system.time(result.sqldf &lt;- sqldf(query)); sqldf()
  • @G.Grothendieck 你的观点 (1) 很好。请参阅上面的修改。
  • 天哪,data.table 运行良好。我的程序刚刚在 20 分钟内完成。非常感谢你。以后我会记得在转换和调用 sqldf 时要小心。我不知道这两个电话有多么昂贵。
猜你喜欢
  • 2017-06-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-05-27
  • 2014-05-24
  • 1970-01-01
  • 2015-03-11
  • 2019-01-13
相关资源
最近更新 更多