Индексирование
ndarrays можно индексировать, используя стандартный синтаксис Python x[obj] , где x — массив, а obj — выборка. Существуют три вида индексирования: доступ к полям, базовая нарезка, расширенное индексирование. Какой вид индексирования будет использован, зависит от obj.
Примечание
В Python x[(exp1, exp2, ..., expN)] эквивалентно x[exp1, exp2, ..., expN]; последнее просто синтаксический сахар для первого.
Базовая нарезка и индексирование
Базовая нарезка расширяет базовое понятие нарезки Python до N измерений. Базовая нарезка происходит, когда obj является объектом slice (созданным с помощью start:stop:step обозначения внутри скобок), целым числом или кортежем из объектов срезов и целых чисел. Ellipsis и newaxis объекты также могут быть вставлены в них.
Устаревшее начиная с версии 1.15.0: Для сохранения обратной совместимости с общим использованием в Numeric, базовая нарезка также инициируется, если объект выборки — любая последовательность, не являющаяся ndarray и кортежем (такая как list), содержащая slice объекты, объект Ellipsis или объект newaxis, но не для целочисленных массивов или других вложенных последовательностей.
Простейший случай индексирования с N целыми числами возвращает скаляр массива, представляющий соответствующий элемент. Как и в Python, все индексы нулевые: для i-го индекса , допустимый диапазон —
, где
— i-й элемент формы массива. Отрицательные индексы интерпретируются как счёт от конца массива (т.е., если
, это означает
).
Все массивы, сгенерированные базовой нарезкой, всегда являются видами исходного массива.
Стандартные правила нарезки последовательностей применяются к базовой нарезке в каждом измерении (включая использование индекса шага). Некоторые полезные понятия для запоминания:
-
Синтаксис базовой нарезки —
i:j:k, где i — начальный индекс, j — конечный индекс, а k — шаг (). Это выбирает m элементов (в соответствующем измерении) с индексными значениями i, i + k, …, i + (m - 1) k, где
а q и r — частное и остаток от деления j - i на k: j - i = q k + r, так что i + (m - 1) k < j.
Пример
>>> x = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) >>> x[1:7:2] array([1, 3, 5])
-
Отрицательные i и j интерпретируются как n + i и n + j, где n — количество элементов в соответствующем измерении. Отрицательный k означает движение к меньшим индексам.
Пример
>>> x[-2:10] array([8, 9]) >>> x[-3:3:-1] array([7, 6, 5, 4])
-
Предположим, что n — количество элементов в измерении, которое нарезается. Если i не задан, по умолчанию он равен 0 для k > 0 и n - 1 для k < 0 . Если j не задан, по умолчанию он равен n для k > 0 и -n-1 для k < 0 . Если k не задан, он по умолчанию равен 1. Обратите внимание, что
::эквивалентно:и означает выбор всех индексов вдоль этой оси.Пример
>>> x[5:] array([5, 6, 7, 8, 9])
-
Если количество объектов в кортеже выборки меньше N, то
:предполагается для всех последующих измерений.Пример
>>> x = np.array([[[1],[2],[3]], [[4],[5],[6]]]) >>> x.shape (2, 3, 1) >>> x[1:2] array([[[4], [5], [6]]]) -
Ellipsisрасширяются до необходимого количества:объектов для создания кортежа выбора той же длины, что иx.ndim. Может присутствовать только один эллипс.Пример
>>> x[...,0] array([[1, 2, 3], [4, 5, 6]]) -
Каждый объект
newaxisв кортеже выбора служит для расширения измерений результирующей выборки на одну единичную размерность. Добавляемое измерение — это позиция объектаnewaxisв кортеже выбора.Пример
>>> x[:,np.newaxis,:,:].shape (2, 1, 3, 1)
- Целое число i возвращает те же значения, что и
i:i+1за исключением того, что размерность возвращаемого объекта уменьшается на 1. В частности, кортеж выбора с p-м элементом целым числом (и все другие элементы:) возвращает соответствующий подмассив с размерностью N - 1. Если N = 1, то возвращаемый объект — скаляр массива. Эти объекты объяснены в Скаляры. - Если кортеж выбора содержит все элементы
:за исключением p-го элемента, который является объектом срезаi:j:k, то возвращаемый массив имеет размерность N, образованную конкатенацией подмассивов, возвращаемых индексированием целых чисел элементов i, i+k, …, i + (m - 1) k < j, -
Базовая нарезка с более чем одним ненулевым элементом в кортеже нарезки действует как повторяющееся применение нарезки с использованием одного ненулевого элемента, где ненулевые элементы последовательно используются (при этом все другие ненулевые элементы заменяются
:). Таким образом,x[ind1,...,ind2,:]действует какx[ind1][...,ind2,:]при базовой нарезке.Предупреждение
Вышесказанное не верно для расширенного индексирования.
- Вы можете использовать нарезку для установки значений в массиве, но (в отличие от списков) вы никогда не сможете увеличить массив. Размер значения, которое нужно установить в
x[obj] = value, должен быть (совместим с векторизацией) с той же формой, что иx[obj].
Примечание
Помните, что кортеж нарезки всегда может быть построен как obj и используется в x[obj] обозначении. Объекты срезов могут быть использованы в конструкции вместо [start:stop:step] обозначения. Например, x[1:10:5,::-1] также можно реализовать как obj = (slice(1,10,5), slice(None,None,-1)); x[obj] . Это может быть полезно для создания универсального кода, который работает с массивами произвольной размерности.
-
numpy.newaxis -
Объект
newaxisможет быть использован во всех операциях нарезки для создания оси длиной один.newaxisявляется псевдонимом для ‘None’, и ‘None’ может использоваться вместо него с тем же результатом.
Расширенное индексирование
Расширенное индексирование инициируется, когда объект выборки obj — это последовательность, не являющаяся кортежем, ndarray (типа данных целое число или булево), или кортеж с по крайней мере одним объектом последовательности или ndarray (типа данных целое число или булево). Существуют два типа расширенного индексирования: целочисленное и булево.
Расширенное индексирование всегда возвращает копию данных (в отличие от базовой нарезки, которая возвращает вид).
Предупреждение
Определение расширенного индексирования означает, что x[(1,2,3),] фундаментально отличается от x[(1,2,3)]. Последнее эквивалентно x[1,2,3] , что инициирует базовую выборку, в то время как первое инициирует расширенное индексирование. Убедитесь, что вы понимаете, почему это происходит.
Также обратите внимание, что x[[1,2,3]] инициирует расширенное индексирование, в то время как из-за устаревшей совместимости с Numeric x[[1,2,slice(None)]] инициирует базовую нарезку.
Индексирование целочисленным массивом
Индексирование целочисленным массивом позволяет выбирать произвольные элементы в массиве на основе их N-мерного индекса. Каждый целочисленный массив представляет собой ряд индексов в этом измерении.
Чистое индексирование целочисленным массивом
Когда индекс состоит из такого же количества целочисленных массивов, что и размерность индексируемого массива, индексирование происходит непосредственно, но отличается от нарезки.
Расширенные индексы всегда
result[i_1, ..., i_M] == x[ind_1[i_1, ..., i_M], ind_2[i_1, ..., i_M],
..., ind_N[i_1, ..., i_M]]
, и итерируются как один: result[i_1, ..., i_M] == x[ind_1[i_1, ..., i_M], ind_2[i_1, ..., i_M],
..., ind_N[i_1, ..., i_M]]
Обратите внимание, что форма результата идентична формам массива индексов (с учетом векторизации) ind_1, ..., ind_N.
Пример
Из каждой строки необходимо выбрать определённый элемент. Индекс строки — просто [0, 1, 2] , а индекс столбца указывает элемент для выбора в соответствующей строке, здесь [0, 1, 0]. Используя их вместе, задачу можно решить с помощью расширенного индексирования:
>>> x = np.array([[1, 2], [3, 4], [5, 6]]) >>> x[[0, 1, 2], [0, 1, 0]] array([1, 4, 5])
Для достижения поведения, аналогичного базовой нарезке выше, можно использовать векторизацию. Функция ix_ может помочь с этим. Это лучше всего понять на примере.
Пример
Из 4x3 массива необходимо выбрать угловые элементы, используя расширенное индексирование. Таким образом, необходимо выбрать все элементы, для которых столбец является одним из [0, 2] и строка — одним из [0, 3]. Чтобы использовать расширенное индексирование, необходимо явно выбрать все элементы. Используя описанный ранее метод, можно написать:
>>> x = array([[ 0, 1, 2],
... [ 3, 4, 5],
... [ 6, 7, 8],
... [ 9, 10, 11]])
>>> rows = np.array([[0, 0],
... [3, 3]], dtype=np.intp)
>>> columns = np.array([[0, 2],
... [0, 2]], dtype=np.intp)
>>> x[rows, columns]
array([[ 0, 2],
[ 9, 11]])
Однако, так как массивы индексов выше просто повторяют себя, векторизация (сравните операции, такие как rows[:, np.newaxis] + columns) может упростить это:
>>> rows = np.array([0, 3], dtype=np.intp)
>>> columns = np.array([0, 2], dtype=np.intp)
>>> rows[:, np.newaxis]
array([[0],
[3]])
>>> x[rows[:, np.newaxis], columns]
array([[ 0, 2],
[ 9, 11]])
Это вещание также можно выполнить, используя функцию ix_:
>>> x[np.ix_(rows, columns)]
array([[ 0, 2],
[ 9, 11]])
Обратите внимание, что без вызова np.ix_, будут выбраны только диагональные элементы, как это было в предыдущем примере. Это различие является самым важным моментом, который нужно запомнить при индексировании с помощью нескольких расширенных индексов.
Объединение расширенного и базового индексирования
Когда в индексе присутствует хотя бы один срез (:), эллипсис (...) или np.newaxis, или когда массив имеет больше измерений, чем расширенных индексов, поведение может быть более сложным. Это похоже на конкатенацию результатов индексирования для каждого элемента расширенного индекса
В самом простом случае присутствует только один расширенный индекс. Один расширенный индекс может, например, заменить срез, и результирующий массив будет таким же, однако он является копией и может иметь другую структуру памяти. Срез предпочтительнее, когда это возможно.
Пример
>>> x[1:2, 1:3] array([[4, 5]]) >>> x[1:2, [1, 2]] array([[4, 5]])
Самый простой способ понять ситуацию — это представить ее в терминах формы результата. Операция индексирования состоит из двух частей: подпространство, определяемое базовым индексированием (исключая целые числа), и подпространство от части с расширенным индексированием. Следует различать два случая объединения индексов:
- Расширенные индексы разделены срезом, эллипсисом или newaxis. Например,
x[arr1, :, arr2]. - Расширенные индексы расположены друг за другом. Например,
x[..., arr1, arr2, :], но неx[arr1, :, 1], так как1в этом отношении является расширенным индексом.
В первом случае измерения, полученные в результате операции расширенного индексирования, стоят первыми в результирующем массиве, а после этого — измерения подпространства. Во втором случае измерения от операций расширенного индексирования вставляются в результирующий массив в той же позиции, что и в исходном массиве (последняя логика делает простое расширенное индексирование похожим на срезы).
Пример
Предположим, x.shape имеет форму (10,20,30), а ind — это массив расширенного индексирования формы (2,3,4), тогда result = x[...,ind,:] имеет форму (10,2,3,4,30), потому что подпространство формы (20,) было заменено широковещательным подпространством индексирования формы (2,3,4). Если мы позволим переменным i, j, k перебирать подпространство формы (2,3,4), то result[...,i,j,k,:] = x[...,ind[i,j,k],:]. Этот пример дает тот же результат, что и x.take(ind, axis=-2).
Пример
Пусть 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.
Индексирование с помощью булевых массивов
Это расширенное индексирование происходит, когда obj — это массив типа bool, например, такой, который может быть возвращен операторами сравнения. Единственный булев массив-индекс практически идентичен x[obj.nonzero()], где, как описано выше, obj.nonzero() возвращает кортеж (длины obj.ndim) массивов целых индексов, показывающих True элементы obj. Однако это быстрее, когда obj.shape == x.shape.
Если obj.ndim == x.ndim, то x[obj] возвращает одномерный массив, заполненный элементами x, соответствующими True значениям obj. Порядок поиска будет строчно-главный, C-стиль. Если obj имеет True значения в позициях, выходящих за пределы x, то будет поднято исключение индекса. Если obj меньше x, это эквивалентно его заполнению False.
Пример
Распространённый случай использования этого — фильтрация для нужных значений элементов. Например, можно выбрать все элементы массива, которые не являются NaN:
>>> x = np.array([[1., 2.], [np.nan, 3.], [np.nan, np.nan]]) >>> x[~np.isnan(x)] array([ 1., 2., 3.])
Или добавить константу ко всем отрицательным элементам:
>>> x = np.array([1., -1., -2., 3]) >>> x[x < 0] += 20 >>> x array([ 1., 19., 18., 3.])
В общем случае, если индекс содержит булев массив, результат будет идентичен вставке obj.nonzero() в ту же позицию и использованию механизма индексирования целыми числами, описанного выше. x[ind_1, boolean_array, ind_2] эквивалентно x[(ind_1,) + boolean_array.nonzero() + (ind_2,)].
Если присутствует только один булев массив и нет массива целых индексов, это просто. Следует только позаботиться о том, чтобы булев индекс имел ровно столько измерений, сколько необходимо.
Пример
Из массива выберите все строки, сумма элементов которых меньше или равна двум:
>>> x = np.array([[0, 1], [1, 1], [2, 2]])
>>> rowsum = x.sum(-1)
>>> x[rowsum <= 2, :]
array([[0, 1],
[1, 1]])
Но если rowsum также будет иметь два измерения:
>>> rowsum = x.sum(-1, keepdims=True) >>> rowsum.shape (3, 1) >>> x[rowsum <= 2, :] # fails IndexError: too many indices >>> x[rowsum <= 2] array([0, 1])
Последнее даст только первые элементы из-за дополнительного измерения. Сравните rowsum.nonzero() для понимания этого примера.
Объединение нескольких булевых массивов-индексов или булевого с массивом целых индексов лучше всего понять с помощью аналогии с obj.nonzero(). Функция ix_ также поддерживает булевы массивы и будет работать без каких-либо неожиданностей.
Пример
Используйте булевое индексирование для выбора всех строк, сумма элементов которых является чётным числом. Одновременно столбцы 0 и 2 должны быть выбраны с помощью расширенного индекса целых чисел. Используя функцию ix_, это можно сделать так:
>>> x = array([[ 0, 1, 2],
... [ 3, 4, 5],
... [ 6, 7, 8],
... [ 9, 10, 11]])
>>> rows = (x.sum(-1) % 2) == 0
>>> rows
array([False, True, False, True])
>>> columns = [0, 2]
>>> x[np.ix_(rows, columns)]
array([[ 3, 5],
[ 9, 11]])
Без вызова np.ix_ или только диагональные элементы были бы выбраны.
Или без np.ix_ (сравните примеры с массивами целых чисел):
>>> rows = rows.nonzero()[0]
>>> x[rows[:, np.newaxis], columns]
array([[ 3, 5],
[ 9, 11]])
Подробные примечания
Вот некоторые подробные примечания, которые не имеют значения для повседневного индексирования (в произвольном порядке):
- Родной тип индексирования NumPy —
intpи может отличаться от значения по умолчанию для массивов целых чисел.intp— это минимальный тип данных, достаточный для безопасного индексирования любого массива; для расширенного индексирования он может быть быстрее, чем другие типы. - Для расширенных присваиваний, как правило, нет гарантии порядка итераций. Это означает, что если элемент устанавливается более одного раза, то конечный результат невозможно предсказать.
- Пустой (кортеж) индекс — это полный скалярный индекс в нульмерном массиве.
x[()]возвращает скаляр, еслиxимеет нулевую размерность, и представление в противном случае. С другой стороны,x[...]всегда возвращает представление. - Если в индексе присутствует нульмерный массив и это полный индекс целого числа, результатом будет скаляр, а не нульмерный массив. (Расширенное индексирование не активируется.)
- Когда присутствует эллипсис (
...) , но его размер равен нулю (т.е. он заменяет нуль:), результат по-прежнему всегда будет массивом. Представление, если нет расширенного индекса, в противном случае копия. - Эквивалентность
nonzeroдля булевых массивов не сохраняется для нульмерных булевых массивов. - Если в результате операции расширенного индексирования нет элементов, но отдельный индекс находится за пределами границ, поднимается ли исключение
IndexError(например,x[[], [123]]с123находящимся за пределами границ) не определено. - Если при присваивании возникает ошибка преобразования (например, при обновлении числового массива с использованием последовательности строк), массив, к которому производится присваивание, может оказаться в непредсказуемом частично обновлённом состоянии. Однако, если возникает любая другая ошибка (например, индекс за пределами границ), массив останется неизменным.
- Структура памяти результата расширенного индексирования оптимизирована для каждой операции индексирования, и какой-либо определённый порядок памяти нельзя предположить.
- При использовании подкласса (особенно подкласса, который управляет формой), стандартное поведение
ndarray.__setitem__будет вызывать__getitem__для базового индексирования, но не для расширенного индексирования. Для такого подкласса предпочтительнее вызыватьndarray.__setitem__с представлением ndarray базового класса на данных. Это обязательно нужно сделать, если подклассы__getitem__не возвращают представления.
Доступ к полям массивов
См. также
Если объект ndarray представляет собой массив со структурой, к полям массива можно получить доступ, индексируя массив строками, как в словаре.
Индексирование x['field-name'] возвращает новое представление массива, которое имеет такую же форму, что и x (за исключением случая, когда поле является подмассивом), но с типом данных x.dtype['field-name'] и содержит только часть данных в указанном поле. Также скаляры массивов со структурой данных могут быть «индексированы» таким образом.
Индексирование в структурированный массив также может выполняться с помощью списка имен полей, напр. x[['field-name1','field-name2']]. В настоящее время это возвращает новый массив, содержащий копию значений в указанных в списке полях. Начиная с NumPy 1.7, возврат копии устаревает в пользу возвращения представления. Копия по-прежнему будет возвращаться, но при записи в копию будет выведено предупреждение FutureWarning. Если вы полагаетесь на текущее поведение, то мы рекомендуем явно скопировать возвращаемый массив, т.е. используйте x[[‘имя_поля1’,’имя_поля2’]].copy(). Это будет работать как с прошлыми, так и с будущими версиями NumPy.
Если доступное поле является подмассивом, размерности подмассива добавляются к форме результата.
Пример
>>> x = np.zeros((2,2), dtype=[('a', np.int32), ('b', np.float64, (3,3))])
>>> x['a'].shape
(2, 2)
>>> x['a'].dtype
dtype('int32')
>>> x['b'].shape
(2, 2, 3, 3)
>>> x['b'].dtype
dtype('float64')
Индексация плоского итератора
x.flat возвращает итератор, который будет перебирать весь массив (в стиле C-contiguous, где последний индекс изменяется быстрее всего). Этот объект итератора также может быть индексирован с помощью базовых срезов или продвинутого индексирования, пока объект выбора не является кортежем. Это должно быть понятно из того факта, что x.flat является одномерным представлением. Его можно использовать для целочисленного индексирования с одномерными плоскими индексами в стиле C. Таким образом, форма любого возвращаемого массива — это форма объекта целочисленного индексирования.
© 2005–2019 NumPy Developers
Licensed under the 3-clause BSD License.
https://docs.scipy.org/doc/numpy-1.15.4/reference/arrays.indexing.html