Странная индексация с использованием numpy

27

У меня есть переменная х, которая имеет форму (2,2,50,100).

У меня также есть массив y, равный np.array ([0,10,20]). Странная вещь случается, когда я индексирую x [0,:,:, y].

x = np.full((2,2,50,100),np.nan)
y = np.array([0,10,20])
print(x.shape)
(2,2,50,100)
print(x[:,:,:,y].shape)
(2,2,50,3)
print(x[0,:,:,:].shape)
(2,50,100)
print(x[0,:,:,y].shape)
(3,2,50)

Почему последний выводит (3,2,50), а не (2,50,3)?

Пол Скотти
источник
Я новичок в numpy, поэтому у меня нет ответа на ваш вопрос. Чтобы исследовать это далее, я предлагаю найти меньший пример, который является только 2D или 3D и похож на самое большее 10 элементов на любой оси.
Код-ученик

Ответы:

21

Вот как NumPy использует расширенную индексацию для трансляции форм массива. Когда вы передаете a 0для первого индекса и yдля последнего индекса, numpy будет транслировать, 0чтобы иметь ту же форму, что и y. Эквивалентность имеет место x[0,:,:,y] == x[(0, 0, 0),:,:,y]. вот пример

import numpy as np

x = np.arange(120).reshape(2,3,4,5)
y = np.array([0,2,4])

np.equal(x[0,:,:,y], x[(0, 0, 0),:,:,y]).all()
# returns:
True

Теперь, поскольку вы фактически передаете два набора индексов, вы используете API расширенного индексирования для формирования (в данном случае) пар индексов.

x[(0, 0, 0),:,:,y])

# equivalent to
[
  x[0,:,:,y[0]], 
  x[0,:,:,y[1]], 
  x[0,:,:,y[2]]
]

# equivalent to
rows = np.array([0, 0, 0])
cols = y
x[rows,:,:,cols]

# equivalent to
[
  x[r,:,:,c] for r, c in zip(rows, columns)
]

Который имеет первое измерение, которое равно длине y. Это то, что вы видите.

В качестве примера рассмотрим массив с 4 измерениями, которые описаны в следующем фрагменте:

x = np.arange(120).reshape(2,3,4,5)
y = np.array([0,2,4])

# x looks like:
array([[[[  0,   1,   2,   3,   4],    -+      =+
         [  5,   6,   7,   8,   9],     Sheet1  |
         [ 10,  11,  12,  13,  14],     |       |
         [ 15,  16,  17,  18,  19]],   -+       |
                                                Workbook1
        [[ 20,  21,  22,  23,  24],    -+       |
         [ 25,  26,  27,  28,  29],     Sheet2  |
         [ 30,  31,  32,  33,  34],     |       |
         [ 35,  36,  37,  38,  39]],   -+       |
                                                |
        [[ 40,  41,  42,  43,  44],    -+       |
         [ 45,  46,  47,  48,  49],     Sheet3  |
         [ 50,  51,  52,  53,  54],     |       |
         [ 55,  56,  57,  58,  59]]],  -+      =+


       [[[ 60,  61,  62,  63,  64],
         [ 65,  66,  67,  68,  69],
         [ 70,  71,  72,  73,  74],
         [ 75,  76,  77,  78,  79]],

        [[ 80,  81,  82,  83,  84],
         [ 85,  86,  87,  88,  89],
         [ 90,  91,  92,  93,  94],
         [ 95,  96,  97,  98,  99]],

        [[100, 101, 102, 103, 104],
         [105, 106, 107, 108, 109],
         [110, 111, 112, 113, 114],
         [115, 116, 117, 118, 119]]]])

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

Первое измерение похоже на наличие двух книг Excel, второе измерение похоже на наличие трех листов в каждой книге, третье измерение похоже на наличие 4 строк на листе, а последнее измерение составляет 5 значений для каждой строки (или столбцов на листе).

Глядя на это так, спрашивая x[0,:,:,0], можно сказать: «в первой книге, для каждого листа, для каждой строки, дайте мне первое значение / столбец».

x[0,:,:,y[0]]
# returns:
array([[ 0,  5, 10, 15],
       [20, 25, 30, 35],
       [40, 45, 50, 55]])

# this is in the same as the first element in:
x[(0,0,0),:,:,y]

Но теперь с расширенным индексированием мы можем думать x[(0,0,0),:,:,y]как «в первой книге, для каждого листа, для каждой строки, дайте мне yзначение th / столбец. Хорошо, теперь сделайте это для каждого значения y»

x[(0,0,0),:,:,y]
# returns:
array([[[ 0,  5, 10, 15],
        [20, 25, 30, 35],
        [40, 45, 50, 55]],

       [[ 2,  7, 12, 17],
        [22, 27, 32, 37],
        [42, 47, 52, 57]],

       [[ 4,  9, 14, 19],
        [24, 29, 34, 39],
        [44, 49, 54, 59]]])

Где он сходит с ума, так это то, что numpy будет транслироваться, чтобы соответствовать внешним измерениям массива индекса. Поэтому, если вы хотите выполнить ту же операцию, что и выше, но для ОБА «книг Excel», вам не нужно зацикливаться и объединять. Вы можете просто передать массив в первое измерение, но он ДОЛЖЕН иметь совместимую форму.

Передача целого числа транслируется на y.shape == (3,). Если вы хотите передать массив в качестве первого индекса, только последнее измерение массива должно быть совместимо с y.shape. Т.е. последнее измерение первого индекса должно быть 3 или 1.

ix = np.array([[0], [1]])
x[ix,:,:,y].shape
# each row of ix is broadcast to length 3:
(2, 3, 3, 4)

ix = np.array([[0,0,0], [1,1,1]])
x[ix,:,:,y].shape
# this is identical to above:
(2, 3, 3, 4)

ix = np.array([[0], [1], [0], [1], [0]])
x[ix,:,:,y].shape
# ix is broadcast so each row of ix has 3 columns, the length of y
(5, 3, 3, 4)

В документах нашел краткое объяснение: https://docs.scipy.org/doc/numpy/reference/arrays.indexing.html#combining-advanced-and-basic-indexing.


Редактировать:

Исходя из первоначального вопроса, чтобы получить одну строку нужного сублицензирования, вы можете использовать x[0][:,:,y]:

x[0][:,:,y].shape
# returns
(2, 50, 3)

Однако, если вы пытаетесь назначить этим сублимам, вы должны быть очень осторожны, когда смотрите на общий вид памяти исходного массива. В противном случае присваивание будет не исходному массиву, а копии.

Общая память возникает только тогда, когда вы используете целое число или фрагмент для подмножества вашего массива, то есть x[:,0:3,:,:]или x[0,:,:,1:-1].

np.shares_memory(x, x[0])
# returns:
True

np.shares_memory(x, x[:,:,:,y])
# returns:
False

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

НО! Поскольку ваш массив для yможет быть выражен в виде среза, вы МОЖЕТЕ получить назначаемое представление вашего массива через:

x[0,:,:,0:21:10].shape
# returns:
(2, 50, 3)

np.shares_memory(x, x[0,:,:,0:21:10])
# returns:
True

# actually assigns to the original array
x[0,:,:,0:21:10] = 100

Здесь мы используем фрагмент, 0:21:10чтобы получить каждый индекс, который будет в range(0,21,10). Мы должны использовать, 21а не 20потому, что точка останова исключена из среза, как в rangeфункции.

Таким образом, в принципе, если вы можете создать срез, который соответствует вашим критериям сублицензирования, вы можете сделать назначение.

Джеймс
источник
4

Это называется combining advanced and basic indexing. In combining advanced and basic indexing, numpy, сначала выполните индексацию в расширенной индексации, а подпространство / объедините результат с измерением базовой индексации.

Пример из документов:

Пусть x.shape будет (10,20,30,40,50) и предположим, что ind_1 и ind_2 можно транслировать в форму (2,3,4). Тогда x [:, ind_1, ind_2] имеет форму (10,2,3,4,40,50), потому что (20,30) -образное подпространство из X было заменено на (2,3,4) подпространство из индексы. Тем не менее, x [:, ind_1,:, ind_2] имеет форму (2,3,4,10,30,50), потому что нет однозначного места , куда можно попасть в подпространство индексирования, поэтому оно привязано к началу . Всегда можно использовать .transpose () для перемещения подпространства в любое место. Обратите внимание, что этот пример не может быть реплицирован с использованием take.

так, на x[0,:,:,y], 0и yпредварительны индексации. Они передаются вместе, чтобы дать измерение (3,).

In [239]: np.broadcast(0,y).shape
Out[239]: (3,)

Это (3,)привязывает к началу 2-го и 3-го измерения, чтобы сделать(3, 2, 50)

Чтобы увидеть, что 1-е и последнее измерение действительно передаются вместе, вы можете попробовать изменить 0их, [0,1]чтобы увидеть ошибку вещания

print(x[[0,1],:,:,y])

Output:
IndexError                                Traceback (most recent call last)
<ipython-input-232-5d10156346f5> in <module>
----> 1 x[[0,1],:,:,y]

IndexError: shape mismatch: indexing arrays could not be broadcast together with
 shapes (2,) (3,)
Энди Л.
источник