Algorithm 有效累积大数据集的滑动窗口百分比变化

Algorithm 有效累积大数据集的滑动窗口百分比变化,algorithm,dataset,moving-average,sliding-window,Algorithm,Dataset,Moving Average,Sliding Window,我有几百万个数据点,每个数据点都有一个时间和一个值。我感兴趣的是了解所有滑动窗口(即,4000个数据点的块),其中窗口从高到低的范围超过了一个恒定的阈值 例如:,假设一个窗口的长度为3,阈值的高-低>3。然后序列:[101214131011414141417]将导致[0,2,4,5],因为这些是三个时段窗口的高-低范围超过阈值的索引 我的窗口大小为4000,数据集大小为数百万 天真的方法是只计算每一个可能的窗口范围,即1-4000、2-4001、3-4002等,然后累积那些超出阈值的集合。对于大

我有几百万个数据点,每个数据点都有一个时间和一个值。我感兴趣的是了解所有滑动窗口(即,4000个数据点的块),其中窗口从高到低的范围超过了一个恒定的阈值

例如:,假设一个窗口的长度为3,阈值的高-低>3。然后序列:[101214131011414141417]将导致[0,2,4,5],因为这些是三个时段窗口的高-低范围超过阈值的索引

我的窗口大小为4000,数据集大小为数百万

天真的方法是只计算每一个可能的窗口范围,即1-4000、2-4001、3-4002等,然后累积那些超出阈值的集合。对于大型数据集来说,这可能需要很长时间

因此,我认为更好的算法如下:

计算第一个窗口的范围(1-4000),并存储窗口范围的高/低索引。然后,迭代到(2-4001,3-4002)等。仅当窗口最右侧的新值高于/低于旧缓存值时,才更新高/低索引

现在,假设1-4000窗口的高/低索引分别为333和666。我迭代并继续更新右侧的新高/低值,但只要窗口位于334-4333(只要缓存的高/低值在当前窗口之外),我就会重新计算当前窗口(334-4333)的高/低值,缓存并继续迭代

我的问题是:

1.)是否有一个数学公式可以完全消除算法的需要?我知道有一些计算窗口期加权和指数移动平均数的公式,不需要重新计算窗口

2)我的算法合理吗?精确的有没有一种方法可以大大简化或改进


非常感谢。

有一些算法可以在滑动窗口中保持最小(或最大)值,每个元素的摊销复杂性O(1)(所有数据集的O(N))。这是其中一个使用Deque数据结构,其中包含值/索引对。对于Min和Max,您必须保留两个DEQUE(最大长度为4000)

每一步:
如果(!Deque.Empty)和(Deque.Head.Index CurrentValue)执行
德克。提取器尾;
//删除窗口中没有机会成为最小值的元素
Deque.AddTail(CurrentValue,CurrentIndex);
CurrentMin=Deque.Head.Value
//头值是当前窗口中的最小值

以下是用于此目的的python代码:

import heapq

l = [10,12, 14, 13, 10, 11, 16, 14, 17]
w = 3
threshold = 3
breached_indexes = []


#set up the heap for the initial window size
min_values = [(l[i], i) for i in range(0,w)]
max_values = [(-l[i], i) for i in range(0,w)]
heapq.heapify(min_values)
heapq.heapify(max_values)

#check if first window violates the add the index
if (threshold <= -max_values[0][0] - min_values[0][0]):
        breached_indexes.append(0)

for i in range(1, len(l)-w+1):
    #remove all elements before the current index
    while min_values[0][1] < i:
        heapq.heappop(min_values)

    while max_values[0][1] < i:
        heapq.heappop(max_values)

    #check the breach
    if (threshold <= -max_values[0][0] - min_values[0][0]):
        breached_indexes.append(i)

    if (i+w >= len(l)):
        break

    #push the next element entering the window
    heapq.heappush(min_values, (l[i+w], i+w))
    heapq.heappush(max_values, (-l[i+w], i+w))

print breached_indexes
导入heapq
l=[10,12,14,13,10,11,16,14,17]
w=3
阈值=3
违反的_索引=[]
#为初始窗口大小设置堆
最小值=[(l[i],i)对于范围(0,w)内的i]
最大_值=[(-l[i],i)范围内的i(0,w)]
heapq.heapify(最小值)
heapq.heapify(最大值)
#检查第一个窗口是否违反添加索引的规则

如果(threshold朴素的版本不是线性的。线性将是O(n)。朴素的算法是O(n*k),其中k是窗口大小。在最坏的情况下,您的改进也是O(n*k)(想象一个排序数组),但在一般情况下,您应该会看到运行时间有很大的改进,因为您将避免大量的重新计算

在O(n log k)中,您可以通过使用a(或两个堆)来解决这个问题,但是您必须使用一种可以删除O(log k)中任意节点的堆。您不能使用标准二进制堆,因为尽管删除任意节点是O(log k),但查找节点是O(k)

假设您有一个最小-最大堆,算法如下所示:

heap = create empty heap
add first k items to the heap
for (i = k; i < n-k; ++i)
{
    if (heap.MaxItem - heap.MinItem) > threshold
        output range
    remove item i-k from the heap
    add item i to the heap
}
queue = create empty queue of heap nodes
heap = create empty heap
for (i = 0; i < k; ++i)
{
    node = heap.Add(array[i]);
    queue.Add(node);
}
for (i = k; i < n-k; ++i)
{
    if (heap.MaxItem - heap.MinItem) > threshold
        output range
    node = queue.Dequeue()
    remove item at position node.Index from the heap
    node = heap.Add(array[i])
    queue.Add(node)
}
    1  2  3  4  5  6  7  8  9

    1  1  1  1
    x  2  2  2  2
    x  x  3  3  3  3
    x  x  x  4  4  4  4
                5  5  5  5
                   6  6  6  6
                      7  7  7
                         8  8
                            9
heap=创建空堆
将前k个项目添加到堆中
对于(i=k;i阈值
输出范围
从堆中删除项i-k
将项目i添加到堆中
}
当然,问题是从堆中删除项i-k。实际上,问题是如何高效地找到它。我过去这样做的方式是修改二进制堆,使其存储包含索引和值的节点。堆比较当然使用该值。索引是节点在备份数组中的位置,并且是更新的当一个项目被添加到堆中时,Add方法返回一个对该节点的引用,我在数组中维护该节点。或者在您的情况下,您可以在队列中维护它

因此,算法如下所示:

heap = create empty heap
add first k items to the heap
for (i = k; i < n-k; ++i)
{
    if (heap.MaxItem - heap.MinItem) > threshold
        output range
    remove item i-k from the heap
    add item i to the heap
}
queue = create empty queue of heap nodes
heap = create empty heap
for (i = 0; i < k; ++i)
{
    node = heap.Add(array[i]);
    queue.Add(node);
}
for (i = k; i < n-k; ++i)
{
    if (heap.MaxItem - heap.MinItem) > threshold
        output range
    node = queue.Dequeue()
    remove item at position node.Index from the heap
    node = heap.Add(array[i])
    queue.Add(node)
}
    1  2  3  4  5  6  7  8  9

    1  1  1  1
    x  2  2  2  2
    x  x  3  3  3  3
    x  x  x  4  4  4  4
                5  5  5  5
                   6  6  6  6
                      7  7  7
                         8  8
                            9
queue=创建堆节点的空队列
heap=创建空堆
对于(i=0;i阈值
输出范围
node=queue.Dequeue()
从堆中删除位置节点处的项。索引
node=heap.Add(数组[i])
queue.Add(节点)
}
这是可证明的O(n log k)。每个项都被读取并添加到堆中。实际上,它也从堆中删除。此外,每个项都被添加到队列并从队列中删除,但这两个操作是O(1)

对于那些怀疑我的人来说,只要知道堆中任意元素的位置,就可以在O(logk)时间内从堆中删除该元素。我在这里解释了该技术:

因此,如果您有一个大小为4000的窗口,那么运行时间将大致与:
3n*2(log k)
成正比。如果有一百万个项目和5000个窗口大小,则计算结果为3000000*(12.3*2),或大约7500万。这大致相当于必须在优化的naive方法中重新计算整个窗口200次

正如我所说的,如果对数组进行排序,优化方法可能会花费很长时间,或者几乎如此。我上面概述的堆算法不会因此而受到影响

你应该尝试一下你的“更好”算法,看看它是否足够快。如果足够快,而且你不需要病理数据,那就太好了。否则,看看这项技术。

只是wan
(defn freqs 
  "Like frequencies but uses a sorted map"
  [coll]
  (reduce (fn [counts x] 
            (assoc counts x (inc (get counts x 0)))) 
          (sorted-map) coll))

(defn rng
  "Return max - min value of a sorted-map (log time)"
  [smap]
  (- (ffirst (rseq smap)) (ffirst smap)))

(defn slide-threshold [v w t] 
  (loop [q (freqs (subvec v 0 w)), i 0, j (+ i w), a []] 
    (if (= (count v) j) 
      a 
      (let [q* (merge-with + q {(v i) -1} {(v j) 1}) 
            q* (if (zero? (q* (v i))) (dissoc q* (v i)) q*) 
            a* (if (> (rng q) t) (conj a i) a)] 
        (recur q* (inc i) (inc j) a*)))))
(slide-threshold [10 12 14 13 10 11 16 14 17] 3 3)
;=> [0 2 4 5]