使用python在移动间隔上查找max(和min)

时间:2015-09-07 10:40:44

标签: python arrays python-3.x max min

我有一个类似

的数组
[5.5, 6.0, 6.0, 6.5, 6.0, 5.5, 5.5, 5.0, 4.5]. 

此数组的所有数字相差0.5,两个连续数字的最大差异也为0.5(它们可以相同;如示例中所示)。并且有一个移动间隔或框,其中包括例如3个连续的数字,如下所示:

[(5.5, 6.0, 6.0), 6.5, 6.0, 5.5, 5.5, 5.0, 4.5]  # min: 5.5, max: 6.0

并且框逐个向右移动:

[5.5, (6.0, 6.0, 6.5), 6.0, 5.5, 5.5, 5.0, 4.5]  # min: 6.0, max: 6.5

[5.5, 6.0, (6.0, 6.5, 6.0), 5.5, 5.5, 5.0, 4.5]  # min: 6.0, max: 6.5

问题是,如何在每个时间框移动时找到框内数字的最小值和最大值?

当盒子和数组的大小像这个例子那样小时,我可以处理它,但是我需要将它应用于数组大小100000和盒子大小10000.使用我的方法(我使用for循环计算每个最大值和最小值)对于每个时间框通过),花了太多时间(我有100多个阵列要做,需要重复运行)。有一些时间限制,所以我需要在0.5秒内完成一次计算。

6 个答案:

答案 0 :(得分:5)

看一下pandas的rolling windows

>>> import pandas as pd
>>> L = [5.5, 6.0, 6.0, 6.5, 6.0, 5.5, 5.5, 5.0, 4.5]
>>> a = pd.DataFrame(L)
>>> pd.rolling_max(a, 3)
     0
0  NaN
1  NaN
2  6.0
3  6.5
4  6.5
5  6.5
6  6.0
7  5.5
8  5.5
>>> pd.rolling_min(a, 3)
     0
0  NaN
1  NaN
2  5.5
3  6.0
4  6.0
5  5.5
6  5.5
7  5.0
8  4.5

答案 1 :(得分:2)

起初,在我看来,这需要对大列表的每个元素进行最少的O(log(window_size))操作(参见我的其他答案)。但@wim向我指出了@adamax在这篇文章中描述的真正卓越的算法:

Implement a queue in which push_rear(), pop_front() and get_min() are all constant time operations

这是一个实现。

使用1000窗口在建议的100000数字上运行它需要0.6秒而不是天真算法的60秒。

class MinMaxStack(object):

    def __init__(self):
        self.stack = []

    def push(self,val):
        if not self.stack:
            self.stack = [(val,val,val)]
        else:
            _,minimum,maximum = self.stack[-1]
            if val < minimum:
                self.stack.append((val,val,maximum))
            elif val > maximum:
                self.stack.append((val,minimum,val))
            else:
                self.stack.append((val,minimum,maximum))

    def pop(self):
        return self.stack.pop()

    def get_minimax(self):
        return self.stack[-1][1:]

    def __len__(self):
        return len(self.stack)

class RollingWindow(object):

    def __init__(self):
        self.push_stack = MinMaxStack()
        self.pop_stack = MinMaxStack()

    def push_only(self,o):
        self.push_stack.push(o)

    def push_and_pop(self,o):
        self.push_stack.push(o)
        if not self.pop_stack:
            for i in range(len(self.push_stack.stack)-1):
                self.pop_stack.push(self.push_stack.pop()[0])
            self.push_stack.pop()
        else:
            self.pop_stack.pop()

    def get_minimax(self):
        if not self.pop_stack:
            return self.push_stack.get_minimax()
        elif not self.push_stack:
            return self.pop_stack.get_minimax()
        mn1,mx1 = self.pop_stack.get_minimax()
        mn2,mx2 = self.push_stack.get_minimax()
        return min(mn1,mn2),max(mx1,mx2)



import time
import random
window = 10000
test_length = 100000
data = [random.randint(1,100) for i in range(test_length)]

s = time.time()

wr = RollingWindow()
answer1 = []
for i in range(test_length):
    if i < window:
        wr.push_only(data[i])
    else:
        wr.push_and_pop(data[i])
    answer1.append(wr.get_minimax())

print(s-time.time())

s = time.time()
answer2 = []
for i in range(test_length):
    if i+1 < window:
        current_window = i+1
    else:
        current_window = window
    answer2.append((min(data[i+1-current_window:i+1]),max(data[i+1-current_window:i+1])))

print(s-time.time())

if answer1 != answer2:
    print("Test Fail")

可能会有一些小的性能改进。这个版本不断增长和缩小用作堆栈的python列表。相反,它永远不会缩小它并使用结束指针稍微快一些。但只有几个百分点。如果你真的非常渴望几个百分点,你可以将两个堆栈合并到窗口类中,并减少调用中的间接性。我构建了一个优化版本,用collections.deque替换列表并内联堆栈代码并将其降低到0.32秒。

如果需要更高的速度,在C或Cython中编码很容易(特别是对于固定的窗口大小),特别是如果你可以限制堆栈上值的类型。

答案 2 :(得分:0)

l = [5.5, 6.0, 6.0, 6.5, 6.0, 5.5, 5.5, 5.0, 4.5]

windoSize = 3

for i in range(0,len(l)-windowSize+1):

    print max(l[i:i+windoSize])

输出:

6.0
6.5
6.5
6.5
6.0
5.5
5.5

答案 3 :(得分:0)

这是一个滚动窗口,可以在pandas中实现,如另一个答案所示。

但是,如果您想自己实现它,以下代码将有所帮助。这段代码可以进一步优化,可以更加pythonic,但它应该很好地理解算法中发生的事情。

最初找到起始窗口的最小值和最大值。 一旦初始化,我们将子数组视为一个队列,只有2个值变得重要,添加新值并删除旧值。

如果旧值是最小值或最大值,我们会重新计算最小值或最大值,否则我们会检查新值是新值还是最小值。

def updateMinMaxValues(minVal,maxVal,val):
    if val < minVal:
        minVal = val
    if val > maxVal:
        maxVal= val
    return minVal,maxVal

values = [5.5, 6.0, 6.0, 6.5, 6.0, 5.5, 5.5, 5.0, 4.5]
windowSize = 3
minVal,maxVal = min(values[:windowSize]),max(values[:windowSize])

print(minVal,maxVal)
for stepIndex in range(windowSize,len(values)):
    oldVal,newVal = values[stepIndex-windowSize],values[stepIndex]
    if oldVal == minVal:
        minVal = min(values[stepIndex-windowSize+1:stepIndex+1])
    if oldVal == maxVal:
        maxVal = max(values[stepIndex-(windowSize)+1:stepIndex+1])
    minVal,maxVal = updateMinMaxValues(minVal,maxVal,newVal)
    print(minVal,maxVal)

结果:

5.5 6.0
6.0 6.5
6.0 6.5
5.5 6.5
5.5 6.0
5.0 5.5
4.5 5.5

答案 4 :(得分:0)

不确定是否有办法有效地利用数字流的缓慢移动结构。

我认为最好的一般方法是使用优先级队列。我已经在下面留下了如何做到的描述。进入窗口的每个新号码都是O(log(window_size))。

然而,wim对原帖的评论指出有一个O(1)算法,在这篇文章中描述:Implement a queue in which push_rear(), pop_front() and get_min() are all constant time operations

到目前为止,简单地保持其中一个保持最小值和最大值将成为最佳解决方案。

但是我的尝试是参考:

维护一对优先级队列,一个用于最大值,一个用于最小值,并且每次都添加和删除每个队列。这增加了相当多的开销 每个新条目[O(log(window_size))]但每个条目都有一个很好的平滑行为和良好的整体效率。

Python heapq模块是在Python中实现优先级队列的常用方法。但是,它不直接支持删除条目或修改其优先级。这可以通过在队列中添加从数字到位置的字典索引来完成,而不会增加计算复杂度。要删除条目,您可以将其数字更新为极低(或分别为高)并重新堆积,以便它移动到顶部并可以弹出。

这是一个例子,虽然我没有测试过但看起来还不错:

http://code.activestate.com/recipes/522995-priority-dict-a-priority-queue-with-updatable-prio/

您需要消除字典中具有相同值的条目的歧义,或者为每个键保留多个值,以便在删除它们时找到所有实例。

答案 5 :(得分:0)

在新版本的 pandas 中,您必须按照我在文档中所说的那样使用它:

>>> s = pd.Series([4, 3, 5, 2, 6])
>>> s.rolling(3).min()
0    NaN
1    NaN
2    3.0
3    2.0
4    2.0
dtype: float64