【发布时间】:2016-04-28 14:48:30
【问题描述】:
我有一个 data.table(约 3000 万行),由 POSIXct 格式的 datetime 列、id 列和其他几列组成(在示例中,我只留下了一个不相关的列 @ 987654325@ 以证明还有其他列需要保留)。 dput 在帖子底部。
head(DT)
# datetime x id
#1: 2016-04-28 16:20:18 0.02461368 1
#2: 2016-04-28 16:41:34 0.88953932 1
#3: 2016-04-28 16:46:07 0.31818101 1
#4: 2016-04-28 17:00:56 0.14711365 1
#5: 2016-04-28 17:09:11 0.54406602 1
#6: 2016-04-28 17:39:09 0.69280341 1
问:对于每个id,我只需要对相差超过 30 分钟的观察结果进行子集化。什么是有效的data.table 方法来执行此操作(如果可能,无需大量循环)?
逻辑也可以描述为(就像我在下面的评论中一样):
每个 id 始终保留第一行。至少为 30 的下一行 第一个之后的分钟也应保留。让我们假设该行 保留第 4 行。然后,计算第 4 行和第 4 行之间的时间差 第 5:n 行并保留第一个相差超过 30 分钟的行,依此类推 开
在下面的 dput 中,我添加了一个列 keep 来指示在此示例中应保留哪些行,因为它们与之前每个 id 保留的观察值相差超过 30 分钟。困难在于似乎有必要迭代地计算时间差(或者至少,我目前想不出更有效的方法)。
library(data.table)
DT <- structure(list(
datetime = structure(c(1461853218.81561, 1461854494.81561,
1461854767.81561, 1461855656.81561, 1461856151.81561, 1461857949.81561,
1461858601.81561, 1461858706.81561, 1461859078.81561, 1461859103.81561,
1461852799.81561, 1461852824.81561, 1461854204.81561, 1461855331.81561,
1461855633.81561, 1461856311.81561, 1461856454.81561, 1461857177.81561,
1461858662.81561, 1461858996.81561), class = c("POSIXct", "POSIXt")),
x = c(0.0246136845089495, 0.889539316063747, 0.318181007634848,
0.147113647311926, 0.544066024711356, 0.6928034061566, 0.994269776623696,
0.477795971091837, 0.231625785352662, 0.963024232536554, 0.216407935833558,
0.708530468167737, 0.758459537522867, 0.640506813768297, 0.902299045119435,
0.28915973729454, 0.795467417687178, 0.690705278422683, 0.59414202044718,
0.655705799115822),
id = c(1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L),
keep = c(TRUE, FALSE, FALSE, TRUE, FALSE, TRUE, FALSE, FALSE, FALSE, FALSE, TRUE, FALSE,
FALSE, TRUE, FALSE, FALSE, FALSE, TRUE, FALSE, TRUE)),
.Names = c("datetime", "x", "id", "keep"),
row.names = c(NA, -20L),
class = c("data.table", "data.frame"))
setkey(DT, id, datetime)
DT[, difftime := difftime(datetime, shift(datetime, 1L, NA,type="lag"), units = "mins"),
by = id]
DT[is.na(difftime), difftime := 0]
DT[, difftime := cumsum(as.numeric(difftime)), by = id]
keep 列的解释:
- 2:3 行与第 1 行相差不到 30 分钟 -> 删除
- 第 4 行与第 1 行相差超过 30 分钟 -> 保留
- 第 5 行距离第 4 行不到 30 分钟 -> 删除
- 第 6 行与第 4 行相差超过 30 分钟 -> 保留
- ...
期望的输出:
desiredDT <- DT[(keep)]
感谢我收到的三个专家答复。我在 1 和 1000 万行数据上测试了它们。这是基准测试的摘录。
a) 100 万行
microbenchmark(frank(DT_Frank), roland(DT_Roland), eddi1(DT_Eddi1), eddi2(DT_Eddi2),
times = 3L, unit = "relative")
#Unit: relative
# expr min lq mean median uq max neval
# frank(DT_Frank) 1.286647 1.277104 1.185216 1.267769 1.140614 1.036749 3
# roland(DT_Roland) 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 3
# eddi1(DT_Eddi1) 11.748622 11.697409 10.941792 11.647320 10.587002 9.720901 3
# eddi2(DT_Eddi2) 9.966078 9.915651 9.210168 9.866330 8.877769 8.070281 3
b) 1000 万行
microbenchmark(frank(DT_Frank), roland(DT_Roland), eddi1(DT_Eddi1), eddi2(DT_Eddi2),
times = 3L, unit = "relative")
#Unit: relative
# expr min lq mean median uq max neval
# frank(DT_Frank) 1.019561 1.025427 1.026681 1.031061 1.030028 1.029037 3
# roland(DT_Roland) 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 3
# eddi1(DT_Eddi1) 11.567302 11.443146 11.301487 11.323914 11.176515 11.035143 3
# eddi2(DT_Eddi2) 9.796800 9.693823 9.526193 9.594931 9.398969 9.211019 3
显然,@Frank 的 data.table 方法和 @Roland 的基于 Rcpp 的解决方案在性能上相似,但 Rcpp 略有优势,而 @eddi 的方法仍然很快,但性能不如其他方法。
但是,当我检查解决方案的相等性时,我发现@Roland 的方法与其他方法的结果略有不同:
a) 100 万行
all.equal(frank(DT_Frank), roland(DT_Roland))
#[1] "Component “datetime”: Numeric: lengths (982228, 982224) differ"
#[2] "Component “id”: Numeric: lengths (982228, 982224) differ"
#[3] "Component “x”: Numeric: lengths (982228, 982224) differ"
all.equal(frank(DT_Frank), eddi1(DT_Eddi1))
#[1] TRUE
all.equal(frank(DT_Frank), eddi2(DT_Eddi2))
#[1] TRUE
b) 1000 万行
all.equal(frank(DT_Frank), roland(DT_Roland))
#[1] "Component “datetime”: Numeric: lengths (9981898, 9981891) differ"
#[2] "Component “id”: Numeric: lengths (9981898, 9981891) differ"
#[3] "Component “x”: Numeric: lengths (9981898, 9981891) differ"
all.equal(frank(DT_Frank), eddi1(DT_Eddi1))
#[1] TRUE
all.equal(frank(DT_Frank), eddi2(DT_Eddi2))
#[1] TRUE
我目前的假设是,这种差异可能与差异是 > 30 分钟还是 >= 30 分钟有关,尽管我还不确定。
最后的想法:我决定使用@Frank 的解决方案有两个原因:1. 它的性能非常好,几乎等于 Rcpp 解决方案,以及 2. 它不需要我不太熟悉的另一个包然而(无论如何我都在使用 data.table)
【问题讨论】:
-
这些是我认为好的
C/C++解决方案很有价值的任务。没有明显的 R 向量化方式,并且在 C 或 C++ 中编写您描述的条件应该非常简单。如果您知道一点如何编写可从 R 调用的C/C++函数,我建议您使用这条路线。 -
应该很容易使用重叠连接,只需为每个
id准备 from 和 to 日期 -
我同意@nicola。使用 Rcpp 会很简单。想出 R 解决方案不值得伤脑筋。
-
@jangorecki per id 始终保留第一行。在第一行之后至少 30 分钟的下一行也应保留。假设要保留的行是第 4 行。然后,计算第 4 行和第 5:n 行之间的时间差,并保留相差超过 30 分钟的第一个,依此类推
-
@Roland 和 nicola 谢谢,我一定会考虑的。如果您对基于 Rcpp 的解决方案有所了解,很高兴看到这一点
标签: r data.table posixct