6.6. Індексування та зрізи

ndarray адресується чотирма способами: одиночними індексами, зрізами, булевими масками та формами присвоєння кожного з них.

6.6.1. Окремі елементи

Індексування у квадратних дужках повертає значення в заданій позиції:

a = np.arange(10, dtype=np.uint8)
print(a[0], a[-1])      # 0 9
print(a[1], a[-2])      # 1 8

Від’ємні індекси відраховуються від кінця, так само як і для Python list. Індекс за межами діапазону викликає IndexError.

Для масивів вищого рангу кожна вісь приймає індекс. Індекси вказуються всередині одного набору дужок, розділені комами:

m = np.arange(9, dtype=np.uint8).reshape((3, 3))
print(m[1, 1])          # 4
print(m[2, 0])          # 6

Коли надається менше індексів, ніж осей, невідіндексовані осі залишаються неушкодженими. Результат є представленням зменшеного рангу джерела:

print(m[0])             # the first row, as a 1-D view of m

6.6.2. Зрізи

Зріз start:stop:step повертає представлення масиву. Представлення спільно використовує базовий буфер даних із джерелом; запис через представлення записує у джерело:

a = np.arange(10, dtype=np.uint8)
v = a[::2]              # array([0, 2, 4, 6, 8], dtype=uint8)
v[0] = 99
print(a)
# array([99, 1, 2, 3, 4, 5, 6, 7, 8, 9], dtype=uint8)

Коли потрібен незалежний буфер, copy() явно його створює.

Зрізи природньо поширюються на вищі виміри. Кожна вісь приймає власний зріз:

m = np.array([[1, 2, 3],
              [4, 5, 6],
              [7, 8, 9]], dtype=np.uint8)

m[0]            # first row
m[0, :2]        # first two elements of row 0
m[:, 0]         # column 0 (still 2-D in ulab)
m[-1]           # last row
m[::2, ::2]     # every other row, every other column

Поєднання цілого числа (одиночний індекс, що відкидає вісь) і зрізу (що зберігає вісь) є допустимим і саме так зазвичай пишуть доступ до одного рядка/стовпця.

6.6.3. Булеві маски

Булевий масив тієї самої форми, що й джерело, вибирає елементи там, де маска є True. Булеве індексування наразі працює на 1-вимірних масивах; вхідні дані вищого рангу викликають NotImplementedError

a = np.arange(9, dtype=np.float)
mask = a < 5
print(a[mask])

Виведення:

array([0.0, 1.0, 2.0, 3.0, 4.0], dtype=float)

Маска – це звичайний bool ndarray, тому підходить будь-який вираз, що його дає:

b = np.array([4, 4, 4, 3, 3, 3, 13, 13, 13], dtype=np.uint8)
a = np.arange(9, dtype=np.uint8)
print(a[a * a > np.sin(b) * 100.0])

Булеве індексування повертає копію. Вибрані елементи знаходяться на будь-яких позиціях, де маска є True – не на регулярному кроці через джерело – тому немає дескриптора, який представлення могло б використати для їх адресування, і результат матеріалізується у власний буфер.

6.6.4. Індексування цілочисельним масивом

Передача списку або масиву індексів у дужках вибирає ці елементи за один крок:

a = np.array([10, 20, 30, 40, 50], dtype=np.uint8)
a[[0, 2, 4]]
# array([10, 30, 50], dtype=uint8)

Результат є копією; вибрані елементи більше не спільно використовують пам’ять із джерелом. Та сама форма працює у лівій частині присвоєння:

a[[0, 2, 4]] = 0
# array([0, 20, 0, 40, 0], dtype=uint8)

take() (розглянутий на Вибірка та впорядкування) є функціональною формою тієї самої операції і приймає ключове слово out= для безвиділяючого використання у потоковому циклі.

6.6.5. Присвоєння через зріз

Зрізи та маски також з’являються у лівій частині присвоєння. Права частина може бути скаляром, іншим масивом або представленням:

m = np.zeros((3, 3), dtype=np.uint8)
m[0]      = 1            # whole row 0 set to 1
m[:, 2]   = 3            # whole column 2 set to 3
m[1, 1:3] = [7, 8]       # row 1, columns 1 and 2

Булеві маски у лівій частині замінюють елементи, що задовольняють умові:

a = np.arange(9, dtype=np.uint8)
a[a < 3] = 99
# array([99, 99, 99, 3, 4, 5, 6, 7, 8], dtype=uint8)

a = np.arange(9, dtype=np.uint8)
b = np.array(range(9)) + 12
a[b < 15] = b[b < 15]
# array([12, 13, 14, 3, 4, 5, 6, 7, 8], dtype=uint8)

6.6.6. Чому присвоєння через зріз важливе для камери

Присвоєння через зріз записує через масив, що вже існує. Новий масив не виділяється. Це різниця між:

out = a + b              # makes a new array the size of a
out = out * 2            # makes another new array

та:

out[:] = a               # writes into the existing out
out   += b               # in place
out   *= 2               # in place

Перший варіант запитує у камери RAM для двох нових масивів; другий варіант нічого не запитує. На мікроконтролері з обмеженою RAM ця різниця часто є різницею між скриптом, що виконується комфортно, і тим, що закінчується пам’ять.

Продуктивність детально розглядає цей патерн. Важливе правило зараз: присвоєння через зріз, арифметичні оператори «на місці» (+=, *=, …) та ключове слово out= для універсальних функцій є трьома інструментами, що роблять можливими оновлення без виділення пам’яті.