Разрезание массива NumPy 2d или как извлечь подматрицу mxm из массива nxn (n> m)?

174

Я хочу нарезать массив NumPy nxn. Я хочу извлечь произвольный выбор из m строк и столбцов этого массива (т.е. без какого-либо шаблона в количестве строк / столбцов), сделав его новым массивом mxm. Для этого примера допустим, что массив 4x4, и я хочу извлечь из него массив 2x2.

Вот наш массив:

from numpy import *
x = range(16)
x = reshape(x,(4,4))

print x
[[ 0  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]
 [12 13 14 15]]

Строка и столбцы для удаления совпадают. Самый простой случай, когда я хочу извлечь подматрицу 2x2, которая находится в начале или в конце, то есть:

In [33]: x[0:2,0:2]
Out[33]: 
array([[0, 1],
       [4, 5]])

In [34]: x[2:,2:]
Out[34]: 
array([[10, 11],
       [14, 15]])

Но что, если мне нужно удалить другую смесь строк / столбцов? Что если мне нужно удалить первую и третью строки / строки, извлекая таким образом подматрицу [[5,7],[13,15]]? Может быть любая композиция строк / строк. Я где-то читал, что мне просто нужно проиндексировать мой массив, используя массивы / списки индексов для строк и столбцов, но это, похоже, не работает:

In [35]: x[[1,3],[1,3]]
Out[35]: array([ 5, 15])

Я нашел один способ, а именно:

    In [61]: x[[1,3]][:,[1,3]]
Out[61]: 
array([[ 5,  7],
       [13, 15]])

Первая проблема заключается в том, что она едва читаема, хотя я могу жить с этим. Если у кого-то есть лучшее решение, я, конечно, хотел бы услышать это.

Другое дело, что я читал на форуме, что индексация массивов с помощью массивов вынуждает NumPy сделать копию нужного массива, поэтому при обработке больших массивов это может стать проблемой. Почему это так / как работает этот механизм?

Левек
источник

Ответы:

62

Как упомянул Свен, x[[[0],[2]],[1,3]]вернет 0 и 2 строки, которые соответствуют столбцам 1 и 3, а x[[0,2],[1,3]]в массиве вернет значения x [0,1] и x [2,3].

Для первого примера, который я привел, есть полезная функция numpy.ix_. Вы можете сделать то же самое, что и мой первый пример с x[numpy.ix_([0,2],[1,3])]. Это может избавить вас от необходимости вводить все эти дополнительные скобки.

Джастин Пил
источник
111

Чтобы ответить на этот вопрос, мы должны посмотреть, как работает индексирование многомерного массива в Numpy. Давайте сначала скажем, у вас есть массив xиз вашего вопроса. Буфер, назначенный для этого, xбудет содержать 16 возрастающих целых чисел от 0 до 15. Если вы обращаетесь к одному элементу, скажем x[i,j], NumPy должен выяснить расположение в памяти этого элемента относительно начала буфера. Это делается путем вычисления в действительности i*x.shape[1]+j(и умножения на размер int, чтобы получить фактическое смещение памяти).

Если вы извлекаете подмассив с помощью базовых срезов y = x[0:2,0:2], результирующий объект будет совместно использовать базовый буфер x. Но что произойдет, если вы получите доступ y[i,j]? NumPy нельзя использовать i*y.shape[1]+jдля вычисления смещения в массиве, поскольку принадлежащие данные yне являются последовательными в памяти.

NumPy решает эту проблему путем введения шагов . При вычислении смещения памяти для доступа x[i,j]на самом деле рассчитывается i*x.strides[0]+j*x.strides[1](и это уже включает в себя коэффициент для размера int):

x.strides
(16, 4)

Когда yизвлекается , как и выше, NumPy не создает новый буфер, но он делает создать новый объект массива , ссылающийся на тот же буфер ( в противном случае yбудет просто равно x.) Новый объект массив будет иметь другую форму , то xи может быть другой отправной смещение в буфер, но будет делиться шагами с x(по крайней мере, в этом случае):

y.shape
(2,2)
y.strides
(16, 4)

Таким образом, вычисление смещения памяти для y[i,j]даст правильный результат.

Но что NumPy должен сделать для чего-то подобного z=x[[1,3]]? Механизм шагов не позволит правильно индексировать, если используется оригинальный буфер z. NumPy теоретически может добавить более сложный механизм, чем шаги, но это сделает доступ к элементу относительно дорогим, что каким-то образом бросает вызов всей идее массива. Кроме того, представление больше не будет действительно легким объектом.

Это подробно описано в документации NumPy по индексированию .

Да, и почти забыл о вашем реальном вопросе: Вот как заставить индексирование работать с несколькими списками так, как ожидалось:

x[[[1],[3]],[1,3]]

Это связано с тем, что индексные массивы передаются в общей форме. Конечно, для этого конкретного примера вы также можете обойтись с базовой нарезкой:

x[1::2, 1::2]
Свен Марнах
источник
Можно было бы создать подклассы для массивов, чтобы можно было иметь объект "slcie-view", который переназначил бы индексы в исходный массив. Это, возможно, может удовлетворить потребности ОП
Jsbueno
@jsbueno: это будет работать для кода Python, но не для подпрограмм C / Fortran, которые оборачивает Scipy / Numpy. Эти обернутые рутины - вот где сила Numpy.
Дат Чу
Так ... в чем разница между x [[[1], [3]], [1,3]] и x [[1,3],:] [:, [1,3]]? Я имею в виду, есть ли вариант, который лучше использовать, чем другой?
Levesque
1
@JC: x[[[1],[3]],[1,3]]создает только один новый массив, копируя x[[1,3],:][:,[1,3]]дважды, поэтому используйте первый.
Свен Марнач
@JC: Или используйте метод из ответа Джастина.
Свен Марнач
13

Я не думаю, что x[[1,3]][:,[1,3]]это вряд ли читается. Если вы хотите лучше понять свои намерения, вы можете сделать:

a[[1,3],:][:,[1,3]]

Я не эксперт в нарезке, но обычно, если вы пытаетесь нарезать массив, а значения являются непрерывными, вы получаете представление, в котором изменяется значение шага.

Например, в ваших входах 33 и 34, хотя вы получаете массив 2x2, шаг равен 4. Таким образом, когда вы индексируете следующую строку, указатель перемещается в правильную позицию в памяти.

Ясно, что этот механизм не очень хорошо подходит для массива индексов. Следовательно, NumPy должен будет сделать копию. В конце концов, многие другие математические функции матрицы зависят от размера, шага и непрерывного выделения памяти.

Дат Чу
источник
10

Если вы хотите пропустить все остальные строки и все остальные столбцы, то вы можете сделать это с помощью базовой нарезки:

In [49]: x=np.arange(16).reshape((4,4))
In [50]: x[1:4:2,1:4:2]
Out[50]: 
array([[ 5,  7],
       [13, 15]])

Это возвращает представление, а не копию вашего массива.

In [51]: y=x[1:4:2,1:4:2]

In [52]: y[0,0]=100

In [53]: x   # <---- Notice x[1,1] has changed
Out[53]: 
array([[  0,   1,   2,   3],
       [  4, 100,   6,   7],
       [  8,   9,  10,  11],
       [ 12,  13,  14,  15]])

while z=x[(1,3),:][:,(1,3)]использует расширенную индексацию и, таким образом, возвращает копию:

In [58]: x=np.arange(16).reshape((4,4))
In [59]: z=x[(1,3),:][:,(1,3)]

In [60]: z
Out[60]: 
array([[ 5,  7],
       [13, 15]])

In [61]: z[0,0]=0

Обратите внимание, что xбез изменений:

In [62]: x
Out[62]: 
array([[ 0,  1,  2,  3],
       [ 4,  5,  6,  7],
       [ 8,  9, 10, 11],
       [12, 13, 14, 15]])

Если вы хотите выбрать произвольные строки и столбцы, то вы не можете использовать базовую нарезку. Вам придется использовать расширенную индексацию, используя что-то вроде x[rows,:][:,columns], где rowsи columnsявляются последовательностями. Это, конечно, даст вам копию, а не вид вашего исходного массива. Это, как и следовало ожидать, поскольку массивный массив использует непрерывную память (с постоянными шагами), и не было бы никакого способа генерировать представление с произвольными строками и столбцами (так как это потребовало бы непостоянных шагов).

unutbu
источник
5

С помощью numpy вы можете передать срез для каждого компонента индекса, поэтому ваш x[0:2,0:2]приведенный выше пример работает.

Если вы просто хотите равномерно пропустить столбцы или строки, вы можете передать срезы с тремя компонентами (то есть начало, остановка, шаг).

Опять же, для вашего примера выше:

>>> x[1:4:2, 1:4:2]
array([[ 5,  7],
       [13, 15]])

Что в основном: срез в первом измерении, начиная с индекса 1, останавливается, когда индекс равен или больше 4, и добавляет 2 к индексу в каждом проходе. То же самое для второго измерения. Опять же: это работает только для постоянных шагов.

Синтаксис, который вам нужно сделать, чтобы сделать что-то совсем другое внутри - что на x[[1,3]][:,[1,3]]самом деле делает, это создает новый массив, включающий только строки 1 и 3 из исходного массива (сделано с x[[1,3]]деталью), а затем повторно разрезает это - создавая третий массив - включая только столбцы 1 и 3 предыдущего массива.

jsbueno
источник
1
Это решение не работает, поскольку оно специфично для строк / столбцов, которые я пытался извлечь. Представьте то же самое в матрице 50x50, когда я хочу извлечь строки / столбцы 5,11,12,32,39,45, это невозможно сделать с помощью простых срезов. Извините, если я не был ясен в своем вопросе.
Levesque
3

У меня есть подобный вопрос здесь: написание в под-массиве ndarray наиболее питонским способом. Python 2 .

Следуя решению предыдущего поста для вашего случая, решение выглядит так:

columns_to_keep = [1,3] 
rows_to_keep = [1,3]

Использование ix_:

x[np.ix_(rows_to_keep, columns_to_keep)] 

Который:

array([[ 5,  7],
       [13, 15]])
Рафаэль Валеро
источник
0

Я не уверен, насколько это эффективно, но вы можете использовать range () для нарезки по обеим осям

 x=np.arange(16).reshape((4,4))
 x[range(1,3), :][:,range(1,3)] 
Валерий Марсель
источник