【问题标题】:Optimizaion of calculating how many data.frame record timestamps appear in a one second window from every record timestamp in that data.frame从该data.frame中的每个记录时间戳计算一秒窗口中出现多少个数据帧记录时间戳的优化
【发布时间】:2016-03-13 02:13:46
【问题描述】:

我有一个包含许多列的 R data.frame,其中一个包含 POSIXct 时间戳记录。我想向 data.frame 添加一列,对于每一行,该列包含时间戳介于该行的时间戳和未来一秒之间的记录数。

以下代码实现了这一点,但我正在处理的数据(通常是 60K+ 记录)确实很慢。我想知道是否有办法加快速度。

# Create a data frame with POSIXct values spread over a few minutes.
# The actual number of records can be over 60,000.
set.seed(1234)
times <- as.POSIXct("2015-02-18 11:39:17.104206 AEDT") + 
    runif(n = 10000, min = -5*60, max = 5*60)
times <- sort(times) # my source data comes to me sorted
times <- data.frame(datetime = times)

# For each event (timestamp), calculate how many events (timestamps) appear in
# a one second window following that event.
system.time(
for (i in 1:length(times$datetime)) {
        times$eventCount[i] <- sum(
                times$datetime >= times$datetime[i] & 
                times$datetime < times$datetime[i] + 1)
}
)

我系统上的结果是:

user  system elapsed
8.10    0.00    8.21

有趣的是,处理时间并不会随着记录的数量呈线性变化。对于 20K 条记录,用户时间为 24.74 秒。

查看类似的问题(例如 this 和其中引用的问题)会建议使用 data.table 可以大大加快速度,但我无法弥合这些答案中代码之间的差距(看起来在给定记录的任一侧固定数量的记录)到我需要的(查看给定记录一侧的未知数量的记录)。

rcpp 看起来会是最好的方法,但我根本不懂任何 c++。

任何帮助表示赞赏!

【问题讨论】:

  • 我想知道是否进行二分搜索,将间隔减半直到 1 秒窗口内的最大项目会更好?
  • 我添加了反映@BondedDust 建议的Rcpp 实现。速度很快。
  • 强大的工作@Khashaa。自从我对您的第一次努力投了赞成票以来,我无法再添加任何赞成票。你有我的尊重。
  • 谢谢你,@BondedDust。我受宠若惊。

标签: r optimization dataframe data.table self-reference


【解决方案1】:

基于@Kashaa 的 Rcpp 解决方案的更简单逻辑。

数据

require(dplyr)
require(data.table)
set.seed(1234L)
dt = data.table(datetime=as.POSIXct("2015-02-18 11:39:17.104206 AEDT") + 
    runif(n = 100000, min = -5*60, max = 5*60), key="datetime")
df = as.data.frame(dt)

data.table 解决方案

setNumericRounding(0L)
betweendt <- function(x, col, eps) {
    idx1 = dt[.(col), mult="first", roll=-Inf, which=TRUE]
    idx2 = dt[.(col+1-eps-unclass(col)*eps), 
                mult="last", roll=Inf, which=TRUE]
    idx2-idx1+1L
}
system.time({
dt[, eventC := betweendt(dt, dt$datetime, .Machine$double.eps)]
})
#    user  system elapsed 
#   0.043   0.001   0.045 

Rcpp 版本(来自@Khashaa)

system.time({
  col = df$datetime
  df <- df %>% 
    rowwise() %>% 
    mutate(eventC = betweenCpp(col, datetime, datetime+1)) 
})
#    user  system elapsed 
#   0.142   0.001   0.142 

identical(df$eventC, dt$eventC)
# [1] TRUE

data.table 解决方案在这里的速度提高了约 3 倍。


使用foverlaps() 参考旧版本的历史记录(这有点过头了)。

【讨论】:

  • @Arun,非常感谢你。这绝对会杀死它。我的 20k 示例运行时您的代码在 0.16 秒从超过 27 秒下降,而原始 10k 示例从 7.6 秒变为 0.04 秒。一个令人震惊的迷宫。当我想到 data.table 时,我内心有一种温暖的感觉是不健康的吗?感谢您的工作并在此回复。
  • @Khashaa - 我对这段代码的第一次尝试也失败了多达五次,但是当我setNumericRounding(0) 时,结果是相同的。
  • 这看起来不错,但是在尝试将新代码包装在一个函数中以用于 rbenchmark::benchmark 函数时,我得到一个 Error in betweendt(dt, dt$datetime, .Machine$double.eps) : could not find function "." 错误。奇怪的是,如果我直接在控制台中运行代码,我不会收到此错误。这是我应该使用开发版 data.table 的地方吗?
【解决方案2】:

试试

library(dplyr)
dt <- times$datetime
system.time({
times <- times %>% 
  rowwise() %>% 
  mutate(eventCount = sum(between(dt, datetime, datetime+1))) 
})

在 AWS 免费套餐中,

user  system elapsed 
3.309   0.048   3.358 

[编辑]

显然,来自dplyr 的between 相当慢。在 Rcpp 中实现这一步很容易,而且非常富有成效。 betweenCpp(v,x,y) 的行为类似于 sum(between(v, x, y)),即计算位于 x 和 y 之间的 'v' 元素。

Rcpp::cppFunction('int betweenCpp(NumericVector v, double x, double y) {
                  NumericVector::iterator low1, low2;
                  low1=std::lower_bound (v.begin(), v.end(), x);
                  low2=std::lower_bound (v.begin(), v.end(), y);
                  return (low2 - low1);
                  }')

现在,我们可以使用 Rcpp 的强大功能,在我的普通笔记本电脑上运行 n=100.000 需要 0.28 秒。

dt <- times$datetime
system.time({
  times <- times %>% 
    rowwise() %>% 
    mutate(eventC = betweenCpp(dt, datetime, datetime+1)) 
})

[附录]

如果你想要额外的速度,你可以做完整的 Rcpp 实现。

#include <Rcpp.h>
#include <algorithm>
using namespace Rcpp;

int betweenCpp(NumericVector v, double x, double y) {
  NumericVector::iterator low1, low2;
  low1=std::lower_bound (v.begin(), v.end(), x);
  low2=std::lower_bound (v.begin(), v.end(), y);
  return (low2 - low1);
}
// [[Rcpp::export]]
NumericVector EventCountCpp(NumericVector x) {
  int n=x.size();
  NumericVector count(n);
  for (int i = 0; i < n; i++) {
    count[i]=betweenCpp(x, x[i], x[i]+1);
  }
  return(count);
}

将此作为count.cpp 保存在您的工作目录中,然后为n=100.000 花费0.01 秒。

sourceCpp("count.cpp")
system.time(times$EventCount <- EventCountCpp(times$datetime))

【讨论】:

  • 看起来不错。在我的机器上,我改进的版本在 6 秒内运行,而你的改进版本在 2.4 秒内运行。
  • 伙伴,那太好了。对于 10,000 行版本,我得到 2.08 秒。减少了大约 75%!谢谢。您认为访问 data.table 是否还有更多内容?我问是因为我处理的一些文件用我的代码需要 30 多分钟,即使使用你的 dplyr 代码,也需要 5 分钟以上。
  • 对于 20K 版本,我得到 12.4 秒。所以似乎仍然是二次缩放的,......但是从改进的基线开始。对于 40K,它是 90.3 对 54.9
  • @vorpalwabbit 我不知道 data.table 选项。你考虑过并行化吗?
  • @BondedDust 我没有尝试过大值,但看起来搜索确实很昂贵。
【解决方案3】:

当 hte 数量增加时,它对我来说会增加一倍以上。我虽然通过避免所有使用“$”访问无效的数据框,也许我可以获得更好的性能,但我确实看到了一种节省时间的方法。您不应该测试比当前时间少的次数,因为您知道 (i-1) 的答案已经给定数据的排序性质。只需记录前1秒内的项目数。 (我的处理器和你的差不多,所以这个结果实际上比第一个快 25%:

system.time( {dt <- times$datetime
 for (i in 1:length(dt)) {
         eventCount[i] <- sum(
                 tail(dt, 10000-i) < dt[i] + 1)
 }}
 )
   user  system elapsed 
  5.410   0.716   6.042 

【讨论】:

  • 如果我没记错的话,你的解决方案和 OP 的结果是不同的。
  • 有多大不同?我得到的平均值与 1.0 (17.66 and 16.66) 完全不同。我认为这些值恰好移动了一个单位。因为最小值在一种情况下为 0,最大值为 36,在另一种情况下为 1 和 37。
  • 感谢 BondedDust。稍微修改代码(需要在计数中加一)我得到 10,000 条记录示例的 4.75 秒。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2022-12-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-04-26
  • 2022-11-12
  • 1970-01-01
相关资源
最近更新 更多