滚动或滑动窗口迭代器?

时间:2011-07-25 21:41:58

标签: python algorithm

我需要一个可在序列/迭代器/生成器上迭代的滚动窗口(也称为滑动窗口)。默认的Python迭代可以被认为是一种特殊情况,窗口长度为1.我目前正在使用以下代码。有没有人有更多的Pythonic,更简洁,更有效的方法呢?

def rolling_window(seq, window_size):
    it = iter(seq)
    win = [it.next() for cnt in xrange(window_size)] # First window
    yield win
    for e in it: # Subsequent windows
        win[:-1] = win[1:]
        win[-1] = e
        yield win

if __name__=="__main__":
    for w in rolling_window(xrange(6), 3):
        print w

"""Example output:

   [0, 1, 2]
   [1, 2, 3]
   [2, 3, 4]
   [3, 4, 5]
"""

26 个答案:

答案 0 :(得分:106)

旧版本的Python文档中有一个itertools examples

from itertools import islice

def window(seq, n=2):
    "Returns a sliding window (of width n) over data from the iterable"
    "   s -> (s0,s1,...s[n-1]), (s1,s2,...,sn), ...                   "
    it = iter(seq)
    result = tuple(islice(it, n))
    if len(result) == n:
        yield result
    for elem in it:
        result = result[1:] + (elem,)
        yield result

文档中的一个更简洁,并使用itertools来实现我想象的更大效果。

答案 1 :(得分:44)

这似乎是为collections.deque量身定做的,因为你基本上有一个FIFO(添加到一端,从另一端删除)。但是,即使您使用list,也不应该切两次;相反,您应该只从列表中pop(0)append()新项目。

这是一个优化的基于deque的实现,在您的原始版本之后构建:

from collections import deque

def window(seq, n=2):
    it = iter(seq)
    win = deque((next(it, None) for _ in xrange(n)), maxlen=n)
    yield win
    append = win.append
    for e in it:
        append(e)
        yield win

在我的测试中,它可以轻松地击败大部分时间在此处发布的所有内容,尽管pillmuncher的tee版本胜过大型迭代和小窗口。在较大的窗口上,deque以原始速度再次向前拉。

deque中对单个项目的访问速度可能比使用列表或元组更快或更慢。 (如果使用负数索引,则开头附近的项目会更快,或者接近末尾的项目。)我在循环体中放置sum(w);这对deque的强度起作用(从一个项目到另一个项目的迭代速度很快,所以这个循环比下一个最快的方法,即pillmuncher的速度快20%)。当我将其更改为单独查找并在十个窗口中添加项目时,表格已转动,tee方法的速度提高了20%。通过在添加中使用最后五个术语的负索引,我能够恢复一些速度,但tee仍然快一点。总的来说,我估计,对于大多数用途来说,任何一个都是快速的,如果你需要更多的性能,可以选择最适合的那个。

答案 2 :(得分:32)

我喜欢tee()

from itertools import tee, izip

def window(iterable, size):
    iters = tee(iterable, size)
    for i in xrange(1, size):
        for each in iters[i:]:
            next(each, None)
    return izip(*iters)

for each in window(xrange(6), 3):
    print list(each)

给出:

[0, 1, 2]
[1, 2, 3]
[2, 3, 4]
[3, 4, 5]

答案 3 :(得分:18)

以下是对stepfillvalue参数添加支持的概括:

from collections import deque
from itertools import islice

def sliding_window(iterable, size=2, step=1, fillvalue=None):
    if size < 0 or step < 1:
        raise ValueError
    it = iter(iterable)
    q = deque(islice(it, size), maxlen=size)
    if not q:
        return  # empty iterable or size == 0
    q.extend(fillvalue for _ in range(size - len(q)))  # pad to size
    while True:
        yield iter(q)  # iter() to avoid accidental outside modifications
        try:
            q.append(next(it))
        except StopIteration: # Python 3.5 pep 479 support
            return
        q.extend(next(it, fillvalue) for _ in range(step - 1))

如果需要,它会按每个迭代size个位置生成块step项,每个块填充fillvaluesize=4, step=3, fillvalue='*'的示例:

 [a b c d]e f g h i j k l m n o p q r s t u v w x y z
  a b c[d e f g]h i j k l m n o p q r s t u v w x y z
  a b c d e f[g h i j]k l m n o p q r s t u v w x y z
  a b c d e f g h i[j k l m]n o p q r s t u v w x y z
  a b c d e f g h i j k l[m n o p]q r s t u v w x y z
  a b c d e f g h i j k l m n o[p q r s]t u v w x y z
  a b c d e f g h i j k l m n o p q r[s t u v]w x y z
  a b c d e f g h i j k l m n o p q r s t u[v w x y]z
  a b c d e f g h i j k l m n o p q r s t u v w x[y z * *]

有关step参数的用例示例,请参阅Processing a large .txt file in python efficiently

答案 4 :(得分:9)

快速贡献。

由于当前的python docs在itertool示例中没有“window”(即http://docs.python.org/library/itertools.html的底部),这里是一个基于 石斑鱼的代码,这是给出的例子之一:

import itertools as it
def window(iterable, size):
    shiftedStarts = [it.islice(iterable, s, None) for s in xrange(size)]
    return it.izip(*shiftedStarts)

基本上,我们创建了一系列切片迭代器,每个迭代器的起点都向前一个点。然后,我们将这些拉链在一起。注意,这个函数返回一个生成器(它不直接是一个生成器本身)。

与上面的appending-element和advance-iterator版本非常相似,性能(即最佳)随列表大小和窗口大小而变化。我喜欢这个,因为它是一个双线程(它可能是一个单行,但我更喜欢命名概念)。

事实证明上述代码错误。如果传递给 iterable 的参数是一个序列但是如果它是一个迭代器则不行。如果它是一个迭代器,那么在islice调用中共享相同的迭代器(但不是tee'd),这会严重破坏它。

以下是一些固定代码:

import itertools as it
def window(iterable, size):
    itrs = it.tee(iterable, size)
    shiftedStarts = [it.islice(anItr, s, None) for s, anItr in enumerate(itrs)]
    return it.izip(*shiftedStarts)

此外,还有一本书的版本。这个版本不是复制迭代器然后多次推进复制,而是在我们向前移动起始位置时制作每个迭代器的成对副本。因此,迭代器t既提供了“完整”迭代器,也提供了t的起始点,也提供了创建迭代器t + 1的基础:

import itertools as it
def window4(iterable, size):
    complete_itr, incomplete_itr = it.tee(iterable, 2)
    iters = [complete_itr]
    for i in xrange(1, size):
        incomplete_itr.next()
        complete_itr, incomplete_itr = it.tee(incomplete_itr, 2)
        iters.append(complete_itr)
    return it.izip(*iters)

答案 5 :(得分:8)

为了展示如何合并itertools recipes,我使用pairwise食谱尽可能直接将window食谱扩展回consume食谱:

def consume(iterator, n):
    "Advance the iterator n-steps ahead. If n is none, consume entirely."
    # Use functions that consume iterators at C speed.
    if n is None:
        # feed the entire iterator into a zero-length deque
        collections.deque(iterator, maxlen=0)
    else:
        # advance to the empty slice starting at position n
        next(islice(iterator, n, n), None)

def window(iterable, n=2):
    "s -> (s0, ...,s(n-1)), (s1, ...,sn), (s2, ..., s(n+1)), ..."
    iters = tee(iterable, n)
    # Could use enumerate(islice(iters, 1, None), 1) to avoid consume(it, 0), but that's
    # slower for larger window sizes, while saving only small fixed "noop" cost
    for i, it in enumerate(iters):
        consume(it, i)
    return zip(*iters)

window食谱与pairwise相同,它只是替换单个元素&#34;消耗&#34;在第二个tee - ed迭代器上,对n - 1迭代器的消耗逐渐增加。使用consume代替在islice中包装每个迭代器的速度稍慢(对于足够大的迭代),因为您只在islice阶段支付consume包装开销,而不是在提取每个窗口值的过程(因此它被n限制,而不是iterable中的项目数。)

性能方面,与其他一些解决方案相比,这是非常好的(并且比我测试的任何其他解决方案都要好)。在Python 3.5.0,Linux x86-64上使用ipython %timeit magic进行测试。

kindall's the deque solution,通过使用islice而不是自制滚动的生成器表达式来调整性能/正确性并测试结果长度,这样当迭代时间短于时,它就不会产生结果窗口,以及maxlen的{​​{1}}位置而不是关键字(对于较小的输入产生惊人的差异):

deque

与先前适用的kindall解决方案相同,但每个>>> %timeit -r5 deque(windowkindall(range(10), 3), 0) 100000 loops, best of 5: 1.87 μs per loop >>> %timeit -r5 deque(windowkindall(range(1000), 3), 0) 10000 loops, best of 5: 72.6 μs per loop >>> %timeit -r5 deque(windowkindall(range(1000), 30), 0) 1000 loops, best of 5: 71.6 μs per loop 更改为yield win,因此存储生成器的结果无需所有存储结果,实际上是最近结果的视图(所有其他合理的解决方案)在这种情况下是安全的),并将yield tuple(win)添加到函数定义中,以便将tuple=tupletuple中的B转移到LEGB

L
基于

>>> %timeit -r5 deque(windowkindalltupled(range(10), 3), 0) 100000 loops, best of 5: 3.05 μs per loop >>> %timeit -r5 deque(windowkindalltupled(range(1000), 3), 0) 10000 loops, best of 5: 207 μs per loop >>> %timeit -r5 deque(windowkindalltupled(range(1000), 30), 0) 1000 loops, best of 5: 348 μs per loop 的解决方案如上所示:

consume

>>> %timeit -r5 deque(windowconsume(range(10), 3), 0) 100000 loops, best of 5: 3.92 μs per loop >>> %timeit -r5 deque(windowconsume(range(1000), 3), 0) 10000 loops, best of 5: 42.8 μs per loop >>> %timeit -r5 deque(windowconsume(range(1000), 30), 0) 1000 loops, best of 5: 232 μs per loop 相同,但内联consume情况else以避免函数调用和consume测试以减少运行时间,特别是对于设置开销为a的小输入有意义的工作部分:

n is None

(旁注:>>> %timeit -r5 deque(windowinlineconsume(range(10), 3), 0) 100000 loops, best of 5: 3.57 μs per loop >>> %timeit -r5 deque(windowinlineconsume(range(1000), 3), 0) 10000 loops, best of 5: 40.9 μs per loop >>> %timeit -r5 deque(windowinlineconsume(range(1000), 30), 0) 1000 loops, best of 5: 211 μs per loop 上的一个变体,它使用pairwise重复默认参数2来制作嵌套的tee对象,因此任何给定的迭代器只提前一次,而不是相似于MrDrFenner's answer类似于{{3}}类似于非内联tee并且在所有测试中都比内联consume慢,所以我省略了这些结果为简洁起见。)

正如您所看到的,如果您不关心调用者需要存储结果的可能性,我的优化版本的kindall解决方案大部分时间都会获胜,除了& #34;大型可迭代,小窗口大小的情况&#34; (内联consume获胜);随着可迭代大小的增加,它会迅速降级,而随着窗口大小的增加,它不会降级(对于可迭代的大小增加,每个其他解决方案的降级速度都会降低,但窗口大小的增加也会降低)。它甚至可以适应“需要元组”和#34;通过包裹在consume中的情况,其运行速度比将函数中的元组稍微慢一点,但它的重要性(需要1-5%的时间)并且让你在运行时保持更快的运行灵活性可以忍受反复返回相同的值。

如果您需要安全抵御存储的返回,则内联map(tuple, ...)会赢取除最小输入大小之外的所有内容(非内联consume略慢但缩放类似) 。 consume&amp;由于设置成本较低,基于tupling的解决方案仅针对最小输入获胜,且增益很小;随着迭代次数变长,它会严重降级。

为了记录,我使用的deque yield我所使用的kindall解决方案的改编版本是:

tuple

def windowkindalltupled(iterable, n=2, tuple=tuple): it = iter(iterable) win = deque(islice(it, n), n) if len(win) < n: return append = win.append yield tuple(win) for e in it: append(e) yield tuple(win) 的缓存放在函数定义行中,并在每个tuple中使用tuple以获得更快但不太安全的版本。

答案 6 :(得分:6)

我使用以下代码作为一个简单的滑动窗口,它使用生成器来大幅提高可读性。到目前为止,根据我的经验,它的速度足以用于生物信息学序列分析。

我在此处加入,因为我还没有看到这种方法。同样,我对其比较的表现没有任何说法。

def slidingWindow(sequence,winSize,step=1):
"""Returns a generator that will iterate through
the defined chunks of input sequence. Input sequence
must be sliceable."""

    # Verify the inputs
    if not ((type(winSize) == type(0)) and (type(step) == type(0))):
        raise Exception("**ERROR** type(winSize) and type(step) must be int.")
    if step > winSize:
        raise Exception("**ERROR** step must not be larger than winSize.")
    if winSize > len(sequence):
        raise Exception("**ERROR** winSize must not be larger than sequence length.")

    # Pre-compute number of chunks to emit
    numOfChunks = ((len(sequence)-winSize)/step)+1

    # Do the work
    for i in range(0,numOfChunks*step,step):
        yield sequence[i:i+winSize]

答案 7 :(得分:5)

def GetShiftingWindows(thelist, size):
    return [ thelist[x:x+size] for x in range( len(thelist) - size + 1 ) ]

>> a = [1, 2, 3, 4, 5]
>> GetShiftingWindows(a, 3)
[ [1, 2, 3], [2, 3, 4], [3, 4, 5] ]

答案 8 :(得分:5)

有一个库可以满足您的需求:

import more_itertools
list(more_itertools.windowed([1,2,3,4,5,6,7,8,9,10,11,12,13,14,15],n=3, step=3))

Out: [(1, 2, 3), (4, 5, 6), (7, 8, 9), (10, 11, 12), (13, 14, 15)]

答案 9 :(得分:5)

deque窗口的略微修改版本,使其成为真正的滚动窗口。所以它开始只用一个元素填充,然后增长到它的最大窗口大小,然后收缩,因为它的左边缘接近结束:

from collections import deque
def window(seq, n=2):
    it = iter(seq)
    win = deque((next(it, None) for _ in xrange(1)), maxlen=n)
    yield win
    append = win.append
    for e in it:
        append(e)
        yield win
    for _ in xrange(len(win)-1):
        win.popleft()
        yield win

for wnd in window(range(5), n=3):
    print(list(wnd))

这给出了

[0]
[0, 1]
[0, 1, 2]
[1, 2, 3]
[2, 3, 4]
[3, 4]
[4]

答案 10 :(得分:3)

我测试了一些解决方案,然后提出了一个解决方案,发现我提出的解决方案是最快的,因此我想与大家分享。

.container {
  max-width: 960px;
}

答案 11 :(得分:2)

多个迭代器!

def window(seq, size, step=1):
    # initialize iterators
    iters = [iter(seq) for i in range(size)]
    # stagger iterators (without yielding)
    [next(iters[i]) for j in range(size) for i in range(-1, -j-1, -1)]
    while(True):
        yield [next(i) for i in iters]
        # next line does nothing for step = 1 (skips iterations for step > 1)
        [next(i) for i in iters for j in range(step-1)]
当序列结束时,

next(it)引发StopIteration,并且由于某些很酷的原因超出了我,此处的yield语句除了它并且函数返回,忽略了不构成的剩余值全窗口。

无论如何,这是最不线的解决方案,其唯一的要求是seq实现__iter____getitem__并且不依赖于itertools或{{ 1}}除了@dansalmo的解决方案:))

答案 12 :(得分:2)

def rolling_window(list, degree):
    for i in range(len(list)-degree+1):
        yield [list[i+o] for o in range(degree)]

将其设为滚动平均功能

答案 13 :(得分:2)

为什么不

def pairwise(iterable):
    "s -> (s0,s1), (s1,s2), (s2, s3), ..."
    a, b = tee(iterable)
    next(b, None)
    return zip(a, b)

它在Python doc中有记录。 您可以轻松地将其扩展到更宽的窗口。

答案 14 :(得分:1)

#Importing the numpy library
import numpy as np
arr = np.arange(6) #Sequence
window_size = 3
np.lib.stride_tricks.as_strided(arr, shape= (len(arr) - window_size +1, window_size), 
strides = arr.strides*2)

"""Example output:

  [0, 1, 2]
  [1, 2, 3]
  [2, 3, 4]
  [3, 4, 5]

“”“

答案 15 :(得分:1)

让我们变得懒惰吧!

from itertools import islice, tee

def window(iterable, size): 
    iterators = tee(iterable, size) 
    iterators = [islice(iterator, i, None) for i, iterator in enumerate(iterators)]  
    yield from zip(*iterators)

list(window(range(5), 3))
# [(0, 1, 2), (1, 2, 3), (2, 3, 4)]

答案 16 :(得分:1)

深度学习中滑动窗口数据的优化功能

def SlidingWindow(X, window_length, stride):
    indexer = np.arange(window_length)[None, :] + stride*np.arange(int(len(X)/stride)-window_length+4)[:, None]
    return X.take(indexer)

应用于多维数组

import numpy as np
def SlidingWindow(X, window_length, stride1):
    stride=  X.shape[1]*stride1
    window_length = window_length*X.shape[1]
    indexer = np.arange(window_length)[None, :] + stride1*np.arange(int(len(X)/stride1)-window_length-1)[:, None]
    return X.take(indexer)

答案 17 :(得分:0)

修改后的DiPaolo's answer以允许任意填充和可变步长

  SELECT NAME || '  ' || SURNAME "Employee"
    FROM Schema1.Table1 
    LEFT JOIN Schema1.Table2 u 
    ON Manager = u.ID
    ORDER BY ID.Table1;

答案 18 :(得分:0)

我最终使用的(保持简单)解决方案:

def sliding_window(items, size):
    return [items[start:end] for start, end
            in zip(range(0, len(items) - size + 1), range(size, len(items) + 1))]

不用说,items 序列需要是可切片的。使用索引并不理想,但考虑到替代方案,它似乎是最不坏的选择......这也可以很容易地更改为生成器:只需将 [...] 替换为 (...)

答案 19 :(得分:0)

另一种从列表中生成固定长度窗口的简单方法

from collections import deque

def window(ls,window_size=3):
    window = deque(maxlen=window_size)

    for element in ls:
        
        if len(window)==window_size:
            yield list(window)
        window.append(element)

ls = [0,1,2,3,4,5]

for w in window(ls):
    print(w)

答案 20 :(得分:0)

我的 window 实现的两个版本

from typing import Sized, Iterable

def window(seq: Sized, n: int, strid: int = 1, drop_last: bool = False):
    for i in range(0, len(seq), strid):
        res = seq[i:i + n]
        if drop_last and len(res) < n:
            break
        yield res


def window2(seq: Iterable, n: int, strid: int = 1, drop_last: bool = False):
    it = iter(seq)
    result = []
    step = 0
    for i, ele in enumerate(it):
        result.append(ele)
        result = result[-n:]
        if len(result) == n:
            if step % strid == 0:
                yield result
            step += 1
    if not drop_last:
        yield result

答案 21 :(得分:0)

使用islice尝试使用简单,一种衬里,pythonic的方式。但是,可能不是最佳效率。

from itertools import islice
array = range(0, 10)
window_size = 4
map(lambda i: list(islice(array, i, i + window_size)), range(0, len(array) - window_size + 1))
# output = [[0, 1, 2, 3], [1, 2, 3, 4], [2, 3, 4, 5], [3, 4, 5, 6], [4, 5, 6, 7], [5, 6, 7, 8], [6, 7, 8, 9]]

说明: 通过使用window_size的islice创建窗口,并使用map在所有数组上进行迭代。

答案 22 :(得分:0)

这是一个班轮。我对它进行了计时,它与最佳答案的性能相当,并且随着seq的增大,性能会逐渐好转,len(seq)= 20的速度慢20%,len(seq)= 10000的速度慢7%

zip(*[seq[i:(len(seq) - n - 1 + i)] for i in range(n)])

答案 23 :(得分:0)

这是一个老问题,但对于那些仍然感兴趣的人来说,使用this页面中的生成器(Adrian Rosebrock)可以很好地实现窗口滑块。

这是OpenCV的一个实现,但您可以轻松地将其用于任何其他目的。对于急切的人我会在这里粘贴代码,但为了更好地理解它我建议访问原始页面。

def sliding_window(image, stepSize, windowSize):
    # slide a window across the image
    for y in xrange(0, image.shape[0], stepSize):
        for x in xrange(0, image.shape[1], stepSize):
            # yield the current window
            yield (x, y, image[y:y + windowSize[1], x:x + windowSize[0]])

提示:在迭代生成器时,您可以检查窗口的.shape以丢弃那些不符合您要求的生成器

干杯

答案 24 :(得分:0)

如何使用以下内容:

mylist = [1, 2, 3, 4, 5, 6, 7]

def sliding_window(l, window_size=2):
    if window_size > len(l):
        raise ValueError("Window size must be smaller or equal to the number of elements in the list.")

    t = []
    for i in xrange(0, window_size):
        t.append(l[i:])

    return zip(*t)

print sliding_window(mylist, 3)

输出:

[(1, 2, 3), (2, 3, 4), (3, 4, 5), (4, 5, 6), (5, 6, 7)]

答案 25 :(得分:0)

>>> n, m = 6, 3
>>> k = n - m+1
>>> print ('{}\n'*(k)).format(*[range(i, i+m) for i in xrange(k)])
[0, 1, 2]
[1, 2, 3]
[2, 3, 4]
[3, 4, 5]