web-dev-qa-db-ja.com

ローリングウィンドウまたはスライディングウィンドウイテレーター?

シーケンス/イテレーター/ジェネレーターで反復可能なローリングウィンドウ(別名スライディングウィンドウ)が必要です。デフォルトのPython反復は、ウィンドウの長さが1である特別な場合と考えることができます。現在、次のコードを使用しています。誰かがこれを行うために、よりPython的で、より冗長で、より効率的な方法を持っていますか?

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]
"""
133
David B.

古いバージョンのPythonドキュメントには itertoolsの例 があります:

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を使用して、より大きな効果を想像しています。

110
Daniel DiPaolo

これは、FIFOを本質的に持っているので、collections.dequeに合わせて作成されたようです(一方の端に追加し、もう一方の端から削除します)。ただし、listを使用する場合でも、2回スライスすることはできません。代わりに、リストから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%高速に実行されました)。 10個のウィンドウで個別に検索してアイテムを追加するように変更すると、テーブルが変わり、teeメソッドが20%高速になりました。追加の最後の5つの用語に負のインデックスを使用することで、ある程度の速度を回復することができましたが、teeはまだ少し高速でした。全体として、ほとんどの用途でどちらかが十分に高速であり、もう少しパフォーマンスが必要な場合は、最適なプロファイルを選択します。

44
kindall

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]
32
pillmuncher

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位置をローリングして、fillvalueアイテムをチャンク単位で生成します。 size=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パラメーターの使用例については、 pythonでの大きな.txtファイルの効率的な処理 を参照してください。

18
jfs

ただ簡単な貢献。

現在のpythonドキュメントには、itertoolの例に「ウィンドウ」がないため(つまり、 http://docs.python.org/library/itertools.html の下部にあります) =)、これは与えられた例の1つであるグルーパーのコードに基づいたスニペットです:

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

基本的に、一連のスライスされたイテレータを作成します。各イテレータには、開始点が1つ先にあります。次に、これらを一緒に圧縮します。この関数はジェネレーターを返すことに注意してください(ジェネレーターそのものではありません)。

上記のappending-elementバージョンおよびadvancing-iteratorバージョンと同様に、パフォーマンス(つまり、最適)はリストサイズとウィンドウサイズによって異なります。これは、2ライナー(1ライナーでもかまいませんが、命名の概念を好むため)なので気に入っています。

上記のコードはwrongであることがわかります。 iterableに渡されるパラメーターがシーケンスの場合は機能しますが、イテレーターの場合は機能しません。イテレータの場合、同じイテレータがislice呼び出し間で共有されますが(ティーではありません)、これは事態をひどく壊します。

修正済みのコードを次に示します。

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)
9
MrDrFenner

次のコードは、ジェネレーターを使用して読みやすさを大幅に向上させる単純なスライドウィンドウとして使用します。これまでの速度は、私の経験ではバイオインフォマティクスのシーケンス解析で使用するには十分でした。

このメソッドがまだ使用されていないので、ここに含めます。繰り返しますが、私はその比較されたパフォーマンスについて何も主張しません。

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
Gus

まさに必要なことを行うライブラリがあります:

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)]
7
Nikolay Frick
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] ]
6
heyyou482

それを真のローリングウィンドウにするために、dequeウィンドウを少し修正したバージョン。そのため、1つの要素のみが入力され始め、最大ウィンドウサイズまで拡大し、左端に近づくにつれて縮小します。

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

複数のイテレーター!

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またはcollectionsに依存しないことだけです。 @dansalmoのソリューションに加えて:)

2
jameh

何故なの

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

Python doc で文書化されています。簡単に、より広いウィンドウに拡張できます。

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

ローリング平均関数用にこれを作成しました

2
yazdmich

怠けましょう!

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)]
1
Gram
#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]

「」

1
FAYAZ

以下を使用してはどうですか:

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)]
0
keocra
>>> 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]
0
dansalmo

変更 DiPaoloの答え 任意の塗りつぶしと可変ステップサイズを許可する

import itertools
def window(seq, n=2,step=1,fill=None,keep=0):
    "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(itertools.islice(it, n))    
    if len(result) == n:
        yield result
    while True:        
#         for elem in it:        
        elem = Tuple( next(it, fill) for _ in range(step))
        result = result[step:] + elem        
        if elem[-1] is fill:
            if keep:
                yield result
            break
        yield result
0
shouldsee

これは古い質問ですが、まだ興味がある人には 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を確認して、要件を満たさないものを破棄できます

乾杯

0
DarkCygnus