Itérateur de fenêtre mobile ou coulissant?
j'ai besoin d'une fenêtre roulante (alias fenêtre coulissante) itérable sur une séquence/itérateur/générateur. L'itération Python par défaut peut être considérée comme un cas spécial, où la longueur de la fenêtre est 1. J'utilise actuellement le code suivant. Est-ce que quelqu'un a une méthode plus pythonique, moins verbeuse, ou plus efficace pour faire cela?
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]
"""
19 réponses
il y en a un dans une ancienne version de Python docs avec itertools
exemples :
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
celui du docs est un peu plus succinct et utilise itertools
pour plus d'effet j'imagine.
cela semble taillé sur mesure pour un collections.deque
puisque vous avez essentiellement un FIFO (ajouter à un bout, enlever de l'autre). Cependant, même si vous utilisez un list
, vous ne devriez pas couper deux fois; à la place, vous devriez probablement juste pop(0)
de la liste et append()
le nouvel article.
Voici une implémentation basée sur le deque optimisée, inspirée de votre original:
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
dans mes tests, il bat handily tout le reste affiché ici la plupart du temps, bien que la version tee
de pillmuncher bat pour les grandes itérables et les petites fenêtres. Sur les fenêtres plus grandes, le deque
prend de nouveau de l'avance en vitesse brute.
L'accès aux éléments individuels du deque
peut être plus rapide ou plus lent qu'avec les listes ou les tuples. (Les éléments près du début sont plus rapides, ou les éléments près de la fin si vous utilisez un indice négatif.) J'ai mis un sum(w)
dans le corps de ma boucle; cela joue à la force de la deque (itérer d'un élément à l'autre est rapide, de sorte que cette boucle a couru un 20% plus rapide que la méthode la plus rapide suivante, pillmuncher's). Quand je l'ai changé pour chercher individuellement et ajouter des articles dans une fenêtre de dix, les tables ont tourné et la méthode tee
était 20% plus rapide. J'ai pu récupérer un peu de vitesse en utilisant des indices négatifs pour les cinq derniers termes dans l'addition, mais tee
était encore un peu plus rapide. Globalement, je dirais que l'un est beaucoup rapide pour la plupart des utilisations et si vous besoin d'un peu plus de performance, de profil et de choisir celui qui fonctionne le mieux.
j'aime 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)
donne:
[0, 1, 2]
[1, 2, 3]
[2, 3, 4]
[3, 4, 5]
Voici une généralisation qui ajoute la prise en charge des paramètres 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))
Il produit en morceaux size
éléments à la fois de rouler step
positions par itération remplissage de chaque morceau avec fillvalue
si nécessaire. Exemple pour 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 * *]
pour un exemple d'utilisation du paramètre step
, voir traitement d'une grande taille .fichier txt en python efficacement .
juste une contribution rapide.
puisque les docs python actuels n'ont pas de "fenêtre" dans les exemples itertool (i.e., au bas de http://docs.python.org/library/itertools.html ), voici un extrait basé sur le code pour mérou qui est l'un des exemples donnés:
import itertools as it
def window(iterable, size):
shiftedStarts = [it.islice(iterable, s, None) for s in xrange(size)]
return it.izip(*shiftedStarts)
Fondamentalement, nous créons une série d'itérateurs tranchés, chacun avec un point de départ un point plus loin en avant. Ensuite, nous zip ces ainsi. Remarque, cette fonction renvoie un générateur (il n'est pas directement un générateur lui-même).
tout comme les versions des itérateurs et des éléments auxiliaires ci-dessus, la performance (c.-à-d. la meilleure) varie en fonction de la taille de la liste et de la taille de la fenêtre. Je l'aime parce que c'est un deux-liner (cela pourrait être un one-liner, mais je préfère l'appellation de concepts).
il s'avère que le code ci-dessus est faux . Il fonctionne si le paramètre passé itératif est une séquence, mais pas si c'est un itérateur. Si c'est un iterator, le même iterator est partagé (mais pas tee'd) entre les appels d'islice et cela casse les choses mal.
voici un code fixe:
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)
aussi, une version de plus pour les livres. Au lieu de copier un itérateur puis d'avancer des copies de nombreuses fois, cette version fait des copies en paires de chaque itérateur que nous déplacer la position de départ transmettre. Ainsi, l'itérateur t fournit à la fois l'itérateur" complet "avec le point de départ à t et aussi la base pour créer l'itérateur 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)
juste pour montrer comment vous pouvez combiner itertools
recettes , je prolonge la pairwise
recette aussi directement que possible dans la window
recette en utilisant la consume
recette:
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)
la recette window
est la même que pour pairwise
, il remplace simplement l'élément unique "consommer" sur le second itérateur tee
- ed avec des consommations progressivement croissantes sur les itérateurs n - 1
. Utiliser consume
au lieu d'envelopper chaque itérateur dans islice
est légèrement plus rapide (pour les itérables suffisamment grands) puisque vous payez seulement le islice
enveloppant au-dessus pendant la phase consume
, pas pendant le processus d'extraction de chaque valeur fenêtre-ed (donc il est limité par n
, pas le nombre d'articles dans iterable
).
du point de vue de la Performance, comparé à d'autres solutions, c'est assez bon (et meilleur que n'importe laquelle des autres solutions I testé échelle). Testé sur Python 3.5.0, Linux x86-64, en utilisant ipython
%timeit
magic.
kindall la deque
solution , ajustée pour la performance / exactitude en utilisant islice
au lieu d'une expression de générateur laminé à plat et tester la longueur résultante de sorte qu'il ne donne pas de résultats lorsque l'itérable est plus courte que la fenêtre, ainsi que passer le maxlen
de la deque
positionnellement au lieu de par mot-clé (fait une différence surprenante pour les entrées plus petites):
>>> %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
identique à la solution kindall précédemment adaptée, mais avec chaque yield win
changé en yield tuple(win)
donc stocker les résultats du générateur fonctionne sans tous les résultats stockés étant vraiment une vue du résultat le plus récent (toutes les autres solutions raisonnables sont sûres dans ce scénario), et l'ajout de tuple=tuple
à la définition de la fonction de déplacer l'utilisation de tuple
de la B
en LEGB
à la 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
- solution basée ci-dessus:
>>> %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
identique à consume
, mais inlining else
cas de consume
pour éviter l'appel de fonction et n is None
test pour réduire le temps d'exécution, en particulier pour les petites entrées où la mise au plafond est une partie significative du travail:
>>> %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
(note: Une variante de pairwise
qui utilise tee
avec l'argument par défaut de 2 à plusieurs reprises pour faire des objets imbriqués tee
, de sorte qu'un itérateur donné n'est avancé qu'une seule fois, non consommé indépendamment un nombre croissant de fois, similaire à la réponse de MrDrFenner est similaire à non-inlined consume
et plus lent que le inlined consume
sur tous les tests, donc je l'ai omis ces résultats pour la brièveté).
comme vous pouvez le voir, si vous ne vous souciez pas de la possibilité de l'appelant ayant besoin de stocker des résultats, ma version optimisée de la solution de kindall gagne la plupart du temps, sauf dans le "grand itérable, petite fenêtre de taille" "(où inlined consume
gagne); il se dégrade rapidement que la taille itérable augmente, tout en ne se dégradant pas du tout que la taille de la fenêtre augmente (chaque autre solution se dégrade plus lentement pour les augmentations itérables de taille, mais se dégrade également pour les augmentations de taille de fenêtre). Il peut même être adapté pour l'étui "need tuples" en l'enveloppant dans map(tuple, ...)
, qui court toujours un peu plus lentement que la mise du tupling dans la fonction, mais il est trivial (prend 1-5% de plus) et vous permet de garder la flexibilité de courir plus vite quand vous pouvez tolérer plusieurs fois retourner la même valeur.
si vous avez besoin de sécurité contre les retours stockés, inlined consume
gagne sur toutes les tailles d'entrée sauf les plus petites (avec non-inlined consume
étant légèrement plus lent mais échelle similaire). Le deque
& tupling solution basée gagne seulement pour les plus petites entrées, en raison de coûts de configuration plus faibles, et le gain est faible; il se dégrade mal que le itérable devient plus long.
Pour l'enregistrement, la version adaptée de kindall la solution yield
s tuple
s que j'ai utilisé était:
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)
supprimer la mise en cache de tuple
dans la ligne de définition de la fonction et l'utilisation de tuple
dans chaque yield
pour obtenir le plus rapide mais moins version sécurité.
j'utilise le code suivant comme une fenêtre coulissante simple qui utilise des générateurs pour augmenter considérablement la lisibilité. D'après mon expérience, sa vitesse est suffisante pour l'analyse des séquences bioinformatiques.
Je l'inclus ici parce que je n'ai pas encore vu cette méthode utilisée. Encore une fois, je ne fais aucune réclamation au sujet de sa performance comparée.
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]
une version légèrement modifiée de la fenêtre deque, pour en faire une vraie fenêtre roulante. De sorte qu'il commence à être peuplé avec juste un élément, puis grandit à sa taille de fenêtre maximale, et puis se rétrécit comme il est bord gauche vient près de la fin:
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))
ce qui donne
[0]
[0, 1]
[0, 1, 2]
[1, 2, 3]
[2, 3, 4]
[3, 4]
[4]
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] ]
plusieurs itérateurs!
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)
soulève StopIteration
quand la séquence est terminée, et pour une raison cool qui me dépasse, l'instruction de rendement ici l'exclut et la fonction retourne, ignorant les valeurs restantes qui ne forment pas une fenêtre complète.
quoi qu'il en soit, il s'agit de la solution la moins-lignes encore dont la seule exigence est que seq
mettre en œuvre soit __iter__
ou __getitem__
et ne repose pas sur itertools
ou collections
en plus de la solution de @dansalmo:)
def rolling_window(list, degree):
for i in range(len(list)-degree+1):
yield [list[i+o] for o in range(degree)]
Fait ceci pour une moyenne de la fonction
pourquoi pas
def pairwise(iterable):
"s -> (s0,s1), (s1,s2), (s2, s3), ..."
a, b = tee(iterable)
next(b, None)
return zip(a, b)
Il est documenté en Python doc . Vous pouvez facilement l'étendre à une fenêtre plus large.
il y a une bibliothèque qui fait exactement ce dont vous avez besoin:
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)]
>>> 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]
Que Diriez-vous d'utiliser ce qui suit:
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)
sortie:
[(1, 2, 3), (2, 3, 4), (3, 4, 5), (4, 5, 6), (5, 6, 7)]
C'est une vieille question mais pour ceux encore intéressés il y a une grande implémentation d'un glisseur de fenêtre utilisant des générateurs dans cette page (par Adrian Rosebrock).
il s'agit d'une implémentation pour OpenCV mais vous pouvez facilement l'utiliser à d'autres fins. Pour les impatients, je vais coller le code ici, mais pour mieux le comprendre, je recommande de visiter la page originale.
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]])
Tip: vous pouvez vérifier le .shape
de la fenêtre lors de l'itération du générateur pour écarter ceux qui ne répondent pas à vos exigences
Cheers
Modifié DiPaolo la réponse de pour permettre à l'arbitraire de remplir et de l'étape de variable de taille
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
#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]
"""