我有一个类似
的数组[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秒内完成一次计算。
答案 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