Нарізання масиву NdPy 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]])

при цьому 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 попереднього масиву.


1
Це рішення не працює, оскільки характерне для рядків / стовпців, які я намагався витягнути. Уявіть те саме в матриці розміром 50x50, коли я хочу витягнути рядки / стовпці 5,11,12,32,39,45, немає ніякого способу зробити це простими фрагментами. Вибачте, якщо мені не було зрозуміло в питанні.
levesque

3

У мене є подібне запитання тут: Написання в під-ndarray ndarray найбільш пітонічним способом. Пітон 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

Я не впевнений, наскільки це ефективно, але ви можете використовувати діапазон () для зрізу на обох осях

 x=np.arange(16).reshape((4,4))
 x[range(1,3), :][:,range(1,3)] 
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.