Последовательные перекрывающиеся подмножества массива (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, делающий именно это :)


person Sney    schedule 21.03.2010    source источник


Ответы (7)


Кажется, самым быстрым способом является предварительное выделение массива, указанное в качестве опции 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 мс).

person John La Rooy    schedule 21.03.2010
comment
Обратите внимание, что версия islice, вероятно, глупа в этом конкретном случае использования. Это медленнее (нарезка — O(n); нарезка — O(1)). Это может быть несколько более эффективным с точки зрения памяти (может быть, а может быть и нет — пустые слайсы не копируются), но если бы мы хотели оптимизировать память, мы бы вообще никогда не формировали никаких списков. Конечно, тесты говорят, а догадки ходят. - person Mike Graham; 21.03.2010
comment
Если бы я действительно занимался бенчмаркингом (глупой, но достаточно забавной задачей), я бы, вероятно, попытался сэкономить еще пару наносекунд, используя numpy.empty вместо numpy.zeros и используя массив столбцов, чтобы иметь дело с непрерывной памятью при установке столбцы. - person Mike Graham; 21.03.2010
comment
Очень хорошо, но посмотрите на ответ Пола ниже, я проверил его, и он примерно в 5 раз быстрее, чем 7-й метод. - person Yukio Fukuzawa; 08.04.2017
comment
Я пробовал выше и продолжал получать TypeError: итерация по массиву 0-d. Мне пришлось использовать np.array(list(zip(A,A[1:],A[2:],...))) - person Zak Keirn; 24.03.2018

Вы должны использовать stride_tricks. Когда я впервые увидел это, на ум пришло слово «волшебство». Это простой и, безусловно, самый быстрый метод.

>>> 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. Вот слайды для получения дополнительных пояснений.

person Paul    schedule 21.03.2010
comment
Прежде всего, я хотел бы сказать, что это фантастическое решение, спасибо, что указали на него! Обратите внимание, что на моей машине длина шага составляет 8 бит (можно проверить с помощью a.strides, как это предлагается в предоставленной вами ссылке) - вероятно, 64-битная или 32-битная архитектура по умолчанию (хотя я не проверял). То есть команда на моей машине такая: ››› b = as_strided(a,(11,4),(8,8)) - person eldad-a; 12.06.2012
comment
@eldad-a для конкретики используйте b = as_strided(a,(11,4),(a.strides[0], a.strides[0])) - person Yukio Fukuzawa; 08.04.2017

Быстрое и грязное решение:

>>> 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]])
person Federico A. Ramponi    schedule 21.03.2010
comment
В этом нет ничего грязного. - person Mike Graham; 21.03.2010
comment
Конечно быстро - смотрите тайминги в моем ответе. Также легко обобщить на куски разного размера. - person John La Rooy; 21.03.2010

Используя 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))
person Alex Martelli    schedule 21.03.2010
comment
Этот код не работает. numpy интерпретировал бы объект izip как скаляр и сделал бы из него массив rank-1, length-1 dtype object. - person Mike Graham; 21.03.2010
comment
Кроме того, вам необходимо from itertools import tee, izip или использовать полные имена. - person Mike Graham; 21.03.2010
comment
Я думал, что numpy.array принимает любые итерации - редактирование для исправления. - person Alex Martelli; 21.03.2010
comment
Кроме того, у вас есть обратная логика при обходе итераторов, которые вы указали. Столбцы в результирующем массиве были бы обратными, если бы код работал. - person Mike Graham; 21.03.2010
comment
@ Алекс Мартелли, numpy.array нет. Это может расстраивать, но я думаю, причина в том, что ему нужно предварительно выделить массив, чтобы он хотел заранее знать размер ввода. - person Mike Graham; 21.03.2010
comment
(В редактировании вы по-прежнему продвигаете более ранние итераторы больше. В примере вывода ОС вы будете больше продвигать более поздние итераторы.) - person Mike Graham; 21.03.2010
comment
Правильно -- отредактировано снова, чтобы исправить (next(itrs[n], None), а не next(itrs[i], None), как было раньше). - person Alex Martelli; 21.03.2010

Транслировать!

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 бит, 1 ГБ ОЗУ) дает

>>> %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-кратное ускорение вроде бы соответствует масштабированию.

person meduz    schedule 22.03.2010

Я не знаю ни одной функции Python stdlib, которая бы это делала. Это достаточно легко сделать. Вот генератор, который в основном это делает:

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. Если вы просто хотите перебрать эти фрагменты, вам не нужно беспокоиться. Если вам действительно нужно поместить результат в массив, вы можете сделать это таким или более эффективным способом.

person Mike Graham    schedule 21.03.2010

Эффективный способ NumPy сделать это приведен здесь, который слишком длинно, чтобы воспроизвести его здесь. Это сводится к использованию некоторых трюков с шагом и намного быстрее, чем itertools для больших размеров окна. Например, используя метод, практически такой же, как у Алекса Мартелли:

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
person Autoplectic    schedule 21.03.2010