Продвинутая индексация: булевы маски и целочисленные массивы индексов
Содержание курса
Изменение элементов присваиванием по маске и по индексам
До этого момента arr[mask] и arr[idx] использовались для чтения: мы получали новый массив, а исходный arr оставался нетронутым. Но те же конструкции работают и в левой части присваивания — тогда NumPy не создаёт копию, а изменяет элементы прямо в исходном массиве.
import numpy as np
arr = np.array([10, 20, 30, 40, 50])
mask = arr >= 30
arr[mask] = 0
print(arr) # [10 20 0 0 0]
После выполнения arr[mask] = 0 массив изменился на месте: все три позиции, где mask был True, теперь содержат 0. Никакой новый массив не возвращается — операция молча мутирует arr.
То же самое работает с целочисленными индексами:
arr = np.array([10, 20, 30, 40, 50])
idx = np.array([0, 2])
arr[idx] = 99
print(arr) # [99 20 99 40 50]
Вместо скаляра справа можно поставить массив. NumPy требует не буквального равенства длин, а совместимости формы правой части с формой выбранных позиций по правилам broadcasting. Скаляр и массив формы (1,) совместимы с любым числом выбранных позиций — они рассылаются по всем. Массив с точным числом элементов — тоже частный и самый распространённый случай:
arr = np.array([10, 20, 30, 40, 50])
mask = arr >= 30 # три True
# Точное совпадение: три значения на три позиции
arr[mask] = np.array([300, 400, 500])
print(arr) # [ 10 20 300 400 500]
# Broadcasting: одно значение на все выбранные позиции
arr[mask] = np.array([0]) # shape (1,) — работает так же, как скаляр
print(arr) # [ 10 20 0 0 0]
arr = np.array([10, 20, 30, 40, 50])
idx = np.array([1, 3]) # два индекса
arr[idx] = np.array([200, 400])
print(arr) # [ 10 200 30 400 50]
Ошибку вызывает именно несовместимая форма — например, три выбранных позиции и массив из двух значений, которые нельзя разослать:
arr = np.array([10, 20, 30, 40, 50])
mask = arr >= 30 # три True
# Несовместимая форма — NumPy бросит ValueError
arr[mask] = np.array([1, 2]) # shape (2,) нельзя разослать на (3,)
Один практический момент с целочисленной индексацией: не используйте повторяющиеся индексы при присваивании. NumPy не гарантирует порядок, в котором обрабатываются повторяющиеся позиции, поэтому итоговое значение в таких позициях непредсказуемо и может различаться в зависимости от версии и платформы. Для предсказуемого результата убедитесь, что все индексы в idx уникальны. У булевой маски этой проблемы нет по определению: каждая позиция массива либо True, либо False — она не может встретиться дважды.
Ключевое, что нужно держать в голове: arr[mask] = value и arr[idx] = value не возвращают ничего. Они меняют arr непосредственно, и если нужна копия с изменениями — её нужно сделать явно до операции.
