J'ai un tableau de forme numpy (6,2)
[[00,01],
[10,11],
[20,21],
[30,31],
[40,41],
[50,51]]
J'ai besoin d'une fenêtre coulissante avec une taille de pas 1 et une taille de fenêtre 3 aime ça:
[[00,01,10,11,20,21],
[10,11,20,21,30,31],
[20,21,30,31,40,41],
[30,31,40,41,50,51]]
Je cherche une solution numpy. Si votre solution pouvait paramétrer la forme du tableau d'origine ainsi que la taille de la fenêtre et la taille des pas, ce serait parfait.
J'ai trouvé cette réponse connexe en utilisant des enjambées pour un filtre de moyenne mobile efficace mais je ne vois pas comment y spécifier la taille de l'étape et comment réduire la fenêtre du 3d à un tableau 2D continu. Aussi ceci Itérateur de fenêtre coulissante ou coulissante en Python mais c'est en Python et je ne suis pas sûr de son efficacité. En outre, il prend en charge les éléments mais ne les joint pas ensemble à la fin si chaque élément a plusieurs fonctionnalités.
In [1]: import numpy as np
In [2]: a = np.array([[00,01], [10,11], [20,21], [30,31], [40,41], [50,51]])
In [3]: w = np.hstack((a[:-2],a[1:-1],a[2:]))
In [4]: w
Out[4]:
array([[ 0, 1, 10, 11, 20, 21],
[10, 11, 20, 21, 30, 31],
[20, 21, 30, 31, 40, 41],
[30, 31, 40, 41, 50, 51]])
Vous pouvez écrire ceci en tant que fonction:
def window_stack(a, stepsize=1, width=3):
n = a.shape[0]
return np.hstack( a[i:1+n+i-width:stepsize] for i in range(0,width) )
Cela ne dépend pas vraiment de la forme du tableau d'origine, tant que a.ndim = 2
. Notez que je n'utilise jamais l'une ou l'autre des longueurs dans la version interactive. La deuxième dimension de la forme n'est pas pertinente; chaque ligne peut être aussi longue que vous le souhaitez. Grâce à la suggestion de @ Jaime, vous pouvez le faire sans vérifier la forme du tout:
def window_stack(a, stepsize=1, width=3):
return np.hstack( a[i:1+i-width or None:stepsize] for i in range(0,width) )
Vous pouvez faire une fenêtre coulissante vectorisée en numpy en utilisant une indexation sophistiquée.
>>> import numpy as np
>>> a = np.array([[00,01], [10,11], [20,21], [30,31], [40,41], [50,51]])
>>> a
array([[ 0, 1],
[10, 11],
[20, 21], #define our 2d numpy array
[30, 31],
[40, 41],
[50, 51]])
>>> a = a.flatten()
>>> a
array([ 0, 1, 10, 11, 20, 21, 30, 31, 40, 41, 50, 51]) #flattened numpy array
>>> indexer = np.arange(6)[None, :] + 2*np.arange(4)[:, None]
>>> indexer
array([[ 0, 1, 2, 3, 4, 5],
[ 2, 3, 4, 5, 6, 7], #sliding window indices
[ 4, 5, 6, 7, 8, 9],
[ 6, 7, 8, 9, 10, 11]])
>>> a[indexer]
array([[ 0, 1, 10, 11, 20, 21],
[10, 11, 20, 21, 30, 31], #values of a over sliding window
[20, 21, 30, 31, 40, 41],
[30, 31, 40, 41, 50, 51]])
>>> np.sum(a[indexer], axis=1)
array([ 63, 123, 183, 243]) #sum of values in 'a' under the sliding window.
Explication de ce que fait ce code.
np.arange(6)[None, :]
crée un vecteur ligne 0 à 6 et np.arange(4)[:, None]
crée un vecteur colonne 0 à 4. Il en résulte une matrice 4x6 où chaque ligne (six d'entre elles) représente une fenêtre, et le nombre de lignes (quatre d'entre elles) représente le nombre de fenêtres. Le multiple de 2 fait glisser la fenêtre coulissante de 2 unités à la fois, ce qui est nécessaire pour glisser sur chaque tuple. En utilisant le découpage du tableau numpy, vous pouvez passer la fenêtre coulissante dans le tableau numpy aplati et faire des agrégats sur eux comme sum.
La solution est
np.lib.stride_tricks.as_strided(a, shape=(4,6), strides=(8,4))
.
L'utilisation de foulées est intuitive lorsque vous commencez à penser en termes de pointeurs/adresses.
La méthode as_strided()
a 3 arguments.
data est le tableau sur lequel nous opérons.
Pour utiliser as_strided()
pour implémenter des fonctions de fenêtre coulissante, nous devons calculer la forme de la sortie au préalable. Dans la question, (4,6) est la forme de la production. Si les dimensions ne sont pas correctes, nous finissons par lire des valeurs de déchets. En effet, nous accédons aux données en déplaçant le pointeur de quelques octets (selon le type de données).
La détermination de la valeur correcte de foulées est essentielle pour obtenir les résultats attendus. Avant de calculer les foulées, recherchez la mémoire occupée par chaque élément à l'aide de arr.strides[-1]
. Dans cet exemple, la mémoire occupée par un élément est de 4 octets. Les tableaux Numpy sont créés en mode majeur. Le premier élément de la ligne suivante est juste à côté du dernier élément de la ligne actuelle.
Ex: 0, 1 | 10, 11 | ...
10 est juste à côté de 1.
Imaginez le tableau 2D remodelé en 1D (cela est acceptable car les données sont stockées dans un format de ligne principale). Le premier élément de chaque ligne dans la sortie est l'élément indexé impair dans le tableau 1D. 0, 10, 20, 30, ..
Par conséquent, le nombre d'étapes en mémoire que nous devons prendre pour passer de 0 à 10, 10 à 20, etc. est taille de l'élément 2 * mem. Chaque ligne a une foulée de 2 * 4 octets = 8. Pour une ligne donnée dans la sortie, tous les éléments sont adjacents les uns aux autres dans notre tableau 1D imaginaire. Pour obtenir l'élément suivant dans une rangée, il suffit de faire une foulée égale à la taille d'un élément. La valeur de la foulée de colonne est de 4 octets.
Par conséquent, strides=(8,4)
Une autre explication: la sortie a une forme de (4,6). Pas de colonne 4
. Ainsi, les premiers éléments de ligne commencent à l'index 0
Et ont 6 éléments chacun espacés de 4 octets. Une fois la première ligne collectée, la deuxième ligne démarre à 8 octets du début de la ligne actuelle. La troisième ligne commence à 8 octets du point de départ de la deuxième ligne et ainsi de suite.
La forme détermine le nombre de lignes et de colonnes dont nous avons besoin. les foulées définissent les étapes de la mémoire pour démarrer une ligne et collecter un élément de colonne
Une compréhension de la liste courte est possible avec more_itertools.windowed
1:
Étant donné
import numpy as np
import more_itertools as mit
a = [["00","01"],
["10","11"],
["20","21"],
["30","31"],
["40","41"],
["50","51"]]
b = np.array(a)
Code
np.array([list(mit.flatten(w)) for w in mit.windowed(a, n=3)])
ou
np.array([[i for item in w for i in item] for w in mit.windowed(a, n=3)])
ou
np.array(list(mit.windowed(b.ravel(), n=6)))
Production
array([['00', '01', '10', '11', '20', '21'],
['10', '11', '20', '21', '30', '31'],
['20', '21', '30', '31', '40', '41'],
['30', '31', '40', '41', '50', '51']],
dtype='<U2')
Des fenêtres coulissantes de taille n=3
Sont créées et aplaties. Notez que la taille de pas par défaut est more_itertools.windowed(..., step=1)
.
Performances
En tant que tableau, la réponse acceptée est la plus rapide.
%timeit np.hstack((a[:-2], a[1:-1], a[2:]))
# 37.5 µs ± 1.88 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
%timeit np.hstack((b[:-2], b[1:-1], b[2:]))
# 12.9 µs ± 166 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
%timeit np.array([list(mit.flatten(w)) for w in mit.windowed(a, n=3)])
# 23.2 µs ± 1.73 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
%timeit np.array([[i for item in w for i in item] for w in mit.windowed(a, n=3)])
# 21.2 µs ± 999 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
%timeit np.array(list(mit.windowed(b.ravel(), n=6)))
# 43.4 µs ± 374 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
Une bibliothèque tierce qui implémente recettes itertool et de nombreux outils utiles.
Ceci est une pure implémentation Python:
def sliding_window(arr, window=3):
i = iter(arr)
a = []
for e in range(0, window): a.append(next(i))
yield a
for e in i:
a = a[1:] + [e]
yield a
Un exemple:
# flatten array
flatten = lambda l: [item for sublist in l for item in sublist]
a = [[0,1], [10,11], [20,21], [30,31], [40,41], [50,51]]
w = sliding_window(a, width=3)
print( list(map(flatten,w)) )
[[0, 1, 10, 11, 20, 21], [10, 11, 20, 21, 30, 31], [20, 21, 30, 31, 40, 41], [30, 31, 40, 41, 50, 51]]
Benchmark
import timeit
def benchmark():
a = [[0,1], [10,11], [20,21], [30,31], [40,41], [50,51]]
sliding_window(a, width=3)
times = timeit.Timer(benchmark).repeat(3, number=1000)
time_taken = min(times) / 1000
print(time_taken)
1.0944640007437556e-06