【问题标题】:Rolling count of exceedances by variable over time series in RR中随时间序列变化的超出量的滚动计数
【发布时间】:2017-02-02 18:01:23
【问题描述】:

我希望对超过阈值的多个实例站点进行滚动计数。

我的数据的简化版本:

        Dates SiteID Value
1  2015-04-01      A   9.1
2  2015-04-02      A   8.8
3  2015-04-02      A   7.9
4  2015-04-03      A   9.2
5  2015-04-08      A   9.3
6  2015-04-11      A   8.9
7  2015-04-11      A   9.2
8  2015-04-13      A   9.1
9  2015-04-16      A   7.8
10 2015-04-01      B   9.1
11 2015-04-01      B   8.8
12 2015-04-04      B   9.9
13 2015-04-05      B   7.8
14 2015-04-06      B   9.8
15 2015-04-06      B   9.2
16 2015-04-07      B   9.1
17 2015-04-08      B   8.5
18 2015-04-15      B   9.1

如果滚动期为 3 天且“价值”的阈值为 9,我正在寻找一个新列“超出”,它计算“价值”在过去 3 天内大于 9 的次数在给定的“SiteID”。所以这看起来像:

        Dates SiteID Value Exceedances
1  2015-04-01      A   9.1           1
2  2015-04-02      A   8.8           1
3  2015-04-02      A   7.9           1
4  2015-04-03      A   9.2           2
5  2015-04-08      A   9.3           1
6  2015-04-11      A   8.9           0
7  2015-04-11      A   9.2           1
8  2015-04-13      A   9.1           2
9  2015-04-16      A   7.8           0
10 2015-04-01      B   9.1           1
11 2015-04-01      B   8.8           1
12 2015-04-04      B   9.9           1
13 2015-04-05      B   7.8           1
14 2015-04-06      B   9.8           2
15 2015-04-06      B   9.2           3
16 2015-04-07      B   9.1           3
17 2015-04-08      B   8.5           3
18 2015-04-15      B   9.1           1

DT = structure(list(r = c(1L, 2L, 3L, 4L, 5L, 6L, 7L, 8L, 9L, 1L, 
2L, 3L, 4L, 5L, 6L, 7L, 8L, 9L), Dates = structure(c(16526, 16527, 
16527, 16528, 16533, 16536, 16536, 16538, 16541, 16526, 16526, 
16529, 16530, 16531, 16531, 16532, 16533, 16540), class = "Date"), 
    SiteID = c("A", "A", "A", "A", "A", "A", "A", "A", "A", "B", 
    "B", "B", "B", "B", "B", "B", "B", "B"), Value = c(9.1, 8.8, 
    7.9, 9.2, 9.3, 8.9, 9.2, 9.1, 7.8, 9.1, 8.8, 9.9, 7.8, 9.8, 
    9.2, 9.1, 8.5, 9.1), Exceedances = c(1L, 1L, 1L, 2L, 1L, 
    0L, 1L, 2L, 0L, 1L, 1L, 1L, 1L, 2L, 3L, 3L, 3L, 1L)), .Names = c("r", 
"Dates", "SiteID", "Value", "Exceedances"), row.names = c(NA, 
-18L), class = "data.frame")

我见过使用 data.table 和 deplyr 的类似问题,但没有一个解决计数超出阈值的问题。

最终这将应用于非常大的数据集,因此速度最快的方法会受到赞赏。如果这会对建议产生影响,我也会将其应用于滚动年份而不是上面的 3 天示例,并且数据集将包含“NA”。

【问题讨论】:

  • 第 5 行和第 18 行冲突:两者的值 > 9 并且在前三天没有 obs,但是一个得到 1 的结果,而另一个得到 0...?顺便说一句,如果您希望得到解决该案例的答案,您实际上应该展示一个带有 NA 的示例。
  • “日期”的顺序重要吗?因为,对于前 3 天的第 6 行和第 7 行,有 1 个值 > 9"。为什么这不计入第 6 行?
  • 我在问题中添加的structure 部分是为了使其可重现。请在进行更改时对其进行维护,或找到其他方法来保持它的可重复性。一些指导:stackoverflow.com/a/28481250

标签: r data.table dplyr


【解决方案1】:

由于行号似乎很重要,我们可以将其添加为列:

library(data.table)
setDT(DT)

DT[, r := rowid(SiteID)]
setcolorder(DT, c("r", setdiff(names(DT), "r")))

然后你可以做一个非等连接来计算满足条件的行:

DT[, v := 
  DT[.(SiteID = SiteID, rtop = r, d0 = Dates - 3, d1 = Dates), 
    on=.(SiteID, r <= rtop, Dates > d0, Dates <= d1), 
    sum(Value > 9), by=.EACHI]$V1
]


    r      Dates SiteID Value Exceedances v
 1: 1 2015-04-01      A   9.1           1 1
 2: 2 2015-04-02      A   8.8           1 1
 3: 3 2015-04-02      A   7.9           1 1
 4: 4 2015-04-03      A   9.2           2 2
 5: 5 2015-04-08      A   9.3           1 1
 6: 6 2015-04-11      A   8.9           0 0
 7: 7 2015-04-11      A   9.2           1 1
 8: 8 2015-04-13      A   9.1           2 2
 9: 9 2015-04-16      A   7.8           0 0
10: 1 2015-04-01      B   9.1           1 1
11: 2 2015-04-01      B   8.8           1 1
12: 3 2015-04-04      B   9.9           1 1
13: 4 2015-04-05      B   7.8           1 1
14: 5 2015-04-06      B   9.8           2 2
15: 6 2015-04-06      B   9.2           3 3
16: 7 2015-04-07      B   9.1           3 3
17: 8 2015-04-08      B   8.5           3 3
18: 9 2015-04-15      B   9.1           1 1

这里有一些潜在的问题:

  • 您多次计算天数,但可能只想知道#days,即uniqueN(x.Dates[Value &gt; 9]) 而不是sum(Value &gt; 9)
  • 我怀疑这里没有充分的理由关心行顺序。要删除该部分,只需排除有关 rrtop 的部分。

关于它的工作原理,也许可以查看the vignettes 和我的answer to a similar question here

【讨论】:

  • 嘿,我也想弄清楚顺序是否真的重要:)
  • @Frank,在与我的同事讨论后,我们决定日期顺序(行顺序)不是我们关心的问题。你的怀疑是正确的:)
【解决方案2】:

这是使用data.table 的答案。简单,可能很快。它使用shift 获取前两行的Value,将NAs 更改为零(对于每组中的前两个),为>9 提供1,为

library(data.table)
setDT(dt)    
dt[, newCol := ifelse(shift(Value, n=1, fill=0)>9, 1,0)+ ifelse(shift(Value, n=2, fill=0)>=, 1, 0)+ ifelse(Value>9, 1, 0), by=SiteID]

根据弗兰克的评论:

dt[, newCol := (shift(Value, n=1, fill=0)>9)+ (shift(Value, n=2, fill=0)>9) + (Value>9), by=SiteID]

也可以

【讨论】:

  • 不需要ifelse,直接加上逻辑就当做1/0了。试试(3 &gt; 4) + 2。此外,shift 可以采用值向量,例如 n=0:2
  • 我不认为在shift 中使用向量会起作用,因为需要添加 T/F 值并且不能使用 sum,它提供了整个列的总和。跨度>
  • 也许你可以使用Reduce(`+`, shift(...))
【解决方案3】:

考虑到“日期”列的顺序很重要,一种方法似乎是:

thres = 9; n = 3       
do.call(rbind, lapply(split(DT, DT$SiteID),
                      function(d) {
                          cs = cumsum(d$Value >= thres)
                          i = findInterval(d$Dates - (n - 1), d$Dates, left.open = TRUE)
                          cbind(d, exceed = cs - c(rep_len(0, sum(!i)), cs[i]))
                      }))
#     r      Dates SiteID Value Exceedances exceed
#A.1  1 2015-04-01      A   9.1           1      1
#A.2  2 2015-04-02      A   8.8           1      1
#A.3  3 2015-04-02      A   7.9           1      1
#A.4  4 2015-04-03      A   9.2           2      2
#A.5  5 2015-04-08      A   9.3           1      1
#A.6  6 2015-04-11      A   8.9           0      0
#A.7  7 2015-04-11      A   9.2           1      1
#A.8  8 2015-04-13      A   9.1           2      2
#A.9  9 2015-04-16      A   7.8           0      0
#B.10 1 2015-04-01      B   9.1           1      1
#B.11 2 2015-04-01      B   8.8           1      1
#B.12 3 2015-04-04      B   9.9           1      1
#B.13 4 2015-04-05      B   7.8           1      1
#B.14 5 2015-04-06      B   9.8           2      2
#B.15 6 2015-04-06      B   9.2           3      3
#B.16 7 2015-04-07      B   9.1           3      3
#B.17 8 2015-04-08      B   8.5           3      3
#B.18 9 2015-04-15      B   9.1           1      1

【讨论】:

    【解决方案4】:

    我们可以使用 sqldf 将问题表述为复杂的左连接:

    library(sqldf)
    
    sqldf("select a.*, sum(b.Value > 9) exceed
           from DT a
                left join DT b on a.SiteID = b.SITEID and 
                                  b.Dates > a.Dates - 3 and
                                  b.rowid <= a.rowid
           group by a.rowid")
    

    给予:

            Dates SiteID Value exceed
    1  2015-04-01      A   9.1      1
    2  2015-04-02      A   8.8      1
    3  2015-04-02      A   7.9      1
    4  2015-04-03      A   9.2      2
    5  2015-04-08      A   9.3      1
    6  2015-04-11      A   8.9      0
    7  2015-04-11      A   9.2      1
    8  2015-04-13      A   9.1      2
    9  2015-04-16      A   7.8      0
    10 2015-04-01      B   9.1      1
    11 2015-04-01      B   8.8      1
    12 2015-04-04      B   9.9      1
    13 2015-04-05      B   7.8      1
    14 2015-04-06      B   9.8      2
    15 2015-04-06      B   9.2      3
    16 2015-04-07      B   9.1      3
    17 2015-04-08      B   8.5      3
    18 2015-04-15      B   9.1      1
    

    【讨论】:

    • 仅供参考,您的结果看起来不像 OP 的。
    • 感谢您的快速回答,但正如其他人所指出的那样,此解决方案并未提供预期的结果。这是基于前 3 个条目而不是基于日期的前几天执行计数。这不起作用,因为某些天有多个条目,并且缺少天数的间隙。
    • @Mark 请更仔细地查看和/或解释您的预期输出。我认为还有一些不一致的地方,例如为什么第 14 行和第 15 行具有不同的值,即使它们属于同一日期?第 6 行和第 7 行同上。
    • @Frank Rows 6/7 不是我想要的。我已分别将它们更正为 0 和 1。你的问题仍然存在。预期输出包含同一日期的不同值,因为我预计它们会随着遇到其他行而增加。我现在看到一些解决方案可能不会以这种方式分析数据,而是在提供计数之前一起查找所有天数。
    • 已修改答案。
    猜你喜欢
    • 1970-01-01
    • 2021-02-06
    • 1970-01-01
    • 1970-01-01
    • 2013-01-26
    • 1970-01-01
    • 2011-03-26
    • 1970-01-01
    • 2014-02-02
    相关资源
    最近更新 更多