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


Я хочу, чтобы нарезать массив NumPy собой. Я хочу извлечь произвольные выбор 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 делать копию нужного массива, поэтому при обработке с большими массивами это может стать проблемой. Почему это так и как работает этот механизм?

7 146

7 ответов:

как Sven сказано, 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])]. Это может избавить вас от необходимости вводить все эти дополнительные скобки.

чтобы ответить на этот вопрос, мы должны посмотреть, как индексирование многомерного массива работает в 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]

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

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

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

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

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

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

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]])

пока 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 последовательности. Это, конечно, даст вам копию, а не представление, вашего исходного массива. Это так, как и следовало ожидать, поскольку массив numpy использует непрерывную память (с постоянными шагами), и не было бы никакого способа создать представление с произвольными строками и столбцами (поскольку это потребовало бы непостоянных шагов).

С помощью 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 предыдущего массива.

у меня есть аналогичный вопрос здесь:запись в суб-ndarray из 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]])

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

 x=np.arange(16).reshape((4,4))
 x[range(1,3), :][:,range(1,3)]