Нарезка массива NumPy 2d или как извлечь подматрицу mxm из массива nxn (n> m)?
Я хочу обрезать массив 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 делать копию желаемого массива, поэтому при обработке большие массивы, это может стать проблемой. Почему это так/как работает этот механизм?
Ответы
Ответ 1
Чтобы ответить на этот вопрос, мы должны посмотреть, как индексирование многомерного массива работает в 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]
Ответ 2
Как отметил Свен, 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])]
. Это может избавить вас от необходимости вводить во все эти дополнительные скобки.
Ответ 3
Я не думаю, что x [[1,3]] [:, [1,3]] вряд ли читаем. Если вы хотите более четко понять свои намерения, вы можете сделать:
a[[1,3],:][:,[1,3]]
Я не эксперт в разрезе, но обычно, если вы пытаетесь нарезать массив и значения непрерывны, вы возвращаете представление, в котором изменяется значение шага.
например. На ваших входах 33 и 34, хотя вы получаете массив 2x2, шаг равен 4. Таким образом, при индексировании следующей строки указатель перемещается в правильное положение в памяти.
Ясно, что этот механизм не хорошо переносится на массив индексов. Следовательно, numpy должен будет сделать копию. В конце концов, многие другие математические функции матрицы зависят от размера, шага и непрерывного выделения памяти.
Ответ 4
Если вы хотите пропустить каждую другую строку и каждый другой столбец, вы можете сделать это с помощью базовой нарезки:
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
- это последовательности. Это, конечно же, даст вам копию, а не представление, вашего исходного массива. Это как и следовало ожидать, поскольку массив numpy использует непрерывную память (с постоянными шагами), и не было бы способа генерировать представление с произвольными строками и столбцами (поскольку для этого потребовались бы непостоянные шаги).
Ответ 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 предыдущего массива.