我们可以将隐含的有限状态机编码为正则表达式。这不使用任何包,具有直接的逻辑并且运行迅速(参见基准)。
使用末尾注释中可重复定义的输入(我们已将问题的输入扩展为有两个延伸)创建一个指标 ind,即
- 0 表示小于 x,
- 1 如果等于或大于 x 但小于 y 并且
- 2 表示大于或等于 y。
然后将其转换为字符串并使用 gregexpr 查找从 0 开始的由 0 和 1 组成的延伸。
ind <- (DF2$value >= x) + (DF2$value >= y)
g <- gregexpr("0[01]*", paste(ind, collapse = ""))[[1]]
if (min(g) == -1) g <- c()
res <- data.frame(start = as.integer(g), end = as.integer(g) + attr(g, "match.length"))
给予:
res
## start end
## 1 3 12
## 2 16 25
问题没有指定输出的形式,所以如果你想要一个 0/1 向量,那么这会将上面的输出转换为这样的向量:
with(res, sapply(seq_along(ind), function(i) +any(i >= start & i <= end)))
## [1] 0 0 1 1 1 1 1 1 1 1 1 1 0 0 0 1 1 1 1 1 1 1 1 1 1 0
基准测试
如果数据很大并且性能受到关注,那么这些运行速度非常快。
library(microbenchmark)
library(purrr)
library(dplyr)
microbenchmark(
regex = {
ind <- (DF2$value >= x) + (DF2$value >= y)
g <- gregexpr("0[01]*", paste(ind, collapse = ""))[[1]]
data.frame(start = as.integer(g), end = as.integer(g) + attr(g, "match.length"))
},
regex2 = {
ind <- (DF2$value >= x) + (DF2$value >= y)
g <- gregexpr("0[01]*", paste(ind, collapse = ""))[[1]]
res <- data.frame(start = as.integer(g), end = as.integer(g) + attr(g, "match.length"))
with(res, sapply(seq_along(ind), function(i) +any(i >= start & i <= end)))
},
accum = {
DF2 %>%
mutate(counter = accumulate(value,.init = FALSE,
~{ if (.y <= x & !.x) {TRUE} else if (.y <= y ) {.x} else FALSE })[-1])
})
## Unit: milliseconds
## expr min lq mean median uq max neval cld
## regex 1.0019 1.10215 1.209319 1.19970 1.24970 2.3949 100 a
## regex2 1.3651 1.46005 1.599009 1.54315 1.65880 2.8078 100 b
## accum 8.8840 9.95140 10.492953 10.34490 10.86335 13.5756 100 c
注意
我们扩展了输入,以便匹配两个拉伸。
DF <- structure(list(row = 1:13, timestamp = c("2018-01-11 11:23:56",
"2018-01-11 11:24:00", "2018-01-11 11:24:04", "2018-01-11 11:24:08",
"2018-01-11 11:24:11", "2018-01-11 11:24:15", "2018-01-11 11:24:19",
"2018-01-11 11:24:23", "2018-01-11 11:24:27", "2018-01-11 11:24:31",
"2018-01-11 11:24:35", "2018-01-11 11:24:39", "2018-01-11 11:24:43"
), value = c(49.829, 49.803, 49.793, 49.813, 49.844, 49.83, 49.792,
49.777, 49.81, 49.843, 49.867, 49.913, 49.925)),
class = "data.frame", row.names = c(NA, -13L))
DF2 <- rbind(DF, DF)
x <- 49.8
y <- 49.9
刚刚定义的 DF2 只是 DF 的两个副本。请注意,我们不使用上面的行和时间戳,所以我们专注于价值。
> DF2
row timestamp value
1 1 2018-01-11 11:23:56 49.829
2 2 2018-01-11 11:24:00 49.803
3 3 2018-01-11 11:24:04 49.793
4 4 2018-01-11 11:24:08 49.813
5 5 2018-01-11 11:24:11 49.844
6 6 2018-01-11 11:24:15 49.830
7 7 2018-01-11 11:24:19 49.792
8 8 2018-01-11 11:24:23 49.777
9 9 2018-01-11 11:24:27 49.810
10 10 2018-01-11 11:24:31 49.843
11 11 2018-01-11 11:24:35 49.867
12 12 2018-01-11 11:24:39 49.913
13 13 2018-01-11 11:24:43 49.925
14 1 2018-01-11 11:23:56 49.829
15 2 2018-01-11 11:24:00 49.803
16 3 2018-01-11 11:24:04 49.793
17 4 2018-01-11 11:24:08 49.813
18 5 2018-01-11 11:24:11 49.844
19 6 2018-01-11 11:24:15 49.830
20 7 2018-01-11 11:24:19 49.792
21 8 2018-01-11 11:24:23 49.777
22 9 2018-01-11 11:24:27 49.810
23 10 2018-01-11 11:24:31 49.843
24 11 2018-01-11 11:24:35 49.867
25 12 2018-01-11 11:24:39 49.913
26 13 2018-01-11 11:24:43 49.925