Последовательные, перекрывающиеся подмножества массива (NumPy, Python)
У меня есть NumPy массив [1,2,3,4,5,6,7,8,9,10,11,12,13,14]
и хочу иметь массив, структурированный как [[1,2,3,4], [2,3,4,5], [3,4,5,6], ..., [11,12,13,14]]
.
Конечно, это возможно, перейдя по большому массиву и добавив в новый массив массивы длиной четыре, но мне любопытно, есть ли какой-то секретный "волшебный" метод Python, который делает именно это:)
Ответы
Ответ 1
Самый быстрый способ состоит в том, чтобы предварительно распределить массив, заданный как вариант 7, в нижней части этого ответа.
>>> import numpy as np
>>> A=np.array([1,2,3,4,5,6,7,8,9,10,11,12,13,14])
>>> A
array([ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14])
>>> np.array(zip(A,A[1:],A[2:],A[3:]))
array([[ 1, 2, 3, 4],
[ 2, 3, 4, 5],
[ 3, 4, 5, 6],
[ 4, 5, 6, 7],
[ 5, 6, 7, 8],
[ 6, 7, 8, 9],
[ 7, 8, 9, 10],
[ 8, 9, 10, 11],
[ 9, 10, 11, 12],
[10, 11, 12, 13],
[11, 12, 13, 14]])
>>>
Вы можете легко адаптировать это, чтобы сделать это для размера переменной куска.
>>> n=5
>>> np.array(zip(*(A[i:] for i in range(n))))
array([[ 1, 2, 3, 4, 5],
[ 2, 3, 4, 5, 6],
[ 3, 4, 5, 6, 7],
[ 4, 5, 6, 7, 8],
[ 5, 6, 7, 8, 9],
[ 6, 7, 8, 9, 10],
[ 7, 8, 9, 10, 11],
[ 8, 9, 10, 11, 12],
[ 9, 10, 11, 12, 13],
[10, 11, 12, 13, 14]])
Вы можете сравнить производительность между этим и с помощью itertools.islice
.
>>> from itertools import islice
>>> n=4
>>> np.array(zip(*[islice(A,i,None) for i in range(n)]))
array([[ 1, 2, 3, 4],
[ 2, 3, 4, 5],
[ 3, 4, 5, 6],
[ 4, 5, 6, 7],
[ 5, 6, 7, 8],
[ 6, 7, 8, 9],
[ 7, 8, 9, 10],
[ 8, 9, 10, 11],
[ 9, 10, 11, 12],
[10, 11, 12, 13],
[11, 12, 13, 14]])
Мои результаты синхронизации:
1. timeit np.array(zip(A,A[1:],A[2:],A[3:]))
10000 loops, best of 3: 92.9 us per loop
2. timeit np.array(zip(*(A[i:] for i in range(4))))
10000 loops, best of 3: 101 us per loop
3. timeit np.array(zip(*[islice(A,i,None) for i in range(4)]))
10000 loops, best of 3: 101 us per loop
4. timeit numpy.array([ A[i:i+4] for i in range(len(A)-3) ])
10000 loops, best of 3: 37.8 us per loop
5. timeit numpy.array(list(chunks(A, 4)))
10000 loops, best of 3: 43.2 us per loop
6. timeit numpy.array(byN(A, 4))
10000 loops, best of 3: 100 us per loop
# Does preallocation of the array help? (11 is from len(A)+1-4)
7. timeit B=np.zeros(shape=(11, 4),dtype=np.int32)
100000 loops, best of 3: 2.19 us per loop
timeit for i in range(4):B[:,i]=A[i:11+i]
10000 loops, best of 3: 20.9 us per loop
total 23.1us per loop
По мере увеличения len (A) (20000) 4 и 5 сходятся к эквивалентной скорости (44 мс). 1,2,3 и 6 все остаются примерно в 3 раза медленнее (135 мс). 7 намного быстрее (1,36 мс).
Ответ 2
Вы должны использовать stride_tricks
. Когда я впервые увидел это, слово "магия" было spring. Это простой и, безусловно, самый быстрый способ.
>>> as_strided = numpy.lib.stride_tricks.as_strided
>>> a = numpy.arange(1,15)
>>> a
array([ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14])
>>> b = as_strided(a, (11,4), a.strides*2)
>>> b
array([[ 1, 2, 3, 4],
[ 2, 3, 4, 5],
[ 3, 4, 5, 6],
[ 4, 5, 6, 7],
[ 5, 6, 7, 8],
[ 6, 7, 8, 9],
[ 7, 8, 9, 10],
[ 8, 9, 10, 11],
[ 9, 10, 11, 12],
[10, 11, 12, 13],
[11, 12, 13, 14]])
Имейте в виду, что значения в массиве b
являются значениями в a
, просто просматриваются по-разному. Сделайте .copy()
на b
, если вы планируете его модифицировать.
Я видел это на конференции SciPy. Ниже приведены slides.
Ответ 3
Быстрое и грязное решение:
>>> a = numpy.arange(1,15)
>>> numpy.array([ a[i:i+4] for i in range(len(a)-3) ])
array([[ 1, 2, 3, 4],
[ 2, 3, 4, 5],
[ 3, 4, 5, 6],
[ 4, 5, 6, 7],
[ 5, 6, 7, 8],
[ 6, 7, 8, 9],
[ 7, 8, 9, 10],
[ 8, 9, 10, 11],
[ 9, 10, 11, 12],
[10, 11, 12, 13],
[11, 12, 13, 14]])
Ответ 4
Используя itertools и предполагая, что Python 2.6:
import itertools
def byN(iterable, N):
itrs = itertools.tee(iter(iterable), N)
for n in range(N):
for i in range(n):
next(itrs[n], None)
return zip(*itrs)
aby4 = numpy.array(byN(thearray, 4))
Ответ 5
Broadcast!
from numpy import ogrid
def stretch(N=5,M=15):
x, y = ogrid[0:M,0:N]
return x+y+1
Обратите внимание, что ogrid дает такие вещи, как:
>> ogrid[0:5,0:5]
>>
[array([[0],
[1],
[2],
[3],
[4]]),
array([[0, 1, 2, 3, 4]])]
Сравним с другим решением, приведенным здесь:
def zipping(N=5,M=15):
A = numpy.arange(1, M+1)
return numpy.array(zip(*(A[i:] for i in range(N))))
Сравнение (python 2.6, 32 бит, 1Go RAM) дает
>>> %timeit stretch(5,15)
10000 loops, best of 3: 61.2 us per loop
>>> %timeit zipping(5,15)
10000 loops, best of 3: 72.5 us per loop
>>> %timeit stretch(5,1e3)
10000 loops, best of 3: 128 us per loop
>>> %timeit zipping(5,1e3)
100 loops, best of 3: 4.25 ms per loop
40-кратное ускорение похоже на масштабирование.
Ответ 6
Я не знаю никакой функции stdlib Python, которая это делает. Это достаточно легко сделать. Вот генератор, который в основном делает это:
def chunks(sequence, length):
for index in xrange(0, len(sequence) - length + 1):
yield sequence[index:index + length]
Вы можете использовать его так:
>>> import numpy
>>> a = numpy.arange(1, 15)
>>> a
array([ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14])
>>> numpy.array(list(chunks(a, 4)))
array([[ 1, 2, 3, 4],
[ 2, 3, 4, 5],
[ 3, 4, 5, 6],
[ 4, 5, 6, 7],
[ 5, 6, 7, 8],
[ 6, 7, 8, 9],
[ 7, 8, 9, 10],
[ 8, 9, 10, 11],
[ 9, 10, 11, 12],
[10, 11, 12, 13],
[11, 12, 13, 14]])
Единственное странное в этом коде - это то, что я вызвал list
в результате chunks(a, 4)
. Это происходит потому, что numpy.array
не принимает произвольный итеративный, например, генератор chunks
возвращает. Если вы просто хотите перебирать эти куски, вам не нужно беспокоиться. Если вам действительно нужно поместить результат в массив, вы можете сделать это так или несколько более эффективных способов.
Ответ 7
Эффективный способ NumPy для этого предоставляется здесь, который здесь слишком длинный, чтобы воспроизвести здесь. Это сводится к использованию некоторых шаговых трюков и намного быстрее, чем itertools для больших размеров окон. Например, используя метод, по существу такой же, как у Alex Martelli's:
In [16]: def windowed(sequence, length):
seqs = tee(sequence, length)
[ seq.next() for i, seq in enumerate(seqs) for j in xrange(i) ]
return zip(*seqs)
Получаем:
In [19]: data = numpy.random.randint(0, 2, 1000000)
In [20]: %timeit windowed(data, 2)
100000 loops, best of 3: 6.62 us per loop
In [21]: %timeit windowed(data, 10)
10000 loops, best of 3: 29.3 us per loop
In [22]: %timeit windowed(data, 100)
1000 loops, best of 3: 1.41 ms per loop
In [23]: %timeit segment_axis(data, 2, 1)
10000 loops, best of 3: 30.1 us per loop
In [24]: %timeit segment_axis(data, 10, 9)
10000 loops, best of 3: 30.2 us per loop
In [25]: %timeit segment_axis(data, 100, 99)
10000 loops, best of 3: 30.5 us per loop