Hoofdstuk 7 — Indexeren, slicen, reshapen en broadcasting (NumPy‑verdieping)
Let op: De tekst is in het Nederlands; alle Python codevoorbeelden zijn in het Engels.
Doel van dit hoofdstuk:
- Krachtig en correct werken met NumPy‑indexering en slicing (1D en N‑D).
- Begrijpen wanneer je een view of een kopie krijgt (en waarom dat uitmaakt).
- Reshapen en vormmanipulatie (incl.
ravel,flatten,resize, volgordes C/Fortran). - Broadcasting in detail, inclusief veelvoorkomende patronen en valkuilen.
Aanbevolen voorkennis: Hoofdstuk 6 (ndarray‑basis, shapes, eenvoudige broadcasting), basis Python.
7.1 Basisindexering en slicing (views)
Eenvoudige slicing in NumPy levert in het algemeen een view op dezelfde data (geen kopie). Aanpassingen via de slice veranderen dus het origineel.
import numpy as np
x = np.arange(10) # [0 1 2 3 4 5 6 7 8 9]
sub = x[2:7] # view of x (elements 2..6)
sub[:] = -1 # in-place modify the view
print(x) # [ 0 1 -1 -1 -1 -1 -1 7 8 9]
- Syntax:
start:stop:stepwaarbijstartinclusief is enstopexclusief. - Weglaten van grenzen betekent begin of einde van de as.
- Een negatieve
stepkeert de volgorde om (negatieve stride).
x[::-1] # reversed copy view (uses a negative stride)
x[::2] # every second element
x[1:-1:2] # slice from index 1 to the one-before-last, step 2
Let op: hoewel slicing meestal een view is, zijn er uitzonderingen bij niet‑triviale cases met fancy indexing (zie §7.3) die altijd een kopie oplevert.
7.2 Multidimensionale slicing en as‑volgorde
Je kunt per as slicen met komma‑gescheiden indices.
A = np.arange(24).reshape(4, 6) # shape (4, 6)
# rows 1..2 (inclusive of 1, exclusive of 3) and columns 2..4
B = A[1:3, 2:5] # shape (2, 3), view
# select a single row keeps 1D array unless you use a slice
row1 = A[1] # shape (6,) — 1D
row1_2D = A[1:2] # shape (1, 6) — 2D view
# mix of slices and integers
col3 = A[:, 3] # shape (4,) — 1D selection of the 4th column
block = A[::2, ::3] # strides over both axes
- Een integer‑selectie op een as verwijdert die dimensie in het resultaat.
- Gebruik slicing met
start:stopom de dimensie te behouden.
7.2.1 Negatieve indexen en grenzen
Negatieve indexen tellen vanaf het einde: -1 is de laatste, -2 de een‑na‑laatste.
A[-1, -1] # last element in last row
A[:, -2:] # last two columns
7.3 Fancy indexing: integer en boolean arrays (kopieën)
Fancy indexing met lijsten/arrays van indices of met boolean masks maakt ALTIJD een kopie van de geselecteerde elementen. Dit is krachtig, maar kan geheugen kosten.
7.3.1 Integer‑array indexing
x = np.arange(10)
idx = np.array([0, 3, 3, 7])
y = x[idx] # copy with elements at positions 0, 3, 3, 7
y[0] = 999
print(x[0]) # 0 (unchanged, because y is a copy)
Je kunt multi‑axis integer arrays combineren:
M = np.arange(12).reshape(3, 4)
rows = np.array([0, 2])
cols = np.array([1, 3])
sel = M[rows[:, None], cols] # shape (2, 2): pairs (0,1), (0,3), (2,1), (2,3)
7.3.2 Boolean‑mask indexing
x = np.array([3, -1, 0, 5, 2])
mask = x > 0
pos = x[mask] # copy of positive elements
# Assigning via boolean mask writes in-place to original (on the left side)
x[x < 0] = 0 # negative values set to 0 in x
Let op: rechts van de toekenning (x[mask]) is het resultaat een kopie; links (x[mask] = ...) schrijft terug naar x.
7.4 Views vs copies: hoe herken je ze?
- Basis slicing (met
:) → meestal view. - Fancy indexing (integer/boolean) → kopie.
reshape→ meestal view wanneer mogelijk (contigu geheugen); anders kan het een kopie maken.ravel()→ retourneert een 1D‑view indien mogelijk;flatten()maakt altijd een kopie.
A = np.arange(6).reshape(2, 3)
B = A.reshape(3, 2) # view if memory layout allows
C = A.flatten() # always a copy
D = np.ravel(A) # view if possible
B[0, 0] = 99
print(A[0, 0]) # likely 99 if B is a view
Tip: gebruik np.shares_memory(A, B) om te controleren of arrays geheugen delen.
7.5 Vormmanipulatie: reshape, ravel, resize, squeeze, expand
reshape(newshape): retourneert view indien mogelijk;-1voor inferentie.ravel(): 1D weergave (view indien mogelijk), respecteert standaard C‑order.flatten(order='C'|'F'): altijd kopie; kies orde.resize(newshape): kan data heralloceren en aanvullen; gebruik spaarzaam.np.squeeze(a, axis=None): verwijdert eenheden‑dimensies (dimensies met lengte 1).np.expand_dims(a, axis): voegt een as toe; gelijkwaardig aana[np.newaxis, ...]ofa[..., np.newaxis].
x = np.arange(6)
X = x.reshape(2, 3)
col_vec = x[:, np.newaxis] # shape (6, 1)
row_vec = x[np.newaxis, :] # shape (1, 6)
Y = np.squeeze(np.zeros((1, 3, 1))) # shape (3,)
Z = np.expand_dims(np.array([1, 2, 3]), axis=-1) # shape (3, 1)
7.6 Broadcasting in detail
Broadcasting vergelijkt shapes van rechts naar links. Dimensies zijn compatibel als ze gelijk zijn of één is 1. Het resultaat neemt de uitgebreide dimensies aan.
7.6.1 Voorbeelden met expliciete as‑uitbreiding
x = np.array([1, 2, 3]) # (3,)
W = np.array([10, 20, 30, 40]) # (4,)
M = x[np.newaxis, :] + W[:, np.newaxis] # (4, 3)
7.6.2 Schalen en centreren per kolom/rij
A = np.arange(12).reshape(3, 4)
col_mean = A.mean(axis=0) # shape (4,)
A_centered = A - col_mean # broadcast over rows
row_std = A.std(axis=1, keepdims=True) # shape (3,1)
A_scaled = A / row_std
7.6.3 Geavanceerder: afstandsmatrices met broadcasting
points = np.array([[0.0, 0.0],
[1.0, 0.0],
[0.0, 1.0]]) # shape (3, 2)
# pairwise squared Euclidean distances
D = ((points[:, np.newaxis, :] - points[np.newaxis, :, :]) ** 2).sum(axis=-1)
# D.shape -> (3, 3)
7.7 Prestaties, geheugen en valkuilen
- Fancy indexing kan onverwacht veel geheugen gebruiken (kopieën) — gebruik boolean assignment of slicing waar mogelijk.
- Onbedoelde broadcast naar zeer grote shapes kan een
MemoryErrorveroorzaken — check shapes expliciet (.shape). - Negatieve strides (zoals
x[::-1]) kunnen problemen geven met sommige C‑extensies die contiguïteit vereisen — gebruikx.copy()ofnp.ascontiguousarraywanneer nodig. - In‑place bewerkingen (
+=,*=) zijn sneller en alloceren minder, maar let op type‑promotie (kan kopieën veroorzaken).
7.8 Veelgemaakte patronen
- Kolom/rij‑vectoren expliciet maken met
[:, None]ofNone, :. - Batch‑dimensie toevoegen voor vectorized evaluaties:
x[None, ...]ofx[..., None]. - Masked updates:
A[A < 0] = 0. - Indexpatronen bouwen voor blokselecties met
np.ix_:
M = np.arange(16).reshape(4, 4)
rows = np.array([0, 2])
cols = np.array([1, 3])
block = M[np.ix_(rows, cols)] # shape (2, 2), copy
7.9 Korte oefeningen
- Geef een 2D array
A(shape (5, 6)). Selecteer een 3x3 blok in het midden met slicing en zet de waarden op 0 (in‑place). Toon datAis aangepast. - Maak van een vector
x(shape (N,)) een kolomvector en een rijvector en vorm daarmee de sommatrix van shape (N, N) zondertile. - Gebruik fancy indexing om uit een matrix
M(shape (4, 5)) de elementen op (0,0), (1,2), (2,4), (3,1) in een nieuwe array te verzamelen. - Gegeven punten in
R^din arrayPmet shape (n, d): construeer een matrix met alle paargewijze afstanden door broadcasting te gebruiken (geen loops).
Hints: gebruik np.newaxis/None, np.ix_, sum(axis=-1) en let op shapes.
Werkboek en uitwerkingen:
- Oefeningen bij dit hoofdstuk vind je in
../workbook/hoofdstuk-07/oefeningen.md. - Uitwerkingen staan in
../solutions/hoofdstuk-07/uitwerkingen.md.
Verder lezen en vervolg:
- Hoofdstuk 12 behandelt geavanceerde indexing, masking en boolean logica diepgaander.
- Terugkoppeling met Hoofdstuk 6 voor basisbegrippen over arrays en geheugen.