Расширенное индексирование массивов NumPy, стало проще
Понять, что означает [:: 2, [0,3,4],…, 2: 5]
Одним из самых больших преимуществ NumPy является его чрезвычайно быстрая индексация, но она может очень быстро усложняться. Например, учитывая случайно сгенерированный массив целых чисел формы 5 × 6 × 3 × 5, что будет выполнять следующая операция и какой формы будет полученный срез?
array[np.round(array)==array].reshape((5,6,3,5))[::2, [0,3,4], ..., 2:5]
Конечно, эта операция во многих отношениях произвольна, но нередко используются сложные операции с многомерными данными. К концу этой статьи вы сможете разбить и написать свои собственные сложные манипуляции с индексированием массива NumPy.
Одномерные массивы
Рассмотрим конструкцию массива array = np.array([1, 2, 3, 4, 5])
, которая создает массив чисел от 1 до 5.
Чтобы получить доступ к первому элементу, мы вызываем array[0]
. Аналогичным образом, чтобы получить доступ ко второму элементу, мы вызываем array[1]
.
Для доступа к последнему элементу мы используем отрицательные индексы (array[-1]
). Аналогично, ко второму последнему элементу можно получить доступ с помощью array[-2]
.
Чтобы получить доступ к диапазону элементов, укажите начальный индекс и конечный индекс. Результат будет включать все элементы до индекса остановки минус один (подумайте об этом как о том, что 7
не включен в range(7)
). Например, захват первых трех элементов array
может быть выполнен с помощью array[0:3]
, хотя он может быть переписан как array[:3]
, который игнорирует 0, но возвращает тот же результат.
Диапазон элементов также поддерживает назначение. Например, array[2:5] = np.arange(3)
сделает третий, четвертый и пятый элементы (второй, третий и четвертый индексы) равными 0, 1 и 2 соответственно.
С другой стороны, чтобы получить доступ к нескольким значениям в настраиваемых индексах, передайте список или массив в скобки индексации. Например, array[[1, 3]]
возвращает array([2, 4])
, потому что он запрашивает второе и четвертое значения массива. Использование индексированных массивов для индексации других массивов дает большую свободу и часто является хорошим способом достижения идеальной индексации, если функции или санкционированные методы не работают. Индексы в этих списках тоже могут быть отрицательными!
Чтобы получить доступ к диапазону элементов с размером шага, необходимо указать начальный индекс, конечный индекс и размер шага. Например, array[:4:2]
возвращает [1, 3]
. Во-первых, array[:4]
возвращает первые четыре элемента, равные [1, 2, 3, 4]
. Начиная с первого элемента и меняя размер шага, получается массив [1, 3]
.
Использование array[:]
- один из самых быстрых и эффективных способов копирования массива.
Индексация массивов может показаться недоступной из-за сокращенной записи, используемой для избежания ввода нулей или концов: например, array[::2]
возвращает [1, 3, 5]
. Три основных параметра индексации - начальный индекс, конечный индекс и размер шага - указываются их положением относительно двоеточия (:
). Когда набирается [::2]
, это означает, что мы намеренно пренебрегаем начальным и конечным индексами и хотим предоставить информацию только о желаемом размере шага. Как следствие, NumPy вернет весь массив с указанным размером шага.
Массивы NumPy также поддерживают условное индексирование. Рассмотрим десятиэлементный массив случайно сгенерированных целых чисел от -5 до 5 включительно:
rand = np.random.randint(low=-5, high=5, size=10) array([-2, -1, 2, -2, 4, 3, -1, -5, -2, 2])
Чтобы найти все положительные значения, мы можем вызвать rand[rand>0]
, который возвращает array([2, 4, 3, 2])
. Это работает, потому что rand>0
или любое другое аналогичным образом написанное условие называется логической маской, значение которой равно array([False, False, True, False, True, True, False, False, False,
. NumPy просто возвращает значения, соответствующие значения маски которых равны
True])True
. При использовании этой логики, пока условие возвращает допустимую логическую маску, допустима такая операция, как rand[rand**3<=2*rand]
.
Условные операторы работают с массивами всех измерений.
Двумерные массивы
Рассмотрим следующий двумерный массив:
array = np.array([[1, 2, 3, 4, 5], [6, 7, 8, 9, 10], [11, 12, 13, 14, 15]])
Использование одномерного индексирования двумерных данных требует сдвига в представлении о данных не как о табличных, а как о списках. С этой точки зрения array
- это массив из трех элементов, где каждый элемент представляет собой другой массив из пяти элементов.
Следовательно, array[:2]
, который извлекает первые два элемента, должен вернуть:
array([[ 1, 2, 3, 4, 5], [ 6, 7, 8, 9, 10]])
Проверьте себя - что должно array[:2:2]
вернуть?
Поскольку array
- это просто массив, элементы которого являются массивами, мы должны рассматривать его как таковой. [:2:2]
выделяет первые два элемента (нулевой начальный индекс и два конечного индекса) с размером шага два. Поскольку начальная индексация диапазона ([:2]
) возвращает только два значения, а размер шага равен двум, результатом будет только первый элемент. В нашем двумерном массиве это другой массив:
array([[1, 2, 3, 4, 5]])
Использование методов одномерного индексирования для двумерных массивов очень ограничено. По этой причине двумерное индексирование принимает форму array[a:b:c, d:e:f]
. a:b:c
и d:e:f
представляют тройные значения для начального индекса, конечного индекса и индекса шага. Однако a:b:c
в левой части запятой применяет преобразования по строкам, а d:e:f
применяет их «по столбцам» или к следующему измерению.
Рассмотрим, например, следующую команду: array[:2,3:]
. Полезно разбить эту индексацию на несколько последовательных частей.
1 | [:2]
принимает первые два элемента array
. Поскольку array
двумерный, это первые две строки:
array([[ 1, 2, 3, 4, 5],
[ 6, 7, 8, 9, 10]])
2 | [3:]
указывает начальный индекс (3), но не конечный индекс, что означает, что в массиве из пяти элементов он начнется с четвертого элемента (индекс 3) и продолжится до конца массива. Поскольку он применяется по столбцам, результат будет следующим:
array([[ 4, 5], [ 9, 10]])
Как и при одномерном индексировании, двумерные массивы также можно индексировать с помощью списков для доступа к значениям настраиваемого индекса. Однако двумерные массивы также должны индексироваться двумерными списками или массивами.
Индексирование многомерных массивов
Многомерные массивы работают аналогично двум массивам в более низких измерениях. Рассмотрим трехмерный массив с именем array
и формой (3, 2, 3):
array([[[ 0, 1, 2], [ 3, 4, 5]], [[ 6, 7, 8], [ 9, 10, 11]], [[12, 13, 14], [15, 16, 17]]])
Как и двухмерные массивы, измерения можно индексировать с помощью трехзначных групп, с отдельной индексацией для измерений, разделенных запятой. Например, array[:3:2,:,1:3]
выполняет следующие операции:
1 | [:3:2]
означает «проиндексировать все элементы до третьего индекса (четвертого элемента) с размером шага два». В списке массивов с тремя элементами это означает, что первый и третий элементы сохраняются.
array([[[ 0, 1, 2], [ 3, 4, 5]], [[12, 13, 14], [15, 16, 17]]])
2 | [:]
означает «ничего не делать». Поскольку информация не предоставляется, NumPy не изменяет массив, но он должен быть там, чтобы указать, что со вторым измерением ничего не должно произойти.
3 | [1:3]
означает «индексировать все элементы, начиная с первого индекса (второй элемент) и заканчивая третьим индексом (четвертый элемент). Этот разрез применяется к третьему и последнему измерению.
array([[[ 1, 2], [ 4, 5]], [[13, 14], [16, 17]]])
Эллипсы (…
) могут использоваться для обозначения нескольких двоеточий и запятых, когда количество измерений велико. Рассмотрим четырехмерный массив z
:
z = np.arange(81).reshape(3,3,3,3)
В этом случае z[1, :, :, 0]
то же самое, что писать z[1, …, 0]
. Кроме того, мы могли бы записать z[:, :, :, 0] as z[…, 0]
и z[1, :, 2, 0]
как z[1, …, 2, 0]
(хотя в этом конкретном случае это не обязательно). Для каждого индекса поддерживается только одно многоточие.
При использовании множества элементов для индексации массива важно понимать разницу между списками и кортежами. Рассмотрим массив 3 на 3 z
:
array([[0, 1, 2], [3, 4, 5], [6, 7, 8]])
Команда z[[0,0]]
остается в первом построчном измерении, потому что в основных квадратных скобках нет запятых. Результат индексации [0, 0]
состоит в том, что первая строка (индекс 0) повторяется дважды:
array([[0, 1, 2], [0, 1, 2]])
С другой стороны, набор z[(0, 1)]
- это еще один метод набора z[0, 1]
, поскольку он вернет значение в первой строке (индекс 0) и во втором столбце (индекс 1). Перефразированные кортежи могут использоваться для пересечения измерений, но, поскольку их присутствие относительно произвольно, рекомендуется вообще не писать кросс-размерную индексацию в круглых скобках.
Советы по интерпретации и написанию сложной индексации
Индексирование NumPy может очень быстро стать очень сложным с большими размерами, так как его подход к индексации может непредсказуемо меняться и приводить к странным результатам.
- Запишите, какую часть каждого измерения вы хотите проиндексировать на английском языке, а затем переведите это измерение за измерением (разделяя их запятыми).
- Как правило, старайтесь согласовывать типы данных, которые вы используете для индексации в одной команде. Например, использование обозначений
a:b
и[a, b, c, d]
, если это не является абсолютно необходимым, может привести к непредсказуемости результата. - Не сжимайте все в одну команду. Или попробуйте проиндексировать размер за измерением, используя то же пошаговое сокращение данных, а затем перепишите цепочку команд как одну команду, как только убедитесь, что она работает.
- Разбейте все на
a:b:c
групп.