Нарезка NumPy с несколькими кортежами

Рассмотрим следующее:

import numpy as np

arr = np.arange(3 * 4 * 5).reshape((3, 4, 5))

Если я нарежу arr, используя slice, я получу, например:

arr[:, 0:2, :].shape
# (3, 2, 5)

Если теперь я нарежу arr, используя смесь slice() и tuple(), я получу:

arr[:, (0, 1), :].shape
# (3, 2, 5)

np.all(arr[:, (0, 1), :] == arr[:, :2, :])
# True

а также:

arr[:, :, (0, 1)].shape
# (3, 4, 2)

np.all(arr[:, :, (0, 1)] == arr[:, :, :2])
# True

ОДНАКО, если я сделаю:

arr[:, (0, 1), (0, 1)].shape
# (3, 2)

что в основном arr[:, 0, 0] и arr[:, 1, 1] объединено.

Я ожидал получить:

arr[:, (0, 1), (0, 1)].shape
# (3, 2, 2)

np.all(arr[:, (0, 1), (0, 1)] == arr[:, :2, :2])
# True

но это явно не так.

Если я объединим два отдельных среза, я смогу получить желаемый результат, то есть:

arr[:, (0, 1), :][:, :, (0, 1)].shape
# (3, 2, 2)

np.all(arr[:, (0, 1), :][:, :, (0, 1)] == arr[:, :2, :2])
# True

Можно ли получить тот же результат, что и arr[:, (0, 1), :][:, :, (0, 1)], но ИСПОЛЬЗУЯ один срез?

Теперь этот пример не так интересен, потому что я мог бы заменить tuple() на slice(), но если это не так, все становится намного более актуальным, например:

arr[:, (0, 2, 3), :][:, :, (0, 2, 3, 4)]
# [[[ 0  2  3  4]
#   [10 12 13 14]
#   [15 17 18 19]]

#  [[20 22 23 24]
#   [30 32 33 34]
#   [35 37 38 39]]

#  [[40 42 43 44]
#   [50 52 53 54]
#   [55 57 58 59]]]

для которого arr[:, (0, 2, 3), (0, 2, 3, 4)] был бы гораздо более удобным синтаксисом.

РЕДАКТИРОВАТЬ

Комментарии/ответы @Divakar @hpaulj и @MadPhysicist указывали на то, что правильно транслируемый Iterable эквивалентен множественным конкатенированным срезам.

Однако это не так, например:

s = np.ix_((0, 1), (0, 1, 2, 3))
arr[s[0], slice(3), s[1]]
# [[[ 0  5 10]
#   [ 1  6 11]
#   [ 2  7 12]
#   [ 3  8 13]]
# 
#  [[20 25 30]
#   [21 26 31]
#   [22 27 32]
#   [23 28 33]]]

Но:

arr[(0, 1), :, :][:, :3, :][:, :, (0, 1, 2, 3)]
# [[[ 0  1  2  3]
#   [ 5  6  7  8]
#   [10 11 12 13]]
# 
#  [[20 21 22 23]
#   [25 26 27 28]
#   [30 31 32 33]]]

а также:

np.all(arr[:2, :3, :4] == arr[(0, 1), :, :][:, :3, :][:, :, (0, 1, 2, 3)])
# True

np.all(arr[s[0], slice(3), s[1]] == arr[(0, 1), :, :][:, :3, :][:, :, (0, 1, 2, 3)])
# False

person norok2    schedule 27.05.2019    source источник
comment
С np.ix_ - x,y = np.ix_([0,2,3],[0,2,3,4]); arr[:,x,y]. Или arr[:,np.array([0,2,3])[:,None],[0,2,3,4]].   -  person Divakar    schedule 27.05.2019
comment
Вы ожидаете стиль индексации блоков MATLAB. Но чтобы получить такие элементы, как диагональ в MATLAB, вы должны использовать sub2ind для преобразования индексов в плоское индексирование. По сути, то, что просто в MATLAB, (просто) немного сложнее в numpy, и наоборот для другого.   -  person hpaulj    schedule 27.05.2019
comment
@hpaulj Это не работает так же, как несколько объединенных фрагментов, и я не вижу способа заставить его работать в одном фрагменте.   -  person norok2    schedule 28.05.2019
comment
Размещение среза посередине вызывает проблему с порядком измерений, описанную в документах. stackoverflow.com/q/55829631/901925   -  person hpaulj    schedule 28.05.2019
comment
Мне нужно проверить это, чтобы быть уверенным, но я думаю, что slice можно заменить другим кортежем или arange в аргументах ix_.   -  person hpaulj    schedule 28.05.2019
comment
@hpaulj В принципе, вы можете это сделать, но, как правило, вы не можете заменить slice(), не зная априори, каков размер объекта, который нужно разрезать, так что функция, например. ixb() для прозрачного использования, как arr[ixb(a, b, c)] вместо arr[a, :, :][:, b, :][:, :, c], независимо от того, что такое a, b и c, не может быть создано таким образом.   -  person norok2    schedule 28.05.2019


Ответы (2)


Если вы хотите получить возможность программно нарезать массив, ответом будут slice объекты, а не последовательности индексов. Например, :2 становится slice(None, 2):

np.all(arr[:, slice(None, 2), slice(None, 2)] == arr[:, :2, :2])

Срезы выбирают часть оси. Индексные массивы этого не делают: они выбирают отдельные элементы. Форма индекса определяет форму вывода в этом случае.

Если вы хотите выбрать произвольные индексы по нескольким измерениям, форма массивов индексов должна быть такой же формы, как и желаемый результат, или транслироваться на него:

arr[:, [[0], [2], [3]], [[0, 2, 3, 4]]]
person Mad Physicist    schedule 27.05.2019
comment
:2 -> slice(None, 2) не имеет отношения к вопросу. Что касается вещания одинаковой формы, поддерживает ли NumPy программный способ получения, например. с slice(None), (0, 2, 3), (0, 2, 3, 4) по slice(None), [[0], [2], [3]], [[0, 2, 3, 4]]]? np.ix_() делает половину дела, но не смешивается с slice(). - person norok2; 27.05.2019
comment
@норок. Не совсем безразлично. Надеюсь, я объяснил, почему я включил этот бит. - person Mad Physicist; 27.05.2019
comment
НО, похоже, это вообще не работает, см. обновленный вопрос. - person norok2; 28.05.2019

ix_ можно объединить со срезом с помощью конкатенации кортежей:

In [568]: arr[(slice(None),)+np.ix_((0,2,3),(0,2,3,4))]                                               
Out[568]: 
array([[[ 0,  2,  3,  4],
        [10, 12, 13, 14],
        [15, 17, 18, 19]],

       [[20, 22, 23, 24],
        [30, 32, 33, 34],
        [35, 37, 38, 39]],

       [[40, 42, 43, 44],
        [50, 52, 53, 54],
        [55, 57, 58, 59]]])

Кортеж ix_:

In [569]: np.ix_((0,2,3),(0,2,3,4))                                                                   
Out[569]: 
(array([[0],
        [2],
        [3]]), array([[0, 2, 3, 4]]))

объединение кортежей:

In [570]: (slice(None),)+np.ix_((0,2,3),(0,2,3,4))                                                    
Out[570]: 
(slice(None, None, None), array([[0],
        [2],
        [3]]), array([[0, 2, 3, 4]]))
In [571]: arr[_]                                                                                      
Out[571]: 
array([[[ 0,  2,  3,  4],
        [10, 12, 13, 14],
        [15, 17, 18, 19]],
        ....

Идея создания кортежа с помощью кода Python, а затем его использования в выражении индексации используется в ряде функций numpy.

Другой способ построить этот индексирующий кортеж:

In [581]: arr[(slice(None), *np.ix_((0,2,3),(0,2,3,4)))]                                              
Out[581]: 
array([[[ 0,  2,  3,  4],
        [10, 12, 13, 14],
        [15, 17, 18, 19]],
       ...

Это использует преимущество распаковки Python '*' внутри кортежа (но не непосредственно в выражении индексации).

По сути, это способ выполнения arr[:,*ix_[...]], который приводит к синтаксической ошибке.

В сумме:

  • numpy работает в Python, поэтому подчиняется всем его синтаксическим правилам.

  • numpy упрощает индексацию "элементов". Блочная индексация немного сложнее, хотя на самом деле она следует тем же правилам broadcasting.

  • MATLAB имеет собственный язык и синтаксис. Это упрощает индексацию блоков, но индексация элементов, например получение диагонали, более неудобна и требует дополнительного вызова функции sub2ind.

person hpaulj    schedule 27.05.2019
comment
Это работает для этого примера, но не в целом (см. обновленный вопрос). - person norok2; 28.05.2019