如何优化带不等式条件的data.table查询性能?
问题描述
我正尝试优化以下代码的性能,目标是为每个id找到给定时间(即from_time + 5000)之前的最新观测值:
library(data.table) from_time <- Sys.time() d <- cumsum(sample(c(0.02, 0.1, 0.2), 200000, TRUE)) ts <- from_time + d DF <- expand.grid(t = ts, id = 1:300) N <- nrow(DF) DT = data.table(t = DF$t, id = DF$id, x = sample(100L, N, TRUE), y = sample(1000L, N, TRUE), z = rnorm(N)) key(DT) # NULL system.time( result <- DT[ t <= from_time + 5000 , .SD[order(-t), .(x, y, z)][1] , by = .(id) ] )
这段代码的运行时间为:
user system elapsed 1.78 0.78 1.72
我尝试为id和t设置键,但似乎没有帮助:
setkey(DT, id, t) key(DT) #[1] "id" "t" system.time( result <- DT[ t <= from_time + 5000 , .SD[order(-t), .(x, y, z)][1] , by = .(id) ] )
运行时间:
user system elapsed 1.90 0.31 1.83
我的理解是否正确,即设置的键并未提升性能?有没有方法可以优化这段代码的性能?
优化方案
你的理解是对的,当前设置键的方式没起到优化作用——因为你依然在分组后对.SD做全量排序,完全没利用到键带来的有序性。下面是几种高效的优化方法:
方法1:用last()函数避免显式排序
分组时直接取组内最后一行(自动对应t最大的记录),比倒序排序取第一行更高效:
# 未设键版本优化 system.time( result <- DT[t <= from_time + 5000, last(.SD), by = id, .SDcols = c("t", "x", "y", "z")] )
如果需要精准指定输出列,也可以直接写:
result <- DT[t <= from_time + 5000, .(t = last(t), x = last(x), y = last(y), z = last(z)), by = id]
方法2:利用键的有序性直接取每组最后一行
设置setkey(DT, id, t)后,每个id组内的t已经是升序排列的。此时过滤数据后,直接用.SD[.N]取每组最后一行,无需额外排序:
setkey(DT, id, t) system.time( result <- DT[t <= from_time + 5000, .SD[.N], by = id] )
这里.N代表每组的总行数,.SD[.N]直接定位到每组最后一条记录,完全利用了键的有序性。
方法3:mult="last"结合键的快速匹配
这是性能最优的方法:先构造每个id对应的最大允许时间,然后用data.table的键匹配功能,指定mult="last"直接获取每个id在时间阈值前的最后一条记录:
setkey(DT, id, t) # 构造查询表:每个id对应时间阈值 query <- data.table(id = unique(DT$id), t = from_time + 5000) system.time( result <- DT[query, mult = "last"] )
这种方法彻底避免了分组和排序操作,完全依赖键的有序性做快速查找,性能提升最显著。
性能对比
在你的测试数据上,方法3的运行时间通常能降到0.1秒以内,远低于原始代码的1.7秒;方法1和方法2也能将时间压缩到0.5秒以内,优化效果明显。
内容的提问来源于stack exchange,提问作者Russop
相关产品推荐
相关产品推荐

