Выделение нескольких фрагментов из массива одновременно
Я ищу способ выбрать несколько фрагментов из массива Numpy одновременно. Скажем, у нас есть одномерный массив данных и мы хотим извлечь три его части, как показано ниже:
data_extractions = []
for start_index in range(0, 3):
data_extractions.append(data[start_index: start_index + 5])
потом data_extractions
будет:
data_extractions = [
data[0:5],
data[1:6],
data[2:7]
]
Есть ли способ выполнить вышеуказанную операцию без цикла for? Какая-то схема индексации в numpy, которая позволила бы мне выбрать несколько фрагментов из массива и вернуть их в виде такого количества массивов, скажем, в n+1-мерном массиве?
Я подумал, что, может быть, я смогу повторить свои данные и затем выбрать диапазон из каждой строки, но код ниже выдает IndexError
replicated_data = np.vstack([data] * 3)
data_extractions = replicated_data[[range(3)], [slice(0, 5), slice(1, 6), slice(2, 7)]
7 ответов
Вы можете использовать индексы, чтобы выбрать нужные вам строки в нужной форме. Например:
data = np.random.normal(size=(100,2,2,2))
# Creating an array of row-indexes
indexes = np.array([np.arange(0,5), np.arange(1,6), np.arange(2,7)])
# data[indexes] will return an element of shape (3,5,2,2,2). Converting
# to list happens along axis 0
data_extractions = list(data[indexes])
np.all(data_extractions[1] == s[1:6])
True
stride_tricks
может сделать это
a = np.arange(10)
b = np.lib.stride_tricks.as_strided(a, (3, 5), 2 * a.strides)
b
# array([[0, 1, 2, 3, 4],
# [1, 2, 3, 4, 5],
# [2, 3, 4, 5, 6]])
Обратите внимание, что b
ссылается на ту же память, что и a
фактически несколько раз (например, b[0, 1]
а также b[1, 0]
один и тот же адрес памяти). Поэтому наиболее безопасно сделать копию, прежде чем работать с новой структурой.
и может быть сделано аналогичным образом, например, 2d -> 4d
a = np.arange(16).reshape(4, 4)
b = np.lib.stride_tricks.as_strided(a, (3,3,2,2), 2*a.strides)
b.reshape(9,2,2) # this forces a copy
# array([[[ 0, 1],
# [ 4, 5]],
# [[ 1, 2],
# [ 5, 6]],
# [[ 2, 3],
# [ 6, 7]],
# [[ 4, 5],
# [ 8, 9]],
# [[ 5, 6],
# [ 9, 10]],
# [[ 6, 7],
# [10, 11]],
# [[ 8, 9],
# [12, 13]],
# [[ 9, 10],
# [13, 14]],
# [[10, 11],
# [14, 15]]])
В этом посте есть подход с strided-indexing scheme
с помощью np.lib.stride_tricks.as_strided
это в основном создает представление во входном массиве и, как таковое, довольно эффективно для создания, и, будучи представлением, занимает меньше места в памяти. Также это работает для ndarrays с общим числом измерений.
Вот реализация -
def strided_axis0(a, L):
# Store the shape and strides info
shp = a.shape
s = a.strides
# Compute length of output array along the first axis
nd0 = shp[0]-L+1
# Setup shape and strides for use with np.lib.stride_tricks.as_strided
# and get (n+1) dim output array
shp_in = (nd0,L)+shp[1:]
strd_in = (s[0],) + s
return np.lib.stride_tricks.as_strided(a, shape=shp_in, strides=strd_in)
Пробный прогон для 4D
случай массива -
In [44]: a = np.random.randint(11,99,(10,4,2,3)) # Array
In [45]: L = 5 # Window length along the first axis
In [46]: out = strided_axis0(a, L)
In [47]: np.allclose(a[0:L], out[0]) # Verify outputs
Out[47]: True
In [48]: np.allclose(a[1:L+1], out[1])
Out[48]: True
In [49]: np.allclose(a[2:L+2], out[2])
Out[49]: True
В общем случае вы должны выполнить какую-то итерацию - и конкатенацию - либо при построении индексов, либо при сборе результатов. Только когда шаблон нарезки сам по себе является регулярным, вы можете использовать обобщенную нарезку через as_strided
,
Принятый ответ создает индексный массив, по одной строке на срез. Так что это перебирает ломтики, и arange
сама по себе (быстрая) итерация. А также np.array
объединяет их на новой оси (np.stack
обобщает это).
In [264]: np.array([np.arange(0,5), np.arange(1,6), np.arange(2,7)])
Out[264]:
array([[0, 1, 2, 3, 4],
[1, 2, 3, 4, 5],
[2, 3, 4, 5, 6]])
indexing_tricks
удобные способы сделать то же самое:
In [265]: np.r_[0:5, 1:6, 2:7]
Out[265]: array([0, 1, 2, 3, 4, 1, 2, 3, 4, 5, 2, 3, 4, 5, 6])
Это берет обозначение нарезки, расширяет его arange
и объединяет. Это даже позволяет мне расширять и объединять в 2d
In [269]: np.r_['0,2',0:5, 1:6, 2:7]
Out[269]:
array([[0, 1, 2, 3, 4],
[1, 2, 3, 4, 5],
[2, 3, 4, 5, 6]])
In [270]: data=np.array(list('abcdefghijk'))
In [272]: data[np.r_['0,2',0:5, 1:6, 2:7]]
Out[272]:
array([['a', 'b', 'c', 'd', 'e'],
['b', 'c', 'd', 'e', 'f'],
['c', 'd', 'e', 'f', 'g']],
dtype='<U1')
In [273]: data[np.r_[0:5, 1:6, 2:7]]
Out[273]:
array(['a', 'b', 'c', 'd', 'e', 'b', 'c', 'd', 'e', 'f', 'c', 'd', 'e',
'f', 'g'],
dtype='<U1')
Объединение результатов после индексации также работает.
In [274]: np.stack([data[0:5],data[1:6],data[2:7]])
Мои воспоминания о других SO вопросах таковы, что относительные временные интервалы имеют тот же порядок величины. Это может варьироваться, например, в зависимости от количества срезов в зависимости от их длины. В целом количество значений, которые необходимо скопировать из источника в цель, будет одинаковым.
Если срезы различаются по длине, вам придется использовать плоскую индексацию.
Вы можете нарезать свой массив с подготовленным массивом нарезки
a = np.array(list('abcdefg'))
b = np.array([
[0, 1, 2, 3, 4],
[1, 2, 3, 4, 5],
[2, 3, 4, 5, 6]
])
a[b]
Тем не мение, b
не должен генерироваться вручную таким образом. Это может быть более динамичным с
b = np.arange(5) + np.arange(3)[:, None]
Независимо от того, какой подход вы выберете, если 2 слайса содержат один и тот же элемент, он не поддерживает правильно математические операции, если вы не используете
ufunc.at
который может быть более неэффективным, чем цикл. Для тестирования:
def as_strides(arr, window_size, stride, writeable=False):
'''Get a strided sub-matrices view of a 4D ndarray.
Args:
arr (ndarray): input array with shape (batch_size, m1, n1, c).
window_size (tuple): with shape (m2, n2).
stride (tuple): stride of windows in (y_stride, x_stride).
writeable (bool): it is recommended to keep it False unless needed
Returns:
subs (view): strided window view, with shape (batch_size, y_nwindows, x_nwindows, m2, n2, c)
See also numpy.lib.stride_tricks.sliding_window_view
'''
batch_size = arr.shape[0]
m1, n1, c = arr.shape[1:]
m2, n2 = window_size
y_stride, x_stride = stride
view_shape = (batch_size, 1 + (m1 - m2) // y_stride,
1 + (n1 - n2) // x_stride, m2, n2, c)
strides = (arr.strides[0], y_stride * arr.strides[1],
x_stride * arr.strides[2]) + arr.strides[1:]
subs = np.lib.stride_tricks.as_strided(arr,
view_shape,
strides=strides,
writeable=writeable)
return subs
import numpy as np
np.random.seed(1)
Xs = as_strides(np.random.randn(1, 5, 5, 2), (3, 3), (2, 2), writeable=True)[0]
print('input\n0,0\n', Xs[0, 0])
np.add.at(Xs, np.s_[:], 5)
print('unbuffered sum output\n0,0\n', Xs[0,0])
np.add.at(Xs, np.s_[:], -5)
Xs = Xs + 5
print('normal sum output\n0,0\n', Xs[0, 0])
Мы можем использовать списки для этого
data=np.array([1,2,3,4,5,6,7,8,9,10])
data_extractions=[data[b:b+5] for b in [1,2,3,4,5]]
data_extractions
Результаты
[array([2, 3, 4, 5, 6]), array([3, 4, 5, 6, 7]), array([4, 5, 6, 7, 8]), array([5, 6, 7, 8, 9]), array([ 6, 7, 8, 9, 10])]