6.6. Indexering en slicing¶
Een ndarray wordt op vier manieren geadresseerd: enkele indices, slices, booleaanse maskers en de toewijzingsvormen van elk daarvan.
6.6.1. Enkele elementen¶
Indexering met vierkante haken retourneert de waarde op de gegeven positie:
a = np.arange(10, dtype=np.uint8)
print(a[0], a[-1]) # 0 9
print(a[1], a[-2]) # 1 8
Negatieve indices tellen vanaf het einde, net als bij een Python-list. Een index buiten bereik veroorzaakt een IndexError.
Voor arrays van hogere rang neemt elke as een index. De indices staan binnen één set haken, gescheiden door komma’s:
m = np.arange(9, dtype=np.uint8).reshape((3, 3))
print(m[1, 1]) # 4
print(m[2, 0]) # 6
Wanneer er minder indices dan assen worden opgegeven, blijven de niet-geïndexeerde assen intact. Het resultaat is een view met verlaagde rang van de bron:
print(m[0]) # the first row, as a 1-D view of m
6.6.2. Slices¶
Een slice start:stop:step retourneert een view van de array. De view deelt de onderliggende databuffer met de bron; schrijven via de view schrijft naar de bron:
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)
Wanneer er een onafhankelijke buffer nodig is, produceert copy() deze expliciet.
Slicing breidt zich op natuurlijke wijze uit naar hogere dimensies. Elke as neemt zijn eigen 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
Het mengen van een integer (enkele index, laat de as vallen) en een slice (behoudt de as) is toegestaan en is hoe toegang tot een enkele rij / enkele kolom normaal wordt geschreven.
6.6.3. Booleaanse maskers¶
Een booleaanse array met dezelfde vorm als de bron selecteert elementen waar het masker True is. Booleaanse indexering werkt momenteel op 1-D-arrays; invoer van hogere rang veroorzaakt een NotImplementedError
a = np.arange(9, dtype=np.float)
mask = a < 5
print(a[mask])
Uitvoer:
array([0.0, 1.0, 2.0, 3.0, 4.0], dtype=float)
Het masker is een gewone bool-ndarray, dus elke expressie die er een oplevert werkt:
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])
Booleaanse indexering retourneert een kopie. De geselecteerde elementen liggen op de posities waar het masker True is – niet op een regelmatige stap door de bron – dus er is geen descriptor die een view kan gebruiken om ze te adresseren, en het resultaat wordt gematerialiseerd in zijn eigen buffer.
6.6.4. Indexering met integer-arrays¶
Het doorgeven van een lijst of array van indices tussen haken plukt die elementen er in één stap uit:
a = np.array([10, 20, 30, 40, 50], dtype=np.uint8)
a[[0, 2, 4]]
# array([10, 30, 50], dtype=uint8)
Het resultaat is een kopie; de geplukte elementen delen niet langer opslag met de bron. Dezelfde vorm werkt aan de linkerkant van een toewijzing:
a[[0, 2, 4]] = 0
# array([0, 20, 0, 40, 0], dtype=uint8)
take() (behandeld op Selectie en herschikking) is de functievorm van dezelfde bewerking en accepteert een out=-keyword voor allocatievrij gebruik in een streaminglus.
6.6.5. Slice-toewijzing¶
Slices en maskers verschijnen aan de linkerkant van een toewijzing zowel als aan de rechterkant. De rechterkant mag een scalair, een andere array of een view zijn:
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
Booleaanse maskers aan de linkerkant vervangen de elementen die aan de voorwaarde voldoen:
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. Waarom slice-toewijzing er op een camera toe doet¶
Slice-toewijzing schrijft via een array die al bestaat. Er wordt geen nieuwe array gealloceerd. Dat is het verschil tussen:
out = a + b # makes a new array the size of a
out = out * 2 # makes another new array
en:
out[:] = a # writes into the existing out
out += b # in place
out *= 2 # in place
De eerste versie vraagt de cam om twee nieuwe arrays aan RAM; de tweede versie vraagt om niets. Op een microcontroller met beperkt RAM is dat verschil vaak het verschil tussen een script dat comfortabel draait en een dat zonder geheugen komt te zitten.
Prestaties behandelt het patroon in detail. De belangrijke regel voor nu is dat slice-toewijzing, de in-place rekenkundige operatoren (+=, *=, …) en het out=-keyword op universele functies de drie hulpmiddelen zijn die allocatievrije updates mogelijk maken.