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

时间:2014-04-24 22:40:49

标签: algorithm dataset moving-average sliding-window

我有几百万个数据点,每个数据点都有时间和值。我很想知道所有的滑动窗口(即4000个数据点的块),窗口从高到低的范围超过了恒定的阈值。

例如:,假设长度为3的窗口,以及高 - 低> 1的阈值。 3.然后系列:[10 12 14 13 10 11 16 14 17]将导致[0,2,4,5],因为这些是3周期窗口的高 - 低范围超过阈值的索引。

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

天真的方法是计算每个可能的窗口范围,即1-4000,2-4001,3-4002等,并累积那些超过阈值的集合。对于大型数据集,这可能会像你想象的那样永远。

所以,我认为更好的算法如下:

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

现在,假设1-4000窗口的高/低索引分别为333和666。我迭代并继续更新新的高点/低点,因为我在右边看到它们,但是一旦窗口处于334-4333(一旦缓存的高/低超出当前窗口),我重新计算高/低对于当前窗口(334-4333),缓存并继续迭代。

我的问题是:

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

2。)我的算法是否合理?准确?有没有办法可以大大简化或改进?

非常感谢。

5 个答案:

答案 0 :(得分:3)

如果数据长度为n且窗口大小为m,则这是使用排序映射的O(n log m)解决方案。

(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]

答案 1 :(得分:2)

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

您可以使用Min-max heap(或两个堆)在O(n log k)中解决此问题,但您必须使用一种可以删除O(log k)中的任意节点的堆。您不能使用标准二进制堆,因为虽然删除任意节点是O(log k),但查找节点是O(k)。

假设您有一个Min-max堆,算法如下所示:

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
}

问题当然是从堆中删除项目i-k。实际上,问题是有效地找到它。我过去这样做的方法是修改我的二进制堆,以便它存储包含索引和值的节点。当然,堆比较使用该值。索引是节点在后备阵列中的位置,并且每当移动节点时由堆更新。将项添加到堆中时,Add方法返回对节点的引用,我将其保存在数组中。或者在您的情况下,您可以将其保留在队列中。

所以算法看起来像这样:

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)
}

这可证明是O(n log k)。读取每个项目并将其添加到堆中。实际上,它也从堆中删除了。此外,每个项目都会添加到队列中并从队列中删除,但这两个操作都是O(1)。

对于那些怀疑我的人, 可以在O(log k)时间从堆中删除任意元素,前提是你知道它在哪里。我在这里解释了这个技术:https://stackoverflow.com/a/8706363/56778

因此,如果您有一个大小为4,000的窗口,则运行时间将大致与:3n * 2(log k)成比例。鉴于一百万件商品和一个窗口大小为5,000,这可以达到3,000,000 *(12.3 * 2),或大约7500万。这大致相当于必须在优化的天真方法中重新计算完整窗口200次。

正如我所说,如果数组已经排序,那么优化后的方法可能会花费很长时间。我上面概述的堆算法不会受此影响。

您应该尝试“更好”的算法,看看它是否足够快。如果它是,并且您不期望病理数据,那么很好。否则请看一下这种技术。

答案 2 :(得分:1)

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

 at every step:
  if (!Deque.Empty) and (Deque.Head.Index <= CurrentIndex - T) then 
     Deque.ExtractHead;
  //Head is too old, it is leaving the window

  while (!Deque.Empty) and (Deque.Tail.Value > CurrentValue) do
     Deque.ExtractTail;
  //remove elements that have no chance to become minimum in the window

  Deque.AddTail(CurrentValue, CurrentIndex); 
  CurrentMin = Deque.Head.Value
  //Head value is minimum in the current window

Another approach uses stacks

答案 3 :(得分:0)

以下是此代码的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

说明:

  1. 维护2堆,最小堆和最大堆
  2. 在移动窗口的每一步,执行以下操作

    一个。从堆中删除项目,直到项目的索引不会下降 在窗外面 湾检查是否违反了阈值 如果需要,堆的顶部元素并记录索引 C。将新进入窗口的元素推入堆中。

  3. *我对max_heap使用负值,因为python的实现是最小堆

    此算法的最坏情况复杂性为O(n log n).

答案 4 :(得分:-1)

只想玩一个受Simple Moving Average概念启发的想法。

让我们考虑使用大小为4的滑动窗口的9个点。在任何时候,我们都会跟踪分别为4,3,2和1的所有窗口的最大值。在那时候。假设我们将它们存储在数组中......

  • 在位置1(p1),我们有一个值(v1)和一个窗口{p1},数组A1包含max(v1)
  • 在位置2(p2),我们有两个值(v1,v2)和两个窗口{p1,p2}和{p2},数组A2包含max(v1,v2)和max(v2)
  • 在位置3(p3),遵循相同的模式,阵列A3包含max(v1,v2,v3)= max(max(v1,v2),v3),max(v2,v3)和max( V3)。注意我们已经知道A2的最大值(v1,v2)
  • 让我们跳一下,看看位置6(p6),阵列A6包含max(v3,v4,v5,v6),max(v4,v5,v6),max(v5,v6)和max(v6)。同样,我们已经知道A5的max(v3,v4,v5),max(v4,v5)和max(v5)。

粗略地说,它看起来像这样:

    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

这可以概括如下:

Let 
n   number of datapoints
s   window size, 1 <= s <= n
i   current position / datapoint, 1 <= s <= n
Vi  value at position i
Ai  array at position i (note: the array starts at 1 in this definition)

then
Ai (i <= s) has elements 
aj = max(Vi, Ai-1[j]) for j in (1..i-1)
aj = Vi for j = i
aj = undefined/unimportant for j in (i+1..s)  

Ai (i > s) has elements 
aj = max(Vi, Ai-1[j+1]) for j in (1..s-1) 
aj = Vi for j = s

位置i的大小为s的窗口的最大值由Ai [1]给出。此外,可以获得任意大小的窗口x(0&lt)的最大值奖励由Ai [s - x + 1]给出的; x&lt; = s)。

我认为以下情况属实:

  • 计算/时间复杂度极低。没有排序,插入,删除或搜索;但是,max函数被称为n * s次。
  • 空间复杂度更大(我们至少存储大小为s的数组),但前提是我们希望将结果保留在O(1)中运行的未来查询中。否则,只需要两个阵列,Ai-1和Ai;我们需要的只是为了填充位置i处的数组是位置i-1
  • 的数组
  • 我们仍然不能轻易地使这个算法在并行进程中运行
  • 使用此算法计算最小值和最大值,我们可以有效地累积大数据集的滑动窗口百分比变化

我在github上为Javascript添加了一个示例实现/测试平台 - SlidingWindowAlgorithm。这是算法本身的副本(请注意,在此实现中,数组的索引为0):

var evalMaxInSlidingWindow = function(datapoints, windowsize){
    var Aprev = [];
    var Acurr = [];
    var Aresult = [];

    for (var i = 0, len = datapoints.length; i < len; i++)
    {
        if (i < windowsize)
        {
            for(var j = 0; j < windowsize; j++)
            {
                if (j < i)
                {
                    Acurr[j] = Math.max(datapoints[i], Aprev[j]);
                }
                if (j == i)
                {
                    Acurr[j] = datapoints[i];
                }
            }
        } 
        else 
        {
            for(var j = 0; j < windowsize; j++)
            {
                if (j < windowsize - 1)
                {
                    Acurr[j] = Math.max(datapoints[i], Aprev[j + 1]);
                }
                if (j == windowsize - 1)
                {
                    Acurr[j] = datapoints[i];
                }
            }
        }

        Aresult.push(Acurr[0]);
        Aprev = [].concat(Acurr);
    }

    return Aresult;
};

在与Scott讨论后,似乎这个算法没有什么特别之处。好吧,玩它很有趣。 :)