Добрый день, уважаемые читатели. В море интереснейших статей про машинное обучение и анализ данных порой не хватает материалаа про вещи, которые мы ежедневно используем в своей работе. У меня, как и у многих людей, сначала вызывала вопрос индексация массивов NumPy. Именно об этом мы сегодня поговорим.
Подпишись на группу Вконтакте и Телеграм-канал. Там еще больше полезного контента для программистов.
А на YouTube-канале ты найдешь обучающие видео по программированию. Подписывайся!
Сравнение с Python
Конечно же, для начала импортируем исследуемый модуль numpy
.
import numpy as np
По традиции, мы импортируем numpy
под псевдонимом np
.
Теперь создадим вектор (в Python — одномерный список) целых чисел от 0 до 9, используя list
.
numbers = list(range(10)) numbers [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
У списков Python есть только две возможности индексирования по умолчанию (имеется в виду без дополнений пользователя с помощью наследования) — передать туда целое число либо объект типа slice
(в Python он имеет литеральное обозначение start:end:step
).
numbers[5], numbers[9:5:-1] (5, [9, 8, 7, 6])
Теперь же создадим массив NumPy с такими же элементами.
arr = np.arange(0, 10, 1) arr array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
Опробуем тот же метод индексации.
arr[5], arr[9:5:-1] (5, array([9, 8, 7, 6]))
Отлично, мы убедились в том, что массивы NumPy поддерживают те же инструменты индексации, что и объекты Python (написано именно так, потому что не только списки в Python поддерживают индексацию ).
Индексирование с помощью массивов
Также массивы NumPy можно индексировать с помощью обычных итерируемых объектов, содержащих индексы.
arr[[5, 3, 6, 2]] array([5, 3, 6, 2])
Стоит заметить, что порядок элементов сохраняется в соответствии индексов, указанных в массиве.
Также возможна индексация с помощью булевых массивов (такие массивы называются маской). Их форма должна совпадать с формой индексируемого массива. К примеру :
mask = [True, False, True, False, True] * 2 arr[mask] array([0, 2, 4, 5, 7, 9])
Особо удобным этот инструмент кажется из-за того, что все операции, которые должны возвращать булево значение, по отношению к массивам NumPy выполняются поэлементно, и соответственно возвращают булев массив такой же формы. Наглядный пример — чтобы выбрать все элементы массива, больше 5, достаточно передать в квадратные скобки конструкцию arr > 5
, которая возвращает array([False, False, False, False, False, False, True, True, True, True])
.
arr[arr > 5] array([6, 7, 8, 9])
Индексация многомерных массивов
Перейдем к созданию двумерных массивов и выполнению операций над ними. Создадим матрицу вида
matrix = np.array([range(1, 10)]).reshape(3, 3) matrix array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
Теперь перед нами раскрывается ещё больше возможностей, так как теперь у массива появилось две строки индексации (в нашем случае axis=0
это строки матрицы, а axis=1
— столбцы).
Это не значит, что двумерный массив не поддерживает обыкновенную индексацию массивом чисел:
matrix[[0, 1]] array([[1, 2, 3], [4, 5, 6]])
Но теперь же мы можем передать в квадратные скобки второй массив, который произведет индексацию уже по второй оси.
matrix[[0, 1], 2] array([3, 6])
Также мы можем совмещать срезы и индексы, таким способом делая наш инструмент всё более гибким.
matrix[:, [0, 2]] array([[1, 3], [4, 6], [7, 9]])
Конструкция [:, ]
означает полный перебор по соответствующей оси.
Также никто не запрещает нам использовать два среза по обеим осям. В следующем примере продемонстрирована интересная работа, которая поясняет, что конструкции [0]
и [:1]
не совсем одинаковые вещи, какими кажутся на первый взгляд.
matrix[:, 0], matrix[:, :1] (array([1, 4, 7]), array([[1], [4], [7]]))
Также с помощью срезов и операций сравнения можно выполнять интересные вещи. К примеру, выберем из матрицы все столбцы, второй элемент которых (имеется в виду действительно второй элемент, а не элемент с индексом 2) больше 3.
matrix[:, matrix[:, 1] > 3] array([[2, 3], [5, 6], [8, 9]])
Срез — копирование или нет?
Ещё один вопрос заключается в следующем: при срезе элементы копируются или возвращается ссылка на них? Рассмотрим поведение подобного случая по отношению к спискам Python.
matrix_list = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] matrix_slice = matrix_list[0:1] matrix_slice[0] = 5 matrix_list [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
Начиная с определенной версии, срез копируется, а ведь раньше эта проблема была актуальной. Теперь очередь NumPy.
matrix = np.arange(0, 9, 1).reshape(3, 3) matrix_slice = matrix[0:1] matrix_slice[0] = 5 matrix array([[5, 5, 5], [3, 4, 5], [6, 7, 8]])
Ха! Индексация NumPy срез не копирует, а потому его изменение прямо влияет на исходный объект. Будьте с этим предельно осторожны.
Заключение
Сегодня мы рассмотрели, что же такое индексация массивов NumPy. Указание индекса, маски и срезы — всё это сегодня было. Также мы обсудили проблему копирования среза, таким способом выявив значительную разницу между NumPy и Python.
Если вы используете Python в изучении физики, советую прочитать статью Бросок тела с высоты, моделируем на Python.
А также подписывайтесь на группу ВКонтакте, Telegram и YouTube-канал. Там еще больше полезного и интересного для программистов.