Hoofdstuk 8 — Universele functies (ufuncs), aggregaties en NaN‑behandeling
Let op: De tekst is in het Nederlands; alle Python codevoorbeelden zijn in het Engels.
Doel van dit hoofdstuk:
- Begrijpen wat universele functies (ufuncs) zijn en waarom ze de ruggengraat vormen van vectorisatie in NumPy.
- Leren werken met ufunc‑eigenschappen: broadcasting, type‑promotie,
out‑parameter,where, en speciale methoden zoalsreduce,accumulateenouter. - Aggregaties correct gebruiken (per as/axis, met/zonder
keepdims) en de verschillen kennen tussen standaard en NaN‑bewuste varianten. - Robuuste NaN/Inf‑behandeling: detectie, filteren, maskeren en numerieke valkuilen.
Aanbevolen voorkennis: Hoofdstuk 6 (arrays, vectorisatie), Hoofdstuk 7 (indexeren, reshapen, broadcasting).
8.1 Wat is een ufunc?
Een universele functie (ufunc) is een C‑geoptimaliseerde, elementgewijze functie die werkt op NumPy‑arrays met ondersteuning voor broadcasting, type‑casting en meerdere invoer/uitvoer‑argumenten.
Veel voorkomende ufuncs:
- Unair (één input):
np.negative,np.abs,np.sqrt,np.exp,np.log,np.sin,np.isnan,np.isfinite. - Binair (twee inputs):
np.add,np.subtract,np.multiply,np.divide,np.power,np.maximum,np.minimum.
import numpy as np
x = np.array([1.0, 4.0, 9.0])
print(np.sqrt(x)) # [1. 2. 3.]
print(np.add(x, 1)) # [ 2. 5. 10.]
Waarom ufuncs?
- Snel: geschreven in C, vermijden Python‑loops.
- Expressief: elementgewijze semantiek met broadcasting.
- Consistent: uniforme API (o.a.
out,where,dtype‑regels).
8.2 Broadcasting en ufuncs
Ufuncs ondersteunen standaard broadcasting (zie Hoofdstuk 7).
x = np.array([1, 2, 3]) # shape (3,)
M = np.array([[10], [20], [30]]) # shape (3,1)
print(np.add(M, x)) # shape (3,3)
Je kunt explieter zijn met as‑uitbreiding:
col = x[:, None] # (3,1)
row = x[None, :] # (1,3)
sum_matrix = np.add(col, row) # (3,3)
8.3 Type‑promotie en casting
NumPy beslist het uitvoer‑dtype op basis van de input‑dtypes en de ufunc.
x = np.array([1, 2, 3], dtype=np.int32)
y = np.array([0.5, 1.5, 2.5], dtype=np.float64)
print((x + y).dtype) # float64 (upcast)
Let op:
- Integer deling met
/produceert floats;//is floor division (let op negatieven). - Overflows zijn mogelijk met integer dtypes; kies
int64of floats waar nodig.
8.4 De out‑parameter en in‑place rekenen
Veel ufuncs accepteren out= om het resultaat in een bestaande array te schrijven (minder allocaties).
x = np.arange(5, dtype=np.float64)
y = np.empty_like(x)
np.sqrt(x, out=y) # y gets sqrt(x)
np.add(x, 1, out=x) # in-place: x <- x + 1
Dit is geheugen‑ en cache‑vriendelijk. Pas op met type‑compatibiliteit: out moet het resultaat kunnen representeren (geen stille truncatie).
8.5 Voorwaardelijk rekenen met where
where op een ufunc laat je elementgewijs bepalen waar de operatie plaatsvindt; elders wordt de bestaande out behouden.
x = np.array([-2.0, -1.0, 0.0, 1.0, 2.0])
res = np.empty_like(x)
np.sqrt(np.abs(x), out=res) # fill res first
np.sqrt(x, out=res, where=(x >= 0)) # only where x>=0; others keep previous
print(res) # sqrt for non-negative x, abs->sqrt for negatives via prefill
Vergelijk met de hogere‑niveau helper np.where(cond, A, B) die een nieuwe array bouwt; de ufunc‑variant kan allocaties vermijden.
8.6 Speciale ufunc‑methoden: reduce, accumulate, outer
Ufuncs bieden methoden die structurele operaties uitvoeren zonder expliciete Python‑loops.
reduce: herhaalde toepassing langs een as (klassieke aggregatie).accumulate: tussenresultaten (prefix‑operatie).outer: cartesiaanse toepassing op alle paren elementen uit twee vectoren.
x = np.array([1, 2, 3, 4])
print(np.add.reduce(x)) # 10 (equivalent aan np.sum)
print(np.multiply.accumulate(x)) # [ 1 2 6 24]
u = np.array([1, 2, 3])
v = np.array([10, 20, 30])
print(np.multiply.outer(u, v)) # 3x3 outer product
Opmerking: Voor veelgebruikte gevallen bestaan directe functies (np.sum, np.cumsum, np.outer). De ufunc‑methoden zijn expliciet en kunnen handig zijn in generieke code.
8.7 Aggregaties: som, gemiddelden, extremen, arg‑varianten
Aggregaties reduceren dimensies. Belangrijke parameters:
axis: over welke as(sen) te aggregeren.None(default) aggregeert over alles.keepdims: behoud eenheden‑dimensies (handig voor broadcasting terug).dtype: reken‑dtype (bijv. som van int32 als int64 om overflow te vermijden).
A = np.arange(12).reshape(3, 4)
print(A.sum()) # scalar
print(A.sum(axis=0)) # shape (4,)
print(A.mean(axis=1, keepdims=True)) # shape (3,1)
print(A.argmax(axis=1)) # index van maximum per rij
Let op: argmin/argmax geven indexen terug; gebruik die voor geavanceerde selectie.
8.8 NaN‑bewuste varianten en ontbrekende waarden
Standaard aggregaties propageren NaNs: elk NaN in de reductie maakt het resultaat NaN.
x = np.array([1.0, np.nan, 3.0])
print(np.sum(x)) # nan
Gebruik de NaN‑varianten om NaNs te negeren:
print(np.nanmean(x)) # 2.0
print(np.nanstd(x)) # std over niet-NaN elementen
print(np.nanmin(x), np.nanmax(x))
Detecteren en maskeren:
A = np.array([1.0, np.nan, np.inf, -np.inf])
mask = np.isfinite(A) # True voor eindige waarden
finite_vals = A[mask]
Veelvoorkomende strategieën:
- Filter NaNs met boolean masks (
np.isnan,np.isfinite). - Vervang NaNs met
np.nan_to_num(A, nan=0.0, posinf=None, neginf=None)ofnp.where(np.isnan(A), fill, A). - Gebruik
dtype=floatwanneer NaNs verwacht worden; integers kunnen geen NaN representeren.
8.9 Logische en vergelijkings‑ufuncs
Vergelijkingsufuncs leveren boolean arrays, bruikbaar voor masking of all/any.
x = np.array([1, 2, 3])
y = np.array([2, 2, 2])
print(np.greater(x, y)) # [False False True]
print(np.equal(x, y)) # [False True False]
print(np.logical_and(x > 0, y == 2)) # boolean combine
print((x > 0).all(), (x > 0).any())
Let op verschil tussen logische operators en bitwise operators:
- Voor boolean arrays:
&(and),|(or),~(not) werken elementgewijs, maar vereisen haakjes door operator‑precedence. - Gebruik geen
and/ormet arrays (die werken niet elementgewijs).
8.10 Numerieke stabiliteit, overflow en precisie
- Integers kunnen over‑ en onderlopen (stil). Gebruik grotere dtypes of floats bij cumulatieve sommen (
dtype=np.int64offloat64). np.expkan snel naarinfgaan; combineer metnp.logaddexp/np.log1p/np.expm1voor stabiele vormen.- Voor sommen met floats kan
np.sumafhankelijk van volgorde kleine fouten maken;np.sum(..., dtype=np.float64)ofmath.fsum(op Python lists) kan nauwkeuriger zijn.np.nansumheeft dezelfde overwegingen.
x = np.array([1e16, 1, -1e16])
print(np.sum(x)) # 0.0 of 1.0 afhankelijk van order; hier vaak 0.0
print(np.sum(x, dtype=np.float64))
8.11 Veel voorkomende patronen en idiomen
- Normaliseren per kolom:
Z = (A - A.mean(axis=0)) / A.std(axis=0, ddof=0). - Clippen van waarden:
np.clip(x, a_min, a_max)(ufunc‑achtig, zeer snel). - Sigmoid en softmax:
z = np.linspace(-6, 6, 5)
sigmoid = 1 / (1 + np.exp(-z))
# Softmax per rij
X = np.array([[1.0, 2.0, 3.0], [1.0, 2.0, 4.0]])
Xmax = X.max(axis=1, keepdims=True)
exp = np.exp(X - Xmax)
softmax = exp / exp.sum(axis=1, keepdims=True)
- Gebruik
whereom veilig te delen:
num = np.array([1.0, 2.0, 0.0])
den = np.array([0.0, 2.0, 0.0])
res = np.divide(num, den, out=np.zeros_like(num), where=den!=0)
8.12 Prestaties en geheugen
- Vermijd
np.vectorizevoor performance; het is een handige wrapper rond Python‑loops, niet snel. Gebruik echte ufuncs of schrijf C/Numba/Cython indien nodig. - Gebruik
out=en in‑place operatoren (+=,*=, etc.) om allocaties te beperken. - Let op broadcasting naar enorme tussenresultaten (geheugenexplosie). Controleer shapes en gebruik associativiteit/volgorde om tussenstappen te beperken.
- Kies passende dtypes (
float32kan veel sneller/zuiniger zijn op grote arrays; test nauwkeurigheid).
8.13 Korte oefeningen
- Geef een vector
xen gebruiknp.add.reduceom de som te berekenen. Vergelijk metnp.sumen verklaar de relatie. - Gebruik
np.multiply.accumulateom een cumulatief product van een vector te berekenen en verifieer metnp.cumprod. - Maak een (n, m) array met NaNs en bereken de kolomgemiddelden waarbij NaNs genegeerd worden; vergelijk
meanmetnanmean. - Construeer een matrix van alle paren‑som combinaties van twee vectoren met
np.add.outeren check de shape. - Deel twee arrays veilig waarbij delen door nul voorkomt, zonder warnings en zonder NaN, met behulp van
whereenout.
Werkboek en uitwerkingen:
- Oefeningen bij dit hoofdstuk vind je in
../workbook/hoofdstuk-08/oefeningen.md. - Uitwerkingen staan in
../solutions/hoofdstuk-08/uitwerkingen.md.
Verder lezen en vervolg:
- Hoofdstuk 6: basis vectorisatie en ndarray‑mechanica.
- Hoofdstuk 7: broadcasting‑regels en vormmanipulaties.
- Hoofdstuk 12: geavanceerde masking/boolean logica en meer indexeringstrucs.