Индексирование
Индексирование массивов относится к любому использованию квадратных скобок ([ ]) для индексирования значений массива. Существует множество вариантов индексирования, которые придают индексированию NumPy большую мощь, но с мощью приходит и сложность, а также потенциальная путаница. Этот раздел — лишь обзор различных вариантов и проблем, связанных с индексированием. Помимо индексирования отдельных элементов, подробности большинства этих вариантов можно найти в соответствующих разделах.
Присваивание против ссылок
Большинство следующих примеров демонстрируют использование индексирования при ссылке на данные в массиве. Примеры работают аналогично при присваивании массиву. См. раздел в конце для конкретных примеров и объяснений того, как работают присваивания.
Индексирование одного элемента
Индексирование одного элемента для одномерного массива соответствует ожиданиям. Оно работает точно так же, как и для других стандартных последовательностей Python. Оно нулево-индексированное и принимает отрицательные индексы для индексирования с конца массива.
>>> x = np.arange(10) >>> x[2] 2 >>> x[-2] 8
В отличие от списков и кортежей, массивы NumPy поддерживают многомерное индексирование для многомерных массивов. Это означает, что нет необходимости разделять индекс каждой размерности в собственные квадратные скобки.
>>> x.shape = (2,5) # now x is 2-dimensional >>> x[1,3] 8 >>> x[1,-1] 9
Обратите внимание, что если вы индексируете многомерный массив меньшим количеством индексов, чем измерений, вы получаете подмассив. Например:
>>> x[0] array([0, 1, 2, 3, 4])
То есть каждый указанный индекс выбирает массив, соответствующий остальным выбранным измерениям. В приведенном выше примере выбор 0 означает, что оставшееся измерение длиной 5 не указано, и возвращаемый массив имеет эту размерность и размер. Следует отметить, что возвращаемый массив не является копией исходного, а указывает на те же значения в памяти, что и исходный массив. В этом случае возвращается одномерный массив на первой позиции (0). Поэтому использование одного индекса в возвращенном массиве приводит к возврату одного элемента. То есть:
>>> x[0][2] 2
Таким образом, обратите внимание, что x[0,2] = x[0][2] хотя во втором случае это менее эффективно, так как после первого индекса создается новый временный массив, который затем индексируется по 2.
Примечание для тех, кто привык к порядку памяти IDL или Fortran, относящемуся к индексированию. NumPy использует индексирование в порядке C. Это означает, что последний индекс обычно представляет наиболее быстро меняющееся местоположение в памяти, в отличие от Fortran или IDL, где первый индекс представляет наиболее быстро меняющееся местоположение в памяти. Эта разница представляет собой большую потенциальную путаницу.
Другие варианты индексирования
Можно срезать и изменять шаг массивов, чтобы извлечь массивы с тем же количеством измерений, но с разными размерами, чем исходный. Срезы и изменение шага работают точно так же, как и для списков и кортежей, за исключением того, что они могут быть применены и к нескольким измерениям. Несколько примеров лучше всего это иллюстрируют:
>>> x = np.arange(10)
>>> x[2:5]
array([2, 3, 4])
>>> x[:-7]
array([0, 1, 2])
>>> x[1:7:2]
array([1, 3, 5])
>>> y = np.arange(35).reshape(5,7)
>>> y[1:5:2,::3]
array([[ 7, 10, 13],
[21, 24, 27]])
Обратите внимание, что срезы массивов не копируют внутренние данные массива, а только создают новые представления исходных данных. Это отличается от срезов списков или кортежей, и явное copy() рекомендуется, если исходные данные больше не требуются.
Можно индексировать массивы другими массивами для выбора списков значений из массивов в новые массивы. Существует два разных способа достижения этого. Один использует один или несколько массивов значений индекса. Другой включает предоставление булевого массива соответствующей формы для указания выбираемых значений. Массивы индексов являются очень мощным инструментом, позволяющим избежать циклов по отдельным элементам в массивах и тем самым значительно повысить производительность.
Можно использовать специальные возможности, чтобы эффективно увеличить количество измерений в массиве через индексирование, так что результирующий массив приобретает форму, необходимую для использования в выражении или со специфической функцией.
Массивы индексов
Массивы NumPy можно индексировать другими массивами (или любым другим объектом-последовательностью, который может быть преобразован в массив, таким как списки, за исключением кортежей; см. конец этого документа, чтобы понять, почему это так). Использование массивов индексов варьируется от простых, понятных случаев до сложных, труднопонятных. Во всех случаях с массивами индексов возвращается копия исходных данных, а не представление, как это происходит при срезах.
Массивы индексов должны быть целочисленного типа. Каждое значение в массиве указывает, какое значение в массиве использовать вместо индекса. Для иллюстрации:
>>> x = np.arange(10,1,-1) >>> x array([10, 9, 8, 7, 6, 5, 4, 3, 2]) >>> x[np.array([3, 3, 1, 8])] array([7, 7, 9, 2])
Массив индексов, состоящий из значений 3, 3, 1 и 8, соответственно, создает массив длиной 4 (такой же, как и массив индексов), где каждый индекс заменяется значением, которое массив индексов имеет в индексируемом массиве.
Разрешены отрицательные значения, и они работают так же, как и с одиночными индексами или срезами:
>>> x[np.array([3,3,-3,8])] array([7, 7, 4, 2])
Ошибка возникает при использовании значений индекса за пределами границ:
>>> x[np.array([3, 3, 20, 8])] <type 'exceptions.IndexError'>: index 20 out of bounds 0<=index<9
Как правило, при использовании массивов индексов возвращается массив с такой же формой, как и массив индексов, но с типом и значениями индексируемого массива. В качестве примера мы можем использовать многомерный массив индексов вместо этого:
>>> x[np.array([[1,1],[2,3]])]
array([[9, 9],
[8, 7]])
Индексирование многомерных массивов
Дело усложняется при индексировании многомерных массивов, особенно с многомерными массивами индексов. Они, как правило, более необычные варианты использования, но они допустимы и полезны для некоторых задач. Начнем с самого простого многомерного случая (используя массив y из предыдущих примеров):
>>> y[np.array([0,2,4]), np.array([0,1,2])] array([ 0, 15, 30])
В этом случае, если массивы индексов имеют соответствующую форму, и есть массив индексов для каждой размерности индексируемого массива, результирующий массив имеет такую же форму, что и массивы индексов, а значения соответствуют набору индексов для каждой позиции в массивах индексов. В этом примере первое значение индекса для обоих массивов индексов равно 0, и поэтому первое значение результирующего массива — y[0,0]. Следующее значение — y[2,1], а последнее — y[4,2].
Если массивы индексов не имеют одинаковой формы, происходит попытка их трансляции в одинаковую форму. Если их нельзя транслировать в одинаковую форму, генерируется исключение:
>>> y[np.array([0,2,4]), np.array([0,1])] <type 'exceptions.ValueError'>: shape mismatch: objects cannot be broadcast to a single shape
Механизм трансляции позволяет комбинировать массивы индексов со скалярами для других индексов. Эффект состоит в том, что скалярное значение используется для всех соответствующих значений массивов индексов:
>>> y[np.array([0,2,4]), 1] array([ 1, 15, 29])
Переходя к следующему уровню сложности, можно частично индексировать массив с помощью массивов индексов. Требуется немного подумать, чтобы понять, что происходит в таких случаях. Например, если мы просто используем один массив индексов с y:
>>> y[np.array([0,2,4])]
array([[ 0, 1, 2, 3, 4, 5, 6],
[14, 15, 16, 17, 18, 19, 20],
[28, 29, 30, 31, 32, 33, 34]])
Результатом является создание нового массива, где каждое значение массива индексов выбирает одну строку из индексируемого массива, и результирующий массив имеет результирующую форму (количество элементов индекса, размер строки).
Пример, где это может быть полезно, — это цветовая справочная таблица, где нужно сопоставить значения изображения с тройками RGB для отображения. Справочная таблица может иметь форму (nlookup, 3). Индексирование такого массива изображением с формой (ny, nx) с типом данных=np.uint8 (или любым целочисленным типом, пока значения находятся в пределах справочной таблицы) приведет к массиву формы (ny, nx, 3), где тройка значений RGB ассоциирована с каждой позицией пикселя.
В общем случае форма результирующего массива будет представлять собой конкатенацию формы массива индексов (или формы, в которую все массивы индексов были транслированы) с формой любых неиспользованных измерений (не индексированных) в индексируемом массиве.
Булевы или «маска» массивы индексов
Булевы массивы, используемые в качестве индексов, обрабатываются совершенно иначе, чем массивы индексов. Булевы массивы должны иметь ту же форму, что и начальные измерения индексируемого массива. В самом простом случае булев массив имеет такую же форму:
>>> b = y>20 >>> y[b] array([21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34])
В отличие от случая массивов целочисленных индексов, в булевом случае результатом является одномерный массив, содержащий все элементы индексируемого массива, соответствующие всем истинным элементам булева массива. Элементы индексируемого массива всегда итерируются и возвращаются в порядке строчно-смежный (стиль C). Результат также идентичен y[np.nonzero(b)]. Как и с массивами индексов, возвращается копия данных, а не представление, как при срезах.
Результат будет многомерным, если y имеет больше измерений, чем b. Например:
>>> b[:,5] # use a 1-D boolean whose first dim agrees with the first dim of y
array([False, False, False, True, True])
>>> y[b[:,5]]
array([[21, 22, 23, 24, 25, 26, 27],
[28, 29, 30, 31, 32, 33, 34]])
Здесь выбираются 4-я и 5-я строки из индексируемого массива и объединяются в двумерный массив.
В общем случае, когда булев массив имеет меньше измерений, чем индексируемый массив, это эквивалентно y[b, …], что означает, что y индексируется по b, за которым следуют столько двоеточий, сколько необходимо для заполнения ранга y. Таким образом, форма результата — одно измерение, содержащее количество истинных элементов булева массива, за которым следуют оставшиеся измерения индексируемого массива.
Например, использование двумерного булева массива формы (2,3) с четырьмя истинными элементами для выбора строк из трехмерного массива формы (2,3,5) приводит к двумерному результату формы (4,5):
>>> x = np.arange(30).reshape(2,3,5)
>>> x
array([[[ 0, 1, 2, 3, 4],
[ 5, 6, 7, 8, 9],
[10, 11, 12, 13, 14]],
[[15, 16, 17, 18, 19],
[20, 21, 22, 23, 24],
[25, 26, 27, 28, 29]]])
>>> b = np.array([[True, True, False], [False, True, True]])
>>> x[b]
array([[ 0, 1, 2, 3, 4],
[ 5, 6, 7, 8, 9],
[20, 21, 22, 23, 24],
[25, 26, 27, 28, 29]])
Для получения более подробной информации обратитесь к документации NumPy по индексированию массивов.
Комбинирование массивов индексов со срезами
Массивы индексов можно комбинировать со срезами. Например:
>>> y[np.array([0, 2, 4]), 1:3]
array([[ 1, 2],
[15, 16],
[29, 30]])
По сути, операция среза и массива индексов независимы. Операция среза извлекает столбцы с индексом 1 и 2 (т.е. второй и третий столбцы), за которым следует операция массива индексов, которая извлекает строки с индексом 0, 2 и 4 (т.е. первую, третью и пятую строки).
Это эквивалентно:
>>> y[:, 1:3][np.array([0, 2, 4]), :]
array([[ 1, 2],
[15, 16],
[29, 30]])
Аналогично, срезы можно комбинировать с транслированными булевыми индексами:
>>> b = y > 20
>>> b
array([[False, False, False, False, False, False, False],
[False, False, False, False, False, False, False],
[False, False, False, False, False, False, False],
[ True, True, True, True, True, True, True],
[ True, True, True, True, True, True, True]])
>>> y[b[:,5],1:3]
array([[22, 23],
[29, 30]])
Инструменты структурного индексирования
Для облегчения соответствия форм массивов с выражениями и при присваивании можно использовать объект np.newaxis внутри индексов массива для добавления новых измерений размером 1. Например:
>>> y.shape (5, 7) >>> y[:,np.newaxis,:].shape (5, 1, 7)
Обратите внимание, что в массиве нет новых элементов, просто увеличивается размерность. Это может быть полезно для объединения двух массивов таким образом, который в противном случае потребовал бы явных операций изменения формы. Например:
>>> x = np.arange(5)
>>> x[:,np.newaxis] + x[np.newaxis,:]
array([[0, 1, 2, 3, 4],
[1, 2, 3, 4, 5],
[2, 3, 4, 5, 6],
[3, 4, 5, 6, 7],
[4, 5, 6, 7, 8]])
Синтаксис многоточия может использоваться для указания полного выбора по всем оставшимся не указанным измерениям. Например:
>>> z = np.arange(81).reshape(3,3,3,3)
>>> z[1,...,2]
array([[29, 32, 35],
[38, 41, 44],
[47, 50, 53]])
Это эквивалентно:
>>> z[1,:,:,2]
array([[29, 32, 35],
[38, 41, 44],
[47, 50, 53]])
Присваивание значений индексированным массивам
Как уже упоминалось, можно выбрать подмножество массива для присваивания с помощью одиночного индекса, срезов, массивов индексов и масок. Присваиваемое значение должно иметь согласованную форму (ту же форму или транслируемую в форму, которую создает индекс). Например, разрешено присваивать константу срезу:
>>> x = np.arange(10) >>> x[2:7] = 1
или массив нужного размера:
>>> x[2:7] = np.arange(5)
Обратите внимание, что присваивания могут привести к изменениям, если к типам меньшего порядка (например, от float к int) присваиваются типы большего порядка, или даже к исключениям (присваивание complex к float или int):
>>> x[1] = 1.2 >>> x[1] 1 >>> x[1] = 1.2j TypeError: can't convert complex to int
В отличие от некоторых ссылок (таких как индексы массива и маски), присваивания всегда выполняются для исходных данных в массиве (поскольку ничего другого не имеет смысла!). Однако следует учесть, что некоторые действия могут не работать так, как можно наивно ожидать. Этот конкретный пример часто удивляет людей:
>>> x = np.arange(0, 50, 10) >>> x array([ 0, 10, 20, 30, 40]) >>> x[np.array([1, 1, 3, 1])] += 1 >>> x array([ 0, 11, 20, 31, 40])
Где люди ожидают, что первая позиция будет увеличена на 3. На самом деле, она будет увеличена только на 1. Причина в том, что из исходного массива извлекается новый массив (как временный), содержащий значения 1, 1, 3, 1, затем к временному значению добавляется 1, а затем временный массив присваивается обратно исходному массиву. Таким образом, значение массива в x[1]+1 присваивается x[1] трижды, а не увеличивается трижды.
Обработка переменного числа индексов в программах
Синтаксис индексов очень мощный, но ограничен при работе с переменным числом индексов. Например, если вы хотите написать функцию, которая может обрабатывать аргументы с различным числом измерений, не прибегая к написанию специального кода для каждого возможного числа измерений, как это можно сделать? Если в индекс передается кортеж, кортеж будет интерпретироваться как список индексов. Например (используя предыдущее определение массива z):
>>> indices = (1,1,1,1) >>> z[indices] 40
Таким образом, можно использовать код для построения кортежей любого числа индексов и затем использовать их в индексе.
Срезы можно задавать в программах с помощью функции slice() в Python. Например:
>>> indices = (1,1,1,slice(0,2)) # same as [1,1,1,0:2] >>> z[indices] array([39, 40])
Аналогично, эллипсы можно задавать с помощью кода, используя объект Ellipsis:
>>> indices = (1, Ellipsis, 1) # same as [1,...,1]
>>> z[indices]
array([[28, 31, 34],
[37, 40, 43],
[46, 49, 52]])
По этой причине можно использовать вывод функции np.nonzero() непосредственно в качестве индекса, так как она всегда возвращает кортеж массивов индексов.
Из-за специального обращения с кортежами, они не преобразуются в массив автоматически, как это было бы с списком. Например:
>>> z[[1,1,1,1]] # produces a large array
array([[[[27, 28, 29],
[30, 31, 32], ...
>>> z[(1,1,1,1)] # returns a single value
40
© 2005–2021 NumPy Developers
Licensed under the 3-clause BSD License.
https://numpy.org/doc/1.20/user/basics.indexing.html