6.6. Indexování a slicing

ndarray se adresuje čtyřmi způsoby: jednotlivými indexy, řezy, booleovskými maskami a přiřazovacími formami každého z nich.

6.6.1. Jednotlivé prvky

Indexování v hranatých závorkách vrací hodnotu na dané pozici:

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

Záporné indexy se počítají od konce, stejně jako u Python objektu list. Index mimo rozsah vyvolá výjimku IndexError.

U polí vyšší úrovně přijímá každá osa index. Indexy se uvádějí uvnitř jedné sady závorek, oddělené čárkami:

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

Pokud je dodáno méně indexů než os, neindexované osy zůstanou nedotčené. Výsledkem je pohled se sníženou úrovní na zdroj:

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

6.6.2. Řezy

Řez start:stop:step vrací pohled na pole. Pohled sdílí podkladový datový buffer se zdrojem; zápis přes pohled zapisuje do zdroje:

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)

Pokud je potřeba nezávislý buffer, copy() jej vytvoří explicitně.

Slicing se přirozeně rozšiřuje do vyšších dimenzí. Každá osa přijímá vlastní řez:

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

Kombinace celého čísla (jediný index, zruší osu) a řezu (zachová osu) je povolena a takto se obvykle zapisuje přístup k jednomu řádku / jednomu sloupci.

6.6.3. Booleovské masky

Booleovské pole stejného tvaru jako zdroj vybírá prvky, kde je maska True. Booleovské indexování aktuálně funguje na 1-D polích; vstupy vyšší úrovně vyvolají výjimku NotImplementedError

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

Výstup:

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

Maska je obyčejný bool ndarray, takže funguje jakýkoli výraz, který takovou masku vydá:

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])

Booleovské indexování vrací kopii. Vybrané prvky leží na pozicích, kde je maska True – nikoli v pravidelném kroku zdrojem – takže neexistuje deskriptor, který by je pohled mohl použít k adresování, a výsledek se materializuje do vlastního bufferu.

6.6.4. Indexování polem celých čísel

Předání seznamu nebo pole indexů v závorkách vybere tyto prvky v jednom kroku:

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

Výsledkem je kopie; vybrané prvky již nesdílejí úložiště se zdrojem. Stejná forma funguje na levé straně přiřazení:

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

take() (popsaná na Výběr a přeuspořádání) je funkční forma téže operace a přijímá klíčové slovo out= pro použití bez alokace ve smyčce zpracovávající datový proud.

6.6.5. Přiřazení do řezu

Řezy a masky se objevují na levé straně přiřazení stejně jako na pravé. Pravá strana může být skalár, jiné pole nebo pohled:

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

Booleovské masky na levé straně nahrazují prvky, které splňují podmínku:

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. Proč na kameře záleží na přiřazení do řezu

Přiřazení do řezu zapisuje přes již existující pole. Nealokuje se žádné nové pole. To je rozdíl mezi:

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

a:

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

První verze si od kamery vyžádá RAM pro dvě nová pole; druhá verze nevyžádá nic. Na mikrokontroléru s omezenou RAM je tento rozdíl často rozdílem mezi skriptem, který běží pohodlně, a skriptem, kterému dojde paměť.

Výkon popisuje tento vzor podrobně. Důležité pravidlo pro tuto chvíli je, že přiřazení do řezu, aritmetické operátory pracující na místě (+=, *=, …) a klíčové slovo out= u univerzálních funkcí jsou tři nástroje, které umožňují aktualizace bez alokace.