シーケンス/イテレーター/ジェネレーターで反復可能なローリングウィンドウ(別名スライディングウィンドウ)が必要です。デフォルトの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]
"""
古いバージョンの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
を使用して、より大きな効果を想像しています。
これは、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
はまだ少し高速でした。全体として、ほとんどの用途でどちらかが十分に高速であり、もう少しパフォーマンスが必要な場合は、最適なプロファイルを選択します。
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]
step
、fillvalue
パラメーターのサポートを追加する一般化は次のとおりです。
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ファイルの効率的な処理 を参照してください。
ただ簡単な貢献。
現在の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)
次のコードは、ジェネレーターを使用して読みやすさを大幅に向上させる単純なスライドウィンドウとして使用します。これまでの速度は、私の経験ではバイオインフォマティクスのシーケンス解析で使用するには十分でした。
このメソッドがまだ使用されていないので、ここに含めます。繰り返しますが、私はその比較されたパフォーマンスについて何も主張しません。
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]
まさに必要なことを行うライブラリがあります:
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)]
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] ]
それを真のローリングウィンドウにするために、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]
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のソリューションに加えて:)
何故なの
def pairwise(iterable):
"s -> (s0,s1), (s1,s2), (s2, s3), ..."
a, b = tee(iterable)
next(b, None)
return Zip(a, b)
Python doc で文書化されています。簡単に、より広いウィンドウに拡張できます。
def rolling_window(list, degree):
for i in range(len(list)-degree+1):
yield [list[i+o] for o in range(degree)]
ローリング平均関数用にこれを作成しました
怠けましょう!
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)]
#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]
「」
以下を使用してはどうですか:
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)]
>>> 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]
変更 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
これは古い質問ですが、まだ興味がある人には 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
を確認して、要件を満たさないものを破棄できます
乾杯