NumPy: массивы, форма и индексация

Продвинутая индексация: булевы маски и целочисленные массивы индексов

Содержание курса

Изменение элементов присваиванием по маске и по индексам

До этого момента 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 непосредственно, и если нужна копия с изменениями — её нужно сделать явно до операции.

Продвинутая индексация: булевы маски и целочисленные массивы индексов — NumPy и pandas: практический тренажёр — Latorn