6.6. Indexering och slicing

En ndarray adresseras på fyra sätt: enskilda index, slicer, booleska masker och tilldelningsformerna av var och en.

6.6.1. Enskilda element

Indexering med hakparenteser returnerar värdet på den givna positionen:

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

Negativa index räknas från slutet, på samma sätt som för en Python-list. Ett index utanför intervallet ger upphov till IndexError.

För arrayer med högre rang tar varje axel ett index. Indexen står inuti en uppsättning parenteser, separerade med kommatecken:

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

När färre index än axlar anges lämnas de oindexerade axlarna orörda. Resultatet är en vy med reducerad rang av källan:

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

6.6.2. Slicer

En slice start:stop:step returnerar en vy av arrayen. Vyn delar den underliggande databufferten med källan; att skriva genom vyn skriver till källan:

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)

När en oberoende buffert behövs producerar copy() en explicit.

Slicing utökas naturligt till högre dimensioner. Varje axel tar sin egen slice:

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

Att blanda ett heltal (enskilt index, släpper axeln) och en slice (behåller axeln) är tillåtet och är hur åtkomst till en enskild rad / enskild kolumn normalt skrivs.

6.6.3. Booleska masker

En boolesk array av samma form som källan väljer element där masken är True. Boolesk indexering fungerar för närvarande på 1-D-arrayer; indata med högre rang ger upphov till NotImplementedError

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

Utdata:

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

Masken är en vanlig bool-ndarray, så vilket uttryck som helst som ger en sådan fungerar:

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

Boolesk indexering returnerar en kopia. De valda elementen ligger på vilka positioner än masken är True – inte med ett regelbundet steg genom källan – så det finns ingen deskriptor som en vy kan använda för att adressera dem, och resultatet materialiseras till sin egen buffert.

6.6.4. Indexering med heltalsarray

Att skicka en lista eller array av index i parenteser plockar ut dessa element i ett steg:

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

Resultatet är en kopia; de utplockade elementen delar inte längre lagring med källan. Samma form fungerar på vänster sida av en tilldelning:

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

take() (täckt i Urval och omarrangering) är funktionsformen av samma operation och accepterar nyckelordet out= för allokeringsfri användning i en strömmande loop.

6.6.5. Slicetilldelning

Slicer och masker förekommer på vänster sida av en tilldelning lika väl som den högra. Den högra sidan kan vara en skalär, en annan array eller en vy:

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

Booleska masker till vänster ersätter de element som uppfyller villkoret:

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. Varför slicetilldelning är viktigt på en kamera

Slicetilldelning skriver genom en array som redan finns. Ingen ny array allokeras. Det är skillnaden mellan:

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

och:

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

Den första versionen ber kameran om RAM motsvarande två nya arrayer; den andra versionen ber inte om någonting. På en mikrokontroller med begränsat RAM är den skillnaden ofta skillnaden mellan ett skript som körs bekvämt och ett som får slut på minne.

Prestanda täcker mönstret i detalj. Den viktiga regeln för tillfället är att slicetilldelning, de aritmetiska operatorerna på plats (+=, *=, …) och nyckelordet out= på universella funktioner är de tre verktyg som gör allokeringsfria uppdateringar möjliga.