#Inhoud
Introductie en workflow
- Waarom Data Science met Python?
- Installatie en omgeving
- Jupyter Notebooks
- Versiebeheer en samenwerking
- Projecttemplates
Numpy
Pandas
- Pandas: Series en DataFrame
- Data inlezen en schrijven
- Data schoonmaken
- Groeperen
- Tijdsreeksen
- Samenvoegen
- Prestatie,
- Geavanceerde indexen
Matplotlib
- Matplotlib basis
- Veelgebruikte grafieken
- Aangepaste visualisaties
- Best practices
- Integratie met pandas en NumPy
- Exporteren
Scikit-learn
- machine learning met scikit‑learn
- preprocessing
- Regressie
- Classificatie
- Modelselectie
- Clustering
- Feature engineering
Hoofdstuk 1 — Waarom Data Science met Python? Overzicht, use‑cases en ecosysteem
Let op: De tekst is in het Nederlands; alle codeblokken gebruiken Engels waar van toepassing.
Doel van dit hoofdstuk:
- Begrijpen waarom Python een toonaangevende taal is voor data‑analyse, ‑engineering en ‑science.
- Een overzicht krijgen van het ecosysteem (NumPy, pandas, Matplotlib, Jupyter, scikit‑learn, enz.).
- Voorbeelden van use‑cases in business, research en productie (ETL, analyse, ML, dashboards).
Aanbevolen voorkennis: Basiscomputervaardigheden; geen programmeerervaring vereist.
1.1 Waarom Python voor data?
Python biedt een sterke combinatie van leesbaarheid, een grote community en krachtige bibliotheken. Het is:
- Vrij en open‑source met een actief ecosysteem.
- Breed inzetbaar: van data‑analyse en machine learning tot web‑apps en automatisering.
- Interactief (Jupyter), maar ook geschikt voor scripts en services.
Use‑cases:
- Exploratieve data‑analyse, rapportage en dashboards.
- ETL/ELT‑pijplijnen en data‑engineering workflows.
- Machine learning: van prototyping tot uitrol.
- Wetenschappelijk onderzoek en reproducible computing.
1.2 Het wetenschappelijke Python‑ecosysteem
Belangrijke bouwstenen en hun rol:
- NumPy: N‑dimensionale arrays, vectorisatie en numerieke basis.
- pandas: tabulaire data (Series/DataFrame), I/O, wrangling, aggregaties.
- Matplotlib: kernvisualisatie (basis), met uitbreidingen (Seaborn, Plotly) buiten scope.
- Jupyter: interactieve notebooks, combineert code, tekst en figuren.
- scikit‑learn: machine‑learning‑API met consistente fit/transform/predict‑filosofie.
- SciPy: wetenschappelijke routines (optimalisatie, signaalverwerking, statistiek).
- SQLAlchemy: databaseconnecties voor SQL‑bronnen.
- PyArrow: kolomdata en Parquet/Arrow‑integratie.
1.3 Workflow: van notebook naar product
- Start in Jupyter voor verkenning; migreer naar modules/pakketten voor hergebruik.
- Gebruik versiebeheer (Git) voor geschiedenis en samenwerking.
- Automatiseer omgeving (requirements/environment) voor reproduceerbaarheid.
- Exporteer figuren en rapporten; publiceer dashboards of endpoints waar nodig.
1.4 Interoperabiliteit en performance in vogelvlucht
- Vectorisatie (NumPy/pandas) is meestal sneller dan Python‑loops.
- Gebruik kolomformaat (Parquet) voor efficiënte I/O.
- Profiteer van ecosysteemintegratie: pandas ↔ NumPy ↔ Matplotlib ↔ scikit‑learn.
1.5 Voorbeelden van domeinen en patronen
- Retail: vraagvoorspelling, mandanalyse, A/B‑tests.
- Finance: risicomodellen, anomaliedetectie, tijdreeksen.
- Industrie: predictief onderhoud, sensordata, kwaliteitscontrole.
- Publieke sector/onderzoek: open data, reproducible science.
1.6 Tips
- Leer de kernpakketten eerst (NumPy, pandas, Matplotlib), breid daarna uit.
- Werk met kleine, herhaalbare stappen; commit vaak.
- Documenteer aannames en pin versies voor reproduceerbaarheid.
1.7 Samenvatting
Python is krachtig door zijn ecosysteem en community. Je gebruikt het voor verkenning, analyse, visualisatie en ML. In de volgende hoofdstukken leer je de praktische workflow inrichten (installatie, Jupyter, Git) en zet je de stap naar robuuste projecten.
Hoofdstuk 2 — Installatie en omgeving: Python, pip, virtualenv en conda
Let op: De tekst is in het Nederlands; shell‑commando’s en codeblokken zijn in het Engels.
Doel van dit hoofdstuk:
- Een werkende Python‑installatie opzetten op Windows/macOS/Linux.
- Pakketbeheer begrijpen met pip/venv en een alternatief met conda/mamba.
- Beste praktijken voor geïsoleerde omgevingen en reproduceerbare dependencies.
Aanbevolen voorkennis: Hoofdstuk 1.
2.1 Python installeren
Aanbevolen opties:
- Gebruik de officiële installer (python.org) of je OS‑pakketbeheerder (Homebrew, apt, winget).
- Alternatief: conda/mamba‑distributie (Anaconda/Miniforge/Mambaforge) met gebundelde wetenschappelijke pakketten.
Controleer versie:
python --version
python3 --version
2.2 pip en virtualenv/venv
Gebruik per project een geïsoleerde omgeving.
# Maak een venv (Python 3.8+)
python -m venv .venv
# Activeer
# Windows (PowerShell):
.\.venv\Scripts\Activate.ps1
# macOS/Linux (bash/zsh):
source .venv/bin/activate
# Upgrade pip en installeer pakketten
python -m pip install --upgrade pip
pip install numpy pandas matplotlib jupyterlab scikit-learn
# Exporteer requirements
pip freeze > requirements.txt
Deactiveer met deactivate.
2.3 conda/mamba omgevingen (alternatief)
Conda biedt binair beheerde pakketten, handig voor complexe dependencies.
# Installeer mamba (snel) via Mambaforge/Miniforge
mamba --version
# Maak en activeer een omgeving
mamba create -n datasci python=3.11 numpy pandas matplotlib jupyterlab scikit-learn
mamba activate datasci
# Exporteer omgeving
mamba env export --no-builds > environment.yml
Tips:
- Gebruik bij voorkeur één systeem (pip/venv of conda/mamba) per project om mixen te vermijden.
- pin versies in requirements/environment voor reproduceerbaarheid.
2.4 JupyterLab en kernels
Installeer JupyterLab en koppel je omgeving als kernel.
pip install jupyterlab ipykernel
python -m ipykernel install --user --name datasci-venv --display-name "Python (datasci-venv)"
# Start JupyterLab
jupyter lab
2.5 Mapstructuur van een data‑project (vooruitblik)
Een pragmatische structuur (voorbeeld):
project/
data/ # ruwe/brondata (niet committen als groot/gevoelig)
notebooks/ # verkenning en rapportage
src/ # herbruikbare functies/modules
reports/ # figuren en resultaten
tests/ # (optioneel) unit tests
requirements.txt of environment.yml
README.md
2.6 Troubleshooting
- Meerdere Python’s op systeem? Controleer
which python/where python. - Proxy/firewall‑issues met pip? Stel
PIP_INDEX_URLenPIP_TRUSTED_HOSTin indien nodig. - Compileerproblemen? Overweeg conda/mamba voor geprecompileerde wheels.
2.7 Samenvatting
Je hebt nu opties om Python te installeren, geïsoleerde omgevingen te maken met pip/venv of conda/mamba, en JupyterLab te starten. In Hoofdstuk 3 richten we de dagelijkse workflow in met Jupyter en reproducibility‑patronen.
Hoofdstuk 3 — Werkwijze met Jupyter Notebooks, reproducibility en projectstructuur
Let op: De tekst is in het Nederlands; codevoorbeelden en shell‑commando’s zijn in het Engels.
Doel van dit hoofdstuk:
- Efficiënt werken met Jupyter (Lab/Notebook): cellen, kernels, magics, export.
- Reproduceerbare analyses: vaste omgeving, vaste random seeds, datamanagement.
- Praktische projectstructuur voor data‑werk en samenwerking.
Aanbevolen voorkennis: Hoofdstuk 1–2.
3.1 Jupyter: basis en best practices
Start JupyterLab:
jupyter lab
Belangrijke concepten:
- Kernel: de Python‑runtime; herstart bij foutieve staat of geheugenlekken.
- Cellen: mix van code en Markdown; gebruik koppen en tekst voor context.
- Magics:
%timeit,%matplotlib inline,%%bash, etc.
Tips:
- Houd notebooks klein en doelgericht; verplaats herhaalbare code naar
src/modules. - Gebruik
Restart & Run Allvoor een schone run. - Exporteer naar HTML/PDF voor rapportage; controleer output.
3.2 Reproduceerbaarheid: omgeving en seeds
- Pin pakketversies in
requirements.txtofenvironment.yml. - Leg hardware/OS/versies vast in een
REPORT.mdof in de notebook header. - Stel random seeds waar relevant:
import numpy as np
rng = np.random.default_rng(seed=42)
- Noteer datums van data‑extracts en I/O‑paden.
3.3 Data‑beheer in projecten
- Bewaar ruwe data onveranderd in
data/raw/; schrijf tussenresultaten naardata/processed/. - Gebruik Parquet voor kolom‑georiënteerde opslag en snellere herlaad.
- Houd gevoelige data buiten Git (git‑ignore); gebruik secrets managers i.p.v. hard‑coded wachtwoorden.
3.4 Projectstructuur (voorbeeld)
project/
data/
raw/
processed/
notebooks/
01-exploratie.ipynb
02-rapport.ipynb
src/
__init__.py
features.py
viz.py
reports/
figures/
requirements.txt # of environment.yml
README.md
3.5 Notebook → module: code hergebruik
Verplaats functies naar src/ en importeer vanuit notebooks:
# src/features.py
import pandas as pd
def add_amount(df: pd.DataFrame) -> pd.DataFrame:
return df.assign(amount=df['qty'] * df['price'])
# In notebook
from src.features import add_amount
3.6 Valstrikken en performance
- Notebooks kunnen “stateful drift” veroorzaken; herstart regelmatig.
- Grote tabellen/figuren maken notebooks zwaar; cache tussenresultaten in Parquet.
- Gebruik versiebeheer (Git) voor geschiedenis; commit notebooks met nbdime diff (optioneel).
3.7 Samenvatting
Je kunt nu productief werken in Jupyter met aandacht voor reproduceerbaarheid en structuur. In Hoofdstuk 4 leer je samenwerken met Git/GitHub en review‑workflows.
Hoofdstuk 4 — Versiebeheer en samenwerking: Git, GitHub en code review
Let op: De tekst is in het Nederlands; shell‑commando’s zijn in het Engels.
Doel van dit hoofdstuk:
- Basis Git‑workflow: init, commit, branch, merge, pull/push.
- Werken met GitHub (of GitLab): pull requests, issues, CI‑indicaties.
- Code review‑principes voor data‑projecten (notebooks, scripts, data‑artefacten).
Aanbevolen voorkennis: Hoofdstuk 1–3.
4.1 Git initialiseren en eerste commit
# In een nieuwe map
git init
git status
echo "# Project" > README.md
git add README.md
git commit -m "init"
4.2 Branches en merges
git switch -c feature/eda
# werk... voeg bestanden toe
git add notebooks/01-exploratie.ipynb
git commit -m "EDA eerste versie"
# terug naar main en merge
git switch main
git merge feature/eda
4.3 Remote en samenwerken
git remote add origin https://github.com/<org>/<repo>.git
git push -u origin main
- Gebruik pull requests (PR) voor review.
- Koppel issues aan PR’s voor traceerbaarheid.
4.4 Code review voor data‑science
Checklist:
- Is de analyse reproduceerbaar? (requirements/environment aanwezig)
- Zijn notebooks schoon gerund (Restart & Run All) en niet te groot?
- Is data‑in/‑uit duidelijk gedocumenteerd (paden, formaten, privacy)?
- Zijn resultaten/figuren interpreteerbaar en correct gelabeld?
- Zijn functies/herbruikbare logica in
src/ondergebracht met tests (indien toepasselijk)?
4.5 Diffen van notebooks
- Gebruik
nbdimeof GitHub’s notebook viewer om verschillen te bekijken. - Overweeg export naar script (
jupyter nbconvert --to script) voor review van logica.
4.6 Basis CI‑signalen (kort)
- Voeg een eenvoudige CI toe die
pip install -r requirements.txtuitvoert en basislinting doet (ruff/flake8). - Optioneel: run snelle tests (
pytest -q).
4.7 Samenvatting
Git en GitHub ondersteunen samenwerking en review. Gebruik branches en PR’s, en hanteer een reviewchecklist die focust op reproduceerbaarheid, duidelijkheid en datakwaliteit. In Hoofdstuk 5 kijken we naar projecttemplates, cookiecutter en packaging basics.
Hoofdstuk 5 — Projecttemplates, cookiecutter en packaging basics
Let op: De tekst is in het Nederlands; shell‑commando’s en bestandsstructuren zijn in het Engels.
Doel van dit hoofdstuk:
- Snel starten met consistente projecttemplates (Cookiecutter).
- Basis van Python‑packages (src‑layout, pyproject.toml, installable packages).
- Distributie en versiebeheer op hoofdlijnen.
Aanbevolen voorkennis: Hoofdstuk 1–4.
5.1 Waarom templates?
- Consistentie: dezelfde structuur en tooling voor alle projecten.
- Snelheid: minder opzetwerk, meer focus op inhoud.
- Kwaliteit: ingebouwde linters, tests, CI‑skelet.
5.2 Cookiecutter gebruiken
Installeer Cookiecutter en genereer een project:
pip install cookiecutter
cookiecutter https://github.com/cookiecutter/cookiecutter-pypackage
# of een data‑science template, bijv. (voorbeeld):
# cookiecutter https://github.com/drivendata/cookiecutter-data-science
Je beantwoordt prompts; daarna krijg je een projectmap met best practices.
5.3 src‑layout en pyproject.toml
Moderne packaging gebruikt pyproject.toml en een src/‑layout.
project/
src/
mypkg/
__init__.py
core.py
tests/
pyproject.toml
Voorbeeld pyproject.toml (poetry/pep517‑achtig):
[build-system]
requires = ["setuptools", "wheel"]
build-backend = "setuptools.build_meta"
[project]
name = "mypkg"
version = "0.1.0"
authors = [{name = "Jij", email = "jij@example.com"}]
dependencies = [
"numpy",
"pandas",
]
Installeer lokaal in editable mode:
pip install -e .
5.4 Versiebeheer en releases (kort)
- Houd een
CHANGELOG.mdbij met semantische versies (MAJOR.MINOR.PATCH). - Automatiseer releases (GitHub Releases/Actions) en tags (
git tag v0.1.0). - Publiceren naar PyPI kan met
twine(buiten scope voor volledige flow).
5.5 Data‑science templates: extra overwegingen
- Mappen voor
data/(raw/processed),notebooks/,reports/,src/. - Voorgeconfigureerde linting (ruff/flake8), formatting (black), en pre‑commit hooks.
- CI‑workflow voor basischecks en (optioneel) tests.
5.6 Samenvatting
Templates en packaging geven je een vliegende start en betere onderhoudbaarheid. Combineer dit met Git‑workflows (Hoofdstuk 4) en de tools uit Deel III–V voor robuuste, herhaalbare data‑projecten.
Hoofdstuk 6 — NumPy‑arrays en vectorisatie: denken in arrays
Let op: De tekst is in het Nederlands; alle Python codevoorbeelden zijn in het Engels.
Doel van dit hoofdstuk:
- Begrijpen wat een
ndarrayis en waarom het de kern vormt van wetenschappelijk rekenen in Python. - Leren hoe je arrays maakt, inspecteert en transformeert.
- Inzicht krijgen in vectorisatie en waarom dit meestal sneller en leesbaarder is dan Python‑loops.
- Basisprincipes van dtypes, geheugenlayout, views vs. copies en eenvoudige broadcasting.
Aanbevolen voorkennis: Python‑basis (variabelen, functies, lists), elementaire wiskunde.
6.1 Wat is een NumPy‑array (ndarray)?
Een NumPy‑array (numpy.ndarray) is een homogene, N‑dimensionale container voor numerieke data. "Homogeen" betekent dat alle elementen hetzelfde datatypedelen (bijv. float64 of int32). Arrays ondersteunen snelle vector‑ en matrixoperaties dankzij continu geheugen en C/Fortran‑geoptimaliseerde implementaties.
Voorbeeld van een 1D‑array (vector) en 2D‑array (matrix):
import numpy as np
# 1D array
a = np.array([1, 2, 3, 4])
# 2D array (matrix)
B = np.array([[1, 2, 3],
[4, 5, 6]])
print(a.ndim) # 1
print(B.shape) # (2, 3)
print(B.dtype) # e.g., int64 (platform dependent)
Belangrijke attributen:
ndim: aantal dimensies (assen)shape: tuple met de lengte per assize: totaal aantal elementen (np.prod(shape))dtype: data type van de elementenitemsize: bytes per element
6.2 Arrays aanmaken
Er zijn veel manieren om arrays te maken.
6.2.1 Vanuit Python‑iterables
import numpy as np
np.array([1, 2, 3]) # from list
np.array((1.0, 2.5, 3.14)) # from tuple (dtype inferred as float)
# specify dtype
np.array([1, 2, 3], dtype=np.float32)
6.2.2 Reeks‑generatoren
np.arange(0, 10, 2) # [0, 2, 4, 6, 8]
np.linspace(0, 1, 5) # 5 points between 0 and 1 inclusive
np.geomspace(1, 1000, 4) # [1., 10., 100., 1000.]
6.2.3 Vul‑ en identiteitsfuncties
np.zeros((2, 3)) # 2x3 zeros (float64 by default)
np.ones((3, 3), dtype=int)
np.full((2, 2), fill_value=7)
np.empty((1000,)) # uninitialized memory (fast, contents arbitrary)
np.eye(3) # 3x3 identity matrix
6.2.4 Willekeurige waarden
Let op: API’s verschillen tussen np.random (legacy) en np.random.default_rng (aanbevolen).
# Recommended modern RNG
rng = np.random.default_rng(seed=42)
rng.random((2, 3)) # uniform [0,1)
rng.normal(loc=0, scale=1, size=(2, 2))
rng.integers(low=0, high=10, size=5)
6.3 Dtypes en conversies
Het dtype bepaalt precisie, bereik, en geheugenverbruik.
Veelgebruikte dtypes: np.int32, np.int64, np.float32, np.float64, np.bool_, np.complex128.
x = np.array([1, 2, 3], dtype=np.int32)
x_float = x.astype(np.float64) # copy with cast
# upcasting rules in mixed operations
print((x + 0.5).dtype) # float64
Kies het kleinste dtype dat voldoet aan je nauwkeurigheidseisen om geheugen en tijd te besparen (bijv. float32 i.p.v. float64 voor grote modellen als de precisie voldoende is).
6.4 Basisbewerkingen en vectorisatie
Vectorisatie betekent opereren op hele arrays zonder expliciete Python‑loops. Dit is vaak sneller en duidelijker.
# Elementwise operations
x = np.array([1, 2, 3])
y = np.array([10, 20, 30])
print(x + y) # [11 22 33]
print(x * 10) # [10 20 30]
print(np.sqrt(x)) # [1. 1.41421356 1.73205081]
Vergelijk met een Python‑loop:
# Pure Python (slower and more verbose)
xs = [1, 2, 3]
ys = [10, 20, 30]
res = [xi + yi for xi, yi in zip(xs, ys)] # vectorized feel but still Python loop
Veel NumPy‑functies zijn ufuncs (universele functies) die elementgewijs werken en vaak C‑geoptimaliseerd zijn. In Hoofdstuk 8 bespreken we ufuncs en aggregaties uitgebreider.
6.5 Vorm (shape), herstructureren en dimensies
Basisinspectie en reshaping:
A = np.arange(12) # shape (12,)
A2 = A.reshape(3, 4) # shape (3, 4)
A3 = A.reshape(2, 2, 3) # shape (2, 2, 3)
print(A2.ndim) # 2
print(A3.shape) # (2, 2, 3)
Gebruik -1 als flexibele dimensie:
B = np.arange(24).reshape(2, -1, 3) # infer the middle dimension -> (2, 4, 3)
Rijen‑ versus kolom‑major opslaan:
- C‑order (row‑major, default): laatste index verandert het snelst.
- Fortran‑order (column‑major): eerste index verandert het snelst.
C = np.asfortranarray(np.arange(6).reshape(2, 3))
print(C.flags['F_CONTIGUOUS']) # True
6.6 Broadcasting (basis)
Broadcasting maakt bewerkingen tussen arrays met verschillende shapes mogelijk door impliciete uitlijning van dimensies.
Regels (intuïtief):
- Vergelijk shapes van rechts naar links.
- Dimensies zijn compatibel als ze gelijk zijn of als één van beide 1 is.
- Het resultaat heeft de "uitgebreide" shape.
Voorbeelden:
x = np.array([1, 2, 3]) # shape (3,)
M = np.array([[10], [20], [30]]) # shape (3,1)
print(M + x) # result shape (3,3): rows [10,20,30] plus x
# Add a scalar to all elements
print(M + 5)
Meer geavanceerde cases en expliciete newaxes komen in Hoofdstuk 7 aan bod.
6.7 Views vs. copies en geheugen
Veel operaties in NumPy maken een view: een nieuwe "kijk" op hetzelfde blok geheugen zonder data te kopiëren. Kopieën kosten tijd en geheugen.
A = np.arange(6)
B = A.reshape(2, 3) # view (no copy)
B[0, 0] = 99
print(A) # [99 1 2 3 4 5] <- changed!
# Force a real copy
C = A.copy()
C[0] = -1
print(A[0], C[0]) # 99 -1
Niet alle bewerkingen leveren views; sommige slicings wel, fancy indexing niet. In Hoofdstuk 7 werken we dit uit.
6.8 Boolean masks (kort intro)
Boolean arrays kunnen gebruikt worden om te filteren of te selecteren.
x = np.array([3, -1, 0, 5, 2])
mask = x > 0
print(mask) # [ True False False True True]
print(x[mask]) # [3 5 2]
Diepere behandeling van masking en geavanceerde indexing volgt in Hoofdstuk 12.
6.9 Prestaties: waarom vectorisatie werkt
- Minder Python‑overhead: ufuncs en vectoroperaties zijn in C geschreven.
- Betere cache‑lokaliteit door contigu geheugen.
- SIMD‑instructies en BLAS‑aanroepen onder water.
Snelle tips:
- Vermijd Python‑loops over elementen; gebruik ufuncs en aggregaties.
- Kies een passend
dtype(bijv.float32als precisie toereikend is). - Gebruik in‑place operaties waar mogelijk:
x *= 2i.p.v.x = x * 2. - Overweeg
np.ascontiguousarrayofnp.asfortranarraywanneer bibliotheken layout vereisen.
6.10 Veelgemaakte valkuilen
- Onbedoelde kopieën bij fancy indexing of typecasts (
astypemaakt een kopie) — let op geheugen. - Meng geen Python lists en arrays in vectoroperaties: cast eerst naar
np.array. - Verwarring over shapes met 1‑dimensionele arrays vs. kolom/rij‑vectoren; gebruik
np.newaxisofreshape.
6.11 Korte oefeningen
- Maak een array van 0 t/m 99, reshape naar (20, 5) en normaliseer elke kolom naar gemiddeld 0 en std 1, zonder expliciete Python‑loops.
- Simuleer 1000 dobbelsteenworpen (waarden 1–6) met de moderne RNG en bereken de relatieve frequenties per uitkomst.
- Laat met een klein voorbeeld zien dat een view de onderliggende data deelt, en vergelijk met een echte kopie.
Hint voor 1): gebruik ufuncs en broadcasting; voor 2): rng.integers en np.bincount; voor 3): reshape, copy en toewijzing.
Werkboek en uitwerkingen:
- Oefeningen bij dit hoofdstuk vind je in
../workbook/hoofdstuk-06/oefeningen.md. - Uitwerkingen staan in
../solutions/hoofdstuk-06/uitwerkingen.md.
Vervolg:
- Hoofdstuk 7: indexeren, slicen, reshapen en broadcasting in detail.
- Hoofdstuk 8: ufuncs, aggregaties en NaN‑behandeling.
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.
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.
Hoofdstuk 9 — Lineaire algebra, random number generation en numerieke stabiliteit
Let op: De tekst is in het Nederlands; alle Python codevoorbeelden zijn in het Engels.
Doel van dit hoofdstuk:
- Krachtig werken met lineaire algebra in NumPy: vectoren, matrices, products (
@,matmul,einsum). - Lineaire systemen oplossen, least squares, rangen, condities en de pseudo-inverse.
- Overzicht van decomposities (SVD, eigenwaarden/-vectoren) en hun toepassingen.
- Moderne random number generation (RNG) met
numpy.random.Generatorvoor reproduceerbare experimenten. - Numerieke stabiliteit: conditionering, schaalproblemen, stabiele patronen en valkuilen.
Aanbevolen voorkennis: Hoofdstuk 6–8 (arrays, indexeren/broadcasting, ufuncs/aggregaties), basis lineaire algebra.
9.1 Vectors en matrices in NumPy
NumPy vertegenwoordigt vectoren en matrices als ndarray’s. Een 1D-array is een vector; 2D is een matrix.
import numpy as np
x = np.array([1.0, 2.0, 3.0]) # vector (3,)
A = np.array([[1.0, 2.0],
[3.0, 4.0],
[5.0, 6.0]]) # matrix (3, 2)
print(x.shape) # (3,)
print(A.shape) # (3, 2)
Kolom- en rijvectoren expliciteren met None/np.newaxis (zie H6–H7):
col = x[:, None] # (3, 1)
row = x[None, :] # (1, 3)
9.2 Matrix- en vectorproducten: @, matmul, einsum
@ennp.matmulvoeren matrixvermenigvuldiging uit (geen elementgewijs product).np.dotis voor 1D gelijk aan inwendig product; voor 2D komt het overeen metmatmul.np.einsumgeeft expliciete controle over indexcontracties (leesbaar bij complexe patronen).
u = np.array([1.0, 2.0, 3.0]) # (3,)
B = np.array([[1.0, 0.0, 2.0],
[0.0, 1.0, 3.0]]) # (2, 3)
# matrix-vector
y = B @ u # shape (2,)
# matrix-matrix
C = np.array([[1.0, 2.0], [3.0, 4.0]]) # (2,2)
D = B @ C # (2,3) @ (2,2) -> ValueError (incompatible)
# Correct: (3,2) @ (2,2)
E = B.T @ C # shape (3, 2) @ (2, 2) -> (3, 2)
# einsum voorbeelden
# inwendig product van u met zichzelf
ip = np.einsum('i,i->', u, u) # scalar
# batch matrix-vector: (n,m) x (m,) voor meerdere matrices/vektoren
M = np.arange(12.0).reshape(3, 4)
w = np.arange(4.0)
res = M @ w # (3,)
Tip: controleer shapes expliciet om dimensionele fouten te voorkomen.
9.3 Normen, lengtes en projecties
- Vectornormen:
np.linalg.norm(x, ord=2)(Euclidisch);ord=1,ord=np.infvoor L1/L∞. - Matrixnormen: ook via
np.linalg.norm; let op definitie (induced norms). - Normaliseren (unit vector) en projecties.
x = np.array([3.0, 4.0])
length = np.linalg.norm(x) # 5.0
unit = x / length # [0.6, 0.8]
# Projecteer vector v op richting u (niet-nul)
u = np.array([1.0, 2.0, 2.0])
v = np.array([2.0, 1.0, 0.0])
proj_v_on_u = (v @ u) / (u @ u) * u
Orthogonaliteit: u @ v == 0 (binnen numerieke tolerantie) betekent loodrecht.
9.4 Lineaire systemen: solve, lstsq, pinv
Los systemen Ax = b op met stabiele routines; vermijd handmatig inverteren.
A = np.array([[3.0, 2.0],
[1.0, 2.0]])
b = np.array([5.0, 5.0])
# Exact vierkant en niet-singulier
x = np.linalg.solve(A, b)
# Over- of onderbepaald: least squares
M = np.array([[1.0, 1.0],
[1.0, 2.0],
[1.0, 3.0]])
y = np.array([1.0, 2.0, 2.5])
coef, resid, rank, s = np.linalg.lstsq(M, y, rcond=None)
# Pseudo-inverse (Moore-Penrose)
M_pinv = np.linalg.pinv(M) # gebaseerd op SVD
coef2 = M_pinv @ y
Belangrijk:
- Gebruik
solve/lstsqin plaats vaninv(A) @ b. Inversen zijn duurder en numeriek minder stabiel. - Controleer
rankens(singuliere waarden) om conditionering te beoordelen.
9.5 Rang, determinant, condities, condition number
- Rang (
np.linalg.matrix_rank(A)) geeft het aantal lineair onafhankelijke rijen/kolommen. - Determinant (
np.linalg.det(A)) vertelt over singulariteit maar is gevoelig voor schaal. - Condition number (
np.linalg.cond(A)) meet gevoeligheid; hogere waarden impliceren instabiliteit.
A = np.array([[1.0, 2.0], [2.0, 4.000001]])
print(np.linalg.matrix_rank(A))
print(np.linalg.det(A))
print(np.linalg.cond(A))
Regels:
- Slecht geconditioneerde systemen vergroten afrondingsfouten; her-schaal kolommen/rijen of gebruik regularisatie.
- Inspecteer
cond(A)en pas model/algoritme aan bij grote waarden.
9.6 Eigenwaarden/-vectoren en SVD (overzicht)
- Eigen-decompositie:
A v = λ vvoor vierkante matrices. - SVD:
A = U Σ Vᵀwerkt voor rechthoekige matrices en is robuust; nuttig voor PCA, pseudo-inversen en rangen.
A = np.array([[2.0, 0.0], [0.0, 1.0]])
# Eigenwaarden/-vectoren
w, V = np.linalg.eig(A)
# SVD
U, S, VT = np.linalg.svd(A, full_matrices=False)
# Reconstructie
A_rec = (U * S) @ VT
Opmerkingen:
- Voor symmetrische (Hermitische) matrices is
np.linalg.eighnumeriek stabieler daneig. - SVD levert singuliere waarden in aflopende volgorde; kleine waarden wijzen op bijna-singulariteit.
9.7 Geen inverses bouwen: solve/cho_solve
Als A symmetrisch positief-definiet (SPD) is, gebruik Cholesky-factorisatie.
# SPD voorbeeld
S = np.array([[4.0, 2.0], [2.0, 3.0]])
L = np.linalg.cholesky(S) # S = L @ L.T
# los Sx=b op via forward/back substitution
b = np.array([1.0, 2.0])
# Gebruik solve met triangular matrices via scipy.linalg (indien beschikbaar) of terugvallen op np.linalg.solve
x = np.linalg.solve(S, b)
Richtlijnen:
- Vermijd
inv(A). Gebruik specifieke oplossers. - Hergebruik factorisaties waar mogelijk.
9.8 Moderne RNG: numpy.random.Generator
Gebruik de nieuwe API voor reproduceerbare en onafhankelijke streams.
rng = np.random.default_rng(seed=42) # PCG64 als default
# Distributies
u = rng.random(5) # Uniform [0,1)
z = rng.standard_normal(5) # N(0,1)
k = rng.integers(low=0, high=10, size=5)
# Multivariate normaal
mean = np.array([0.0, 0.0])
cov = np.array([[1.0, 0.8], [0.8, 2.0]]) # symmetrisch PSD
samples = rng.multivariate_normal(mean, cov, size=1000, check_valid='warn')
# Permutaties
arr = np.arange(10)
rng.shuffle(arr) # in-place
p = rng.permutation(5) # nieuw array
Best practices:
- Gebruik één
Generatorper task/worker; vermijd globalenp.randomstate. - Zaai (
seed) expliciet voor reproduceerbaarheid; gebruikSeedSequencevoor hiërarchische seeds in parallelle settings.
9.9 Numerieke stabiliteit in de praktijk
- Schaal feature-matrices (kolomgewijs centreren/standaardiseren) om conditionering te verbeteren.
- Gebruik stabiele vormen:
logsumexpin plaats van naïevenp.exp+sum+log. - Kies geschikte dtypes (
float64standaard;float32voor snelheid/geheugen, maar controleer fouten). - Vermijd subtractie van bijna gelijke getallen (catastrofale annulatie).
# Softmax (stabiel 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)
# Log-sum-exp truc
logsumexp = Xmax[:, 0] + np.log(exp.sum(axis=1))
Conditionering illustratie:
A = np.array([[1.0, 1.0], [1.0, 1.000001]])
b = np.array([2.0, 2.000001])
sol = np.linalg.solve(A, b)
print('cond(A)=', np.linalg.cond(A), ' solution=', sol)
9.10 Prestaties en geheugen
- NumPy gebruikt BLAS/LAPACK (via OpenBLAS/MKL) voor
@,svd,eig, etc. - Zorg voor contigue arrays met juiste dtype (
float64offloat32) voor maximale performance. - Voorkom enorme tussenresultaten: gebruik
@/einsumi.p.v.broadcast+sumwanneer het eigenlijk een matrixproduct is. - Batched operaties:
@enmatmulondersteunen broadcasting over leading dims (3D+).
# Batched matmul: (batch,n,m) @ (batch,m,p) -> (batch,n,p)
A = np.random.default_rng(0).random((10, 3, 4))
B = np.random.default_rng(1).random((10, 4, 5))
C = A @ B
assert C.shape == (10, 3, 5)
9.11 Veelvoorkomende patronen en valkuilen
- Z-standaardisatie:
Z = (X - X.mean(axis=0)) / X.std(axis=0, ddof=0). - Regressie met
lstsqi.p.v.inv(X.T@X) @ X.T @ y. - Projectiematrix met orthonormale kolommen
Q:P = Q @ Q.T(symmetrisch idempotent). - Controleer symmetrie (
np.allclose(A, A.T)) voordat jeeighgebruikt.
9.12 Korte oefeningen
- Geef
A(shape (3,3)) enb(shape (3,)). LosAx=bop metnp.linalg.solveen verifieer door terug te vermenigvuldigen. - Maak een matrix
X(shape (n,d)) en bereken de covariantiematrixS = X_centered.T @ X_centered / (n-1). Gebruiknp.linalg.eigofnp.linalg.svdom de hoofdrichtingen te vinden. - Demonstreer het verschil tussen
inv(A) @ bensolve(A, b)qua snelheid/nauwkeurigheid op een willekeurige SPD matrix. - Gebruik de moderne RNG om 1000 samples uit
N(0, I)in 2D te trekken en schat de empirische covariantie; vergelijk met de theoretische. - Laat met een slecht geconditioneerde matrix zien dat kleine verstoringen in
bleiden tot grote verschillen inx.
Werkboek en uitwerkingen:
- Oefeningen bij dit hoofdstuk vind je in
../workbook/hoofdstuk-09/oefeningen.md. - Uitwerkingen staan in
../solutions/hoofdstuk-09/uitwerkingen.md.
Verder lezen en vervolg:
- Hoofdstuk 8: numerieke stabiliteit van ufuncs en aggregaties.
- Hoofdstuk 10: prestaties en geheugenbeheer met NumPy.
- Appendix/verdere bronnen: LAPACK/BLAS, Golub & Van Loan (Matrix Computations).
Hoofdstuk 10 — Prestaties en geheugenbeheer met NumPy
Let op: De tekst is in het Nederlands; alle Python codevoorbeelden zijn in het Engels.
Doel van dit hoofdstuk:
- Begrijpen waar NumPy-snelheid vandaan komt en hoe je die benut.
- Leren hoe geheugenlayout, dtypes, views/kopieën en broadcasting de performance en het geheugengebruik beïnvloeden.
- Praktische strategieën voor minder allocaties, betere cache-lokaliteit en controle over multithreading.
- Werken met grote data (memory mapping, chunking) en tips om out-of-memory te vermijden.
Aanbevolen voorkennis: Hoofdstuk 6–9 (arrays, indexeren/broadcasting, ufuncs/aggregaties, lineaire algebra en numerieke stabiliteit).
10.1 Waarom is NumPy snel?
- Vectorisatie: operaties zijn geïmplementeerd in C (ufuncs/BLAS), vermijden Python-loop overhead.
- Contigu geheugen en eenvoudige dtypes bevorderen cache-hits en SIMD-vectorisatie.
- Aanroepen naar geoptimaliseerde bibliotheken (BLAS/LAPACK) voor lineaire algebra.
Vergelijking loop vs vectorisatie:
import numpy as np
from time import perf_counter
n = 2_000_000
x = np.random.default_rng(0).random(n)
y = np.random.default_rng(1).random(n)
# Python loop (for illustration; avoid in production)
start = perf_counter()
res_py = [xi + yi for xi, yi in zip(x, y)]
t_loop = perf_counter() - start
# Vectorized
start = perf_counter()
res_np = x + y
t_vec = perf_counter() - start
len(res_py), np.allclose(res_np, np.array(res_py)), t_loop, t_vec
10.2 Dtypes, alignement en geheugenvoetafdruk
- Kies het kleinste dtype dat nauwkeurig genoeg is (bijv. float32 i.p.v. float64) om geheugen en bandbreedte te besparen.
itemsizeensizebepalen de bytes:bytes = size * itemsize.- Integers kunnen over-/onderlopen; floats hebben precisie/NaN/Inf.
import numpy as np
n = 10_000_000
x32 = np.empty(n, dtype=np.float32)
x64 = np.empty(n, dtype=np.float64)
mb32 = x32.nbytes / (1024**2)
mb64 = x64.nbytes / (1024**2)
mb32, mb64
10.3 Views vs. kopieën: kostprijs en detectie
- Views zijn gratis in geheugen (gedeelde data) en snel te maken; kopieën kosten O(n) tijd en geheugen.
- Fancy indexing (integer/boolean) maakt altijd een kopie; slicing meestal een view.
- Detecteer met
np.shares_memory(a, b)en inspecteera.flagsvoor contiguïteit.
import numpy as np
A = np.arange(12).reshape(3, 4)
B = A[:, 1:3] # view
C = A[:, [1, 2]] # copy (fancy indexing)
np.shares_memory(A, B), np.shares_memory(A, C)
10.4 Contiguïteit, strides en order (C vs Fortran)
- C-order: laatste index varieert het snelst (standaard). Fortran-order: eerste index varieert het snelst.
- Contigue arrays doorlopen in geheugenvolgorde bevordert cache-lokaliteit.
- Converteer layout indien nodig voor bibliotheken:
np.ascontiguousarray,np.asfortranarray.
import numpy as np
A = np.arange(12).reshape(3, 4) # C-order by default
AF = np.asfortranarray(A)
A.flags['C_CONTIGUOUS'], AF.flags['F_CONTIGUOUS'], A.strides, AF.strides
10.5 Broadcasting: krachtig maar let op explosies
- Broadcasting maakt tijdelijk grotere logische shapes; het resultaat wordt niet gematerialiseerd, maar sommige combinaties creëren enorme tijdelijke arrays bij vervolgbewerkingen.
- Tip: reken in de juiste volgorde en reduceer vroeg (bijv. som over de gebroadcastte as binnen dezelfde uitdrukking).
import numpy as np
X = np.random.default_rng(0).random((10_000, 128)) # (N, d)
w = np.random.default_rng(1).random(128) # (d,)
# Goed: reduceer binnen de uitdrukking
s = np.sum(X * w, axis=1) # (N,)
Valkuil: onbedoelde broadcast naar (N, N) voor paargewijze afstanden; gebruik efficiëntere vormen (@, einsum) of chunking.
10.6 Minder allocaties: in-place en out=
- Gebruik in-place operators (
+=,-=,*=,np.add(..., out=...)) om tijdelijke arrays te vermijden. - Let op dtype-compatibiliteit; anders forceert NumPy een kopie.
import numpy as np
x = np.arange(5, dtype=np.float64)
np.add(x, 1.0, out=x) # in-place add
# Voorwaardelijk schrijven zonder extra allocaties
y = np.empty_like(x)
np.sqrt(x, out=y)
np.sqrt(x, out=y, where=(x > 0))
y
10.7 Operationele fusie en ufunc-idiomen
- Combineer ufuncs zodat NumPy intern minder tussenresultaten aanmaakt (niet alle ufuncs fuseren automatisch, maar
out=enwhere=helpen). - Gebruik ufunc-methoden (
reduce,accumulate,outer) en aggregatieparameters (axis,keepdims) om werk te beperken.
import numpy as np
A = np.random.default_rng(0).standard_normal((1000, 1000))
# Minder allocaties: centreer en schaal in twee passes maar zonder extra kopieën
col_mean = A.mean(axis=0)
A -= col_mean
col_std = A.std(axis=0, ddof=0)
A /= col_std
10.8 BLAS/LAPACK en threading
- Matrixvermenigvuldiging en decomposities gebruiken BLAS/LAPACK (OpenBLAS, MKL, Accelerate).
- Multithreading wordt door de backend geregeld; te veel threads kan tot oversubscripie leiden bij nested parallelisme.
- Omgevingsvariabelen (bij processtart instellen):
OPENBLAS_NUM_THREADS,MKL_NUM_THREADS,OMP_NUM_THREADS.
import numpy as np
np.show_config() # laat zien welke BLAS/LAPACK eronder zit
Tip: in multiprocessing of binnen joblib/tqdm: beperk BLAS-threads per proces om thrash te vermijden.
10.9 NumExpr/Numba/Cython (wanneer verder gaan)
np.vectorizeis geen performance-tool (het is syntactic sugar over Python-loops). Vermijd voor snelheid.numexprkan grote expresies sneller uitvoeren door chunking en multi-threading op cache-vriendelijke blokken.NumbaJIT-compileert Python naar machinecode; nuttig voor custom kernels die niet goed uit te drukken zijn met ufuncs/broadcasting.Cythonvoor nog fijnmaziger controle; meer setup.
Doel van dit boek: eerst optimaal NumPy gebruiken; pas daarna JIT/extern als nodig.
10.10 Geheugenmapping voor grote datasets (np.memmap)
- Memory-mapped bestanden laten je arrays gebruiken die groter dan RAM kunnen zijn door paginagebaseerd IO.
- Werkt goed voor sequentiële/geslicede toegang; random access blijft schijfgebonden.
import numpy as np
# Create a memmap file
filename = 'data.bin'
N = 10_000_000
mm = np.memmap(filename, dtype='float32', mode='w+', shape=(N,))
mm[:100] = np.arange(100, dtype='float32')
mm.flush()
# Reopen read-only
mm_ro = np.memmap(filename, dtype='float32', mode='r', shape=(N,))
mm_ro[10:15]
Tips:
- Gebruik eenvoudige dtypes en contigue layouts.
- Sluit/flush (of laat GC doen) voor consistentie.
10.11 Chunking en streaming
- Verwerk grote arrays in stukken om piekgeheugen te beperken.
- Kies chunkgrootte die in cache/L3 past of afgestemd is op IO-doorvoer.
import numpy as np
rng = np.random.default_rng(0)
X = rng.random((1_000_000, 64), dtype=np.float64)
w = rng.random(64)
def chunked_dot(X, w, chunk=50_000):
out = np.empty(X.shape[0], dtype=X.dtype)
i = 0
while i < X.shape[0]:
j = min(i + chunk, X.shape[0])
out[i:j] = X[i:j] @ w
i = j
return out
res = chunked_dot(X, w)
res.shape
10.12 Cache-lokaliteit en tiling
- Door iteratie in geheugenvolgorde te houden en blokken (tiles) te gebruiken, verbeter je cache-hits.
- Voor pure NumPy is dit vooral relevant bij eigen implementaties of bij het kiezen van C- vs Fortran-order.
import numpy as np
A = np.asfortranarray(np.arange(12).reshape(3, 4))
# Kolomoperaties kunnen dan cache-vriendelijker zijn
col_means = A.mean(axis=0)
A.flags['F_CONTIGUOUS'], col_means.shape
10.13 Profileren en meten
- Micro-benchmarking: IPython/Jupyter
%timeit; oftime.perf_counter. - Inspecteer NumPy/BLAS-config:
np.__version__,np.show_config(). - Memory-profielen:
tracemalloc,memory_profiler(extern),array.nbytesengc-inzichten.
import numpy as np
import sys
A = np.empty((2000, 2000), dtype=np.float64)
A.nbytes / (1024**2), sys.getsizeof(A)
10.14 Veelvoorkomende patronen en checklist
- Vermijd Python-elementloops; gebruik ufuncs/aggregaties en
@voor algebra. - Controleer shapes vroeg; voorkom onbedoelde (N, N) constructies.
- Minimaliseer allocaties met in-place en
out=; hergebruik buffers. - Kies passende dtypes; overweeg float32 bij bandbreedte-gebonden workloads.
- Zorg voor contigu arrays als bibliotheken dat vereisen; converteer expliciet.
- Beperk BLAS-threads om oversubscriptie te voorkomen in parallelle workloads.
- Voor grote data: memmap, chunking en streaming.
10.15 Korte oefeningen
- Meet het verschil in tijd tussen een Python-loop en een vectorized optelling op 5 miljoen elementen. Gebruik
perf_counteren rapporteer de ratio. - Maak een grote array
(5_000_000,)infloat32enfloat64. Bereken de geheugenvoetafdruk in MB en bespreek een scenario waarinfloat32volstaat. - Toon met een klein voorbeeld dat fancy indexing een kopie maakt en slicing een view. Gebruik
np.shares_memoryen verander een element. - Implementeer een chunked dot-product
y = X @ wvoorXvan shape(1_000_000, 128)en vergelijk de piekgeheugenbehoefte met de naïeve broadcastnp.sum(X*w, axis=1). - Gebruik
np.memmapom een bestand van 1e7 float32’s aan te maken, vul de eerste 100 waarden, sluit en heropen, en verifieer de inhoud. - Profileringsopdracht: gebruik
%timeitofperf_counterom de impact vanOPENBLAS_NUM_THREADS=1vs standaard opA @ B(vorm(2000,2000)) te vergelijken. (Beschrijf hoe je de omgeving zou instellen.)
Werkboek en uitwerkingen:
- Oefeningen bij dit hoofdstuk kunnen later toegevoegd worden in
../workbook/hoofdstuk-10/oefeningen.md. - Uitwerkingen kunnen later toegevoegd worden in
../solutions/hoofdstuk-10/uitwerkingen.md.
Verder lezen en vervolg:
- Hoofdstuk 6: basis over arrays, dtypes en geheugen.
- Hoofdstuk 7: broadcasting en vormmanipulatie (prestaties/vallenkuilen).
- Hoofdstuk 8: ufuncs,
out=,whereen aggregaties (allocaties beperken). - Hoofdstuk 9: BLAS/SVD en numerieke stabiliteit.
- Hoofdstuk 11: interoperabiliteit en memory-views voor zero-copy patronen.
Hoofdstuk 11 — Interoperabiliteit: NumPy met Python collections, buffers/memory‑views en PyTorch tensors
Let op: De tekst is in het Nederlands; alle Python codevoorbeelden zijn in het Engels.
Doel van dit hoofdstuk:
- Begrijpen hoe NumPy samenwerkt met standaard Python‑collecties (list/tuple),
arraymodule encollections.abcsequenties. - Zero‑copy interoperabiliteit via buffer‑protocol en memory‑views (
memoryview,np.frombuffer,ndarray.view) benutten. - Efficiënt data uitwisselen met PyTorch tensors in beide richtingen, inclusief device/dtype/contiguïteit en zero‑copy waar mogelijk.
- Praktische patronen, valkuilen, performance‑implicaties en testbare code‑snippets.
Aanbevolen voorkennis: Hoofdstuk 6–10 (arrays, dtypes, views/kopieën, broadcasting, prestaties), basiskennis van PyTorch is nuttig maar niet noodzakelijk.
11.1 Python‑collecties naar en van NumPy
Standaard Python‑collecties zijn heterogeen en niet geoptimaliseerd voor vectorisatie, maar NumPy kan er goed mee werken door te converteren naar homogene arrays.
import numpy as np
# list/tuple → ndarray (copy of data)
x_list = [1, 2, 3]
x_arr = np.array(x_list) # dtype inferred (usually int64 or int32)
y_tuple = (1.0, 2.5, 3.14)
y_arr = np.array(y_tuple, dtype=np.float32) # explicit dtype cast
# ndarray → Python list (copy)
back_to_list = x_arr.tolist()
Opmerkingen:
np.array(obj)maakt doorgaans een kopie; voor nested lists zal NumPy proberen te inferren naar 2D/ND shape. Inconsistente lengtes gevendtype=object— ongunstig voor rekenen..tolist()maakt altijd een kopie en kan langzaam zijn op grote arrays; gebruik alleen voor IO of weergave.
11.1.1 De array module en collections.abc sequenties
De standaardbibliotheek heeft array('f'), array('d'), array('i'), etc. Deze ondersteunen het buffer‑protocol, waardoor zero‑copy uitwisseling met NumPy mogelijk is.
import numpy as np
from array import array
arr_mod = array('d', [1.0, 2.0, 3.0]) # double precision
# Zero-copy view of the same memory (shape (3,))
view_np = np.frombuffer(arr_mod, dtype=np.float64)
view_np[0] = 99.0
assert arr_mod[0] == 99.0 # shares memory!
# Opposite direction: create array('d') from ndarray bytes (copy)
x = np.array([10.0, 20.0], dtype=np.float64)
arr_copy = array('d', x) # this copies the data
collections.abc beschrijft sequentie‑interfaces; NumPy accepteert elk object dat zich als een sequentie gedraagt (iterable van elementaire waarden) voor np.array of ufuncs die Python iterables nemen, maar dat leidt vrijwel altijd tot een kopie.
11.2 Buffer‑protocol en memory‑views
Voor performance‑kritische interoperabiliteit is het buffer‑protocol essentieel. Het stelt Python‑objecten in staat hun ruwe geheugen te delen zonder kopiëren.
Belangrijkste bouwstenen:
memoryview(obj): generieke manier om een buffer vanobjte bekijken.np.frombuffer(buffer, dtype, count, offset): maak een 1D‑array die het buffer deelt (zero‑copy) — let opdtypeen uitlijning.ndarray.tobytes(),ndarray.data,ndarray.view(new_dtype): lage‑niveau toegang/manipulatie.
import numpy as np
# bytes/bytearray → NumPy zonder kopie (view)
buf = bytearray(b"\x00\x00\x80?\x00\x00\x00@") # two float32: 1.0, 2.0
mv = memoryview(buf)
a = np.frombuffer(mv, dtype=np.float32)
assert np.shares_memory(a, np.frombuffer(buf, dtype=np.float32))
# dtype‑re‑interpretatie met .view (geen byte‑kopie, wel herinterpretatie)
x = np.arange(4, dtype=np.uint8)
y = x.view(np.int32) # requires correct length and alignment; platform dependent
Valkuilen en richtlijnen:
- Zero‑copy werkt alleen bij compatibele dtypes, endianness en uitlijning; anders moet je kopiëren (
.astype(new_dtype, copy=True)). - Veel buffer‑bronnen zijn 1D; voor multi‑dimensies moet je
np.ndarray(shape, dtype, buffer=..., strides=...)ofa = np.frombuffer(...).reshape(...)gebruiken. - Gebruik
np.shares_memory(a, b)om te controleren of je echt geheugen deelt.
11.3 Memory‑views van NumPy naar ander Python‑code
Je kunt een NumPy‑array als buffer aanbieden aan andere libraries, of direct via memoryview gebruiken.
import numpy as np
A = np.arange(12, dtype=np.int16).reshape(3, 4)
# Expose a read‑only memoryview of A's data (C‑order assumed)
mv = memoryview(A)
# mv can be passed to any buffer‑consumer (e.g., file IO, struct unpacking)
Let op:
- Niet alle consumers begrijpen striding/ND‑vorm; vaak moet je een contigue view aanleveren (
np.ascontiguousarray(A)). - Negatieve strides (zoals slices met omkering) zijn vaak onverenigbaar met C‑extensions; maak dan een kopie of
ascontiguousarray.
11.4 PyTorch tensors ↔ NumPy arrays
PyTorch en NumPy zijn nauw verweven. Je kunt vaak zero‑copy views delen tussen CPU‑tensors en NumPy arrays. GPU‑tensors (CUDA/MPS) vereisen kopiëren naar host, en terug.
Voorbeelden (CPU):
import numpy as np
import torch
# NumPy → Torch (zero‑copy view when possible)
a_np = np.arange(6, dtype=np.float32).reshape(2, 3)
t_torch = torch.from_numpy(a_np) # shares memory if a_np is writable and contiguous
# Mutations reflect both ways
t_torch[0, 0] = -1.0
assert a_np[0, 0] == -1.0
# Torch → NumPy (CPU)
t2 = torch.tensor([[1, 2, 3], [4, 5, 6]], dtype=torch.float64)
a2 = t2.numpy() # shares memory if t2 is a CPU tensor
# If non‑contiguous, make it contiguous first
u = torch.arange(12, dtype=torch.float32).reshape(3, 4).t() # transpose -> non‑contiguous
u_contig = u.contiguous()
a_from_u = u_contig.numpy()
Belangrijk:
- Zero‑copy geldt alleen voor CPU‑tensors en NumPy arrays; op GPU moet je
.cpu()gebruiken (kopie):a_gpu = t_cuda.detach().cpu().numpy(). - Dtype‑mapping:
float32 ↔ torch.float32,float64 ↔ torch.float64,int64 ↔ torch.int64(PyTorch’s default int),bool ↔ torch.bool, etc. - Writability/ownership:
torch.from_numpyvereist dat de NumPy‑array writeable is; anders krijg je een tensor die mogelijk beperkingen heeft. Omgekeerd deelttensor.numpy()geheugen en respecteertrequires_grad=False. Voor autograd: gebruik.detach().numpy()om grad‑history te verbreken.
11.4.1 Device transfers en pinned memory
# CPU tensor to GPU, then back to NumPy
x_cpu = torch.arange(6, dtype=torch.float32).reshape(2, 3)
x_gpu = x_cpu.to('cuda') # requires CUDA‑capable setup
x_back = x_gpu.detach().cpu().numpy() # copy to host
Pinned memory kan host↔device transfers versnellen, maar vereist geavanceerde setup (pin_memory=True bij DataLoader, of tensor.pin_memory() op CPU‑buffers).
11.4.2 Non‑contiguïteit, strides en views
PyTorch en NumPy hebben beide strides; zero‑copy gedeelde views vereisen compatibele strides en contiguïteit. Na transposes/slices is .contiguous() of np.ascontiguousarray vaak nodig.
# Non‑contiguous example
w = torch.arange(12, dtype=torch.float32).reshape(3, 4)
wT = w.t() # transpose, non‑contiguous
# a direct wT.numpy() raises an error in older versions; in modern PyTorch it may copy
wT_c = wT.contiguous()
a = wT_c.numpy()
11.5 Seriële uitwisseling: DLPack en __array__ protocol
Voor breder ecosysteem‑interop bestaan standaarden:
__array__/__array_priority__: Python protocol waarmee objecten zich als NumPy‑array kunnen presenteren.np.asarray(obj)kan dan een view of kopie verkrijgen.- DLPack: een zero‑copy tensor overdrachtsformaat ondersteund door PyTorch, CuPy, JAX, TensorFlow e.a.
# PyTorch ↔ NumPy via __array__ protocol (NumPy side)
# Many torch tensors implement __array__ returning a NumPy view for CPU tensors.
# PyTorch ↔ andere frameworks via DLPack
import torch
# Torch to DLPack capsule
x = torch.arange(6, dtype=torch.float32)
capsule = torch.utils.dlpack.to_dlpack(x)
# Another framework (e.g., cupy) could import it zero‑copy on the same device
# cupy_arr = cupy.fromDlpack(capsule)
Opmerking: NumPy zelf consumeert DLPack niet standaard (v1.26); interop met GPU frameworks loopt dan via die frameworks of via kopiëren naar CPU.
11.6 Praktische patronen
- Data inlezen als Python list/iterable → éénmalig casten naar
np.arraymet gewenstdtypeen shape; daarna overal arrays gebruiken. - Gebruik het buffer‑protocol voor zero‑copy tussen
arraymodule,bytearray,memoryviewen NumPy. - Voor PyTorch training/inferentie op CPU: gebruik
torch.from_numpyom datasets/tensors te maken zonder kopie; zorg dat arrays contigu en van het juiste dtype zijn. - Voor GPU: verzamel in NumPy, zet om naar torch CPU tensor met
from_numpy, en roep.to('cuda')één keer vóór de training/inferentie. - Let op eigendom en levensduur: als een tensor/array een view is op andermans geheugen, houd dat bron‑object in scope.
11.7 Veelgemaakte valkuilen
- Onbedoelde kopieën bij
.astype,np.array(list)herhaald in een loop, oftensor.numpy()op GPU (maakt impliciet een CPU kopie via.cpu()). - Endianness/dtype mismatch bij
frombuffergeeft corrupte waarden; specificeerdtypecorrect. - Niet‑contigue layouts breken zero‑copy; materialiseer contigu.
- Schrijven naar gedeeld geheugen kan onverwachte bijwerkingen veroorzaken; gebruik
.copy()als isolatie gewenst is.
11.8 Korte oefeningen
- Maak een
array('d',[1,2,3])en construeer er een NumPy‑view bovenop metnp.frombuffer. Pas een element aan en toon dat het originelearray‑object mee verandert (zero‑copy). - Converteer een geneste Python‑list naar een 2D‑NumPy‑array met een expliciet dtype. Laat zien wat er gebeurt als de sub‑lijsten verschillende lengtes hebben.
- Maak een
bytearraymet twee little‑endian float32’s en lees ze in metnp.frombufferen de juistedtype. Reshape naar (1,2) zonder kopie en controleernp.shares_memory. - Zet een NumPy array
float32om naar een PyTorch CPU‑tensor mettorch.from_numpyen toon bidirectionele mutaties. Maak daarna een transpose in PyTorch en converteer naar NumPy; los contiguïteit op. - Demonstreer de roundtrip CPU→GPU→CPU met PyTorch (
.to('cuda')en.cpu()) en converteer terug naar NumPy, met aandacht voor waar kopieën plaatsvinden.
Werkboek en uitwerkingen:
- Oefeningen bij dit hoofdstuk kunnen later toegevoegd worden in
../workbook/hoofdstuk-11/oefeningen.md. - Uitwerkingen kunnen later toegevoegd worden in
../solutions/hoofdstuk-11/uitwerkingen.md.
Verder lezen en vervolg:
- Hoofdstuk 6–7: views/kopieën, contiguïteit en broadcasting.
- Hoofdstuk 10: prestaties en geheugenbeheer; zero‑copy patronen.
- PyTorch docs:
torch.from_numpy,Tensor.numpy, DLPack utils; Python docs: buffer protocol enmemoryview.
Hoofdstuk 13 — Introductie tot pandas: Series en DataFrame
Let op: De tekst is in het Nederlands; alle Python codevoorbeelden zijn in het Engels.
Doel van dit hoofdstuk:
- Begrijpen wat
pandasis en wanneer je het gebruikt ten opzichte van puur Python of NumPy. - Kennismaken met de twee kernobjecten:
SeriesenDataFrame. - Leren hoe je tabulaire data maakt, inspecteert, selecteert en bewerkt.
- Inzicht krijgen in indexen, uitlijning (alignment), dtypes en omgaan met missende waarden op basisniveau.
Aanbevolen voorkennis: Python‑basis, NumPy‑arrays (Hoofdstuk 6–11).
13.1 Wat is pandas en waarom?
pandas is een bibliotheek voor data‑analyse in Python, geoptimaliseerd voor gestructureerde (tabelvormige) data. Het biedt:
Series(1D gelabelde array) enDataFrame(2D tabel met rijen en kolommen).- Krachtige indexering, selectie, groepering, reshaping en I/O (CSV, Excel, Parquet, SQL).
- Vectorized operations met automatische uitlijning op labels (indexen).
Installatie (indien nodig):
pip install pandas
Basisimport:
import pandas as pd
import numpy as np
13.2 Series: een gelabelde 1D‑array
Een Series is vergelijkbaar met een 1D NumPy‑array, maar met labels (index) en rijkere functionaliteit.
13.2.1 Series maken
import pandas as pd
s = pd.Series([10, 20, 30], index=["a", "b", "c"], name="scores")
print(s)
# a 10
# b 20
# c 30
# Name: scores, dtype: int64
Belangrijke attributen: s.index, s.values, s.dtype, s.name.
Je kunt ook vanuit een dict maken (keys worden index):
data = {"apples": 3, "bananas": 5, "oranges": 2}
stock = pd.Series(data)
13.2.2 Selectie en slicing
s["a"] # label-based; returns scalar 10
s[["a", "c"]] # fancy indexing -> Series
s[0] # position-based; discouraged, gebruik .iloc
s.loc["a"] # label-based expliciet
s.iloc[0] # position-based expliciet
s["a":"c"] # label-slice inclusief eindlabel
s.iloc[0:2] # position-slice exclusief einde (zoals Python/NumPy)
13.2.3 Vectorbewerkingen en uitlijning
Operaties zijn elementgewijs en worden uitgelijnd op labels.
s1 = pd.Series([1, 2, 3], index=["a", "b", "c"])
s2 = pd.Series([10, 20, 30], index=["b", "c", "d"])
s1 + s2
# a NaN
# b 11.0
# c 23.0
# d NaN
# dtype: float64
Missende combinaties geven NaN. Gebruik fill_value met method chaining via .add e.d.:
(s1.add(s2, fill_value=0))
# a 1.0
# b 12.0
# c 33.0
# d 30.0
13.2.4 Basisstatistiek en methodes
s.mean(), s.median(), s.std()
s.min(), s.max()
s.isna(), s.notna()
s.fillna(0)
s.astype("float32")
13.3 DataFrame: een 2D tabel
Een DataFrame is een verzameling Series die kolommen vormen, met een gedeelde rij‑index.
13.3.1 DataFrame maken
Constructors vanuit dict, lijst van dicts, NumPy, of CSV/Parquet/…
import numpy as np
df = pd.DataFrame({
"name": ["Alice", "Bob", "Charlie"],
"age": [25, 32, 29],
"score": [88.5, 92.0, 79.0],
})
# Met aangepaste index
people = pd.DataFrame(
[[25, 88.5], [32, 92.0], [29, 79.0]],
index=["Alice", "Bob", "Charlie"],
columns=["age", "score"],
)
Veelgebruikte attributes en inspectie:
df.shape, df.dtypes, df.index, df.columns
df.head() # eerste 5 rijen
df.tail(3) # laatste 3 rijen
df.info() # kolomtypes, non-null counts
df.describe() # numerieke samenvatting
13.3.2 Selectie: kolommen en rijen
# Kolommen
ages = df["age"] # Series
subset = df[["name", "score"]] # DataFrame
# Rijen
first_row = df.iloc[0] # Series (row)
rows_1_3 = df.iloc[1:3] # DataFrame slice
row_bob = people.loc["Bob"] # via indexlabel
# Rijen + kolommen (2D selectie)
val = df.loc[0, "score"] # scalar
block = df.loc[0:1, ["name", "score"]]
Regels:
- Gebruik
.loc[rows, cols]voor labelgebaseerd,.iloc[rows, cols]voor positiegebaseerd. - Enkelvoudige kolomselectie
df["col"]geeft een Series; dubbele hakendf[["c1", "c2"]]geeft een DataFrame.
13.3.3 Filteren en booleaanse indexering
mask = (df["age"] >= 30) & (df["score"] > 90)
df[mask]
# Met loc, explicieter en uitbreidbaar
df.loc[(df["age"] >= 30) & (df["score"] > 90), ["name", "score"]]
13.3.4 Nieuwe kolommen en vectorbewerkingen
df["passed"] = df["score"] >= 85
# Vectorbewerkingen uitgelijnd op index
df["score_norm"] = (df["score"] - df["score"].mean()) / df["score"].std()
# Met NumPy ufuncs
df["age_log"] = np.log(df["age"]) # werkt elementgewijs
13.3.5 Dtypes en conversies
df.dtypes
# Conversie
df["age"] = df["age"].astype("int16")
df = df.astype({"score": "float32"})
# Categoricals
cats = pd.Series(["low", "med", "high"]).astype("category")
13.3.6 Missende waarden (NaN) basis
df2 = pd.DataFrame({"a": [1, np.nan, 3], "b": [np.nan, 5, 6]})
df2.isna() # booleaanse DataFrame
df2.dropna() # rijen met NaN weg
df2.fillna({"a": 0, "b": df2["b"].mean()}) # per kolom vulling
13.3.7 Sorteren en herindexeren
df.sort_values(by=["score", "age"], ascending=[False, True])
df.sort_index() # sorteer op rijindex
# Herindexeren
df_re = df.reindex([0, 1, 2, 3]) # voegt evt. NaN toe voor ontbrekende rijen
13.4 Indexfilosofie en uitlijning
- Indexen labelen rijen/kolommen en sturen selectie, join/merge, en alignment.
- Operaties tussen Series/DataFrames lijnen uit op indexlabels; ontbrekende labels →
NaN. - Reset/zet index waar nuttig:
df_reset = people.reset_index(names="person")
df_set = df_reset.set_index("person")
13.5 In‑/uitvoer (teaser)
Uitgebreide I/O volgt in Hoofdstuk 14. Kort voorbeeld:
# CSV lezen en schrijven
df = pd.read_csv("data.csv")
df.to_csv("out.csv", index=False)
# Parquet (vereist pyarrow of fastparquet)
# df.to_parquet("out.parquet")
13.6 Veelvoorkomende valkuilen en tips
- Gebruik
.locen.iloci.p.v.df[row][col](dat maakt onnodige kopieën en kan SettingWithCopyWarnings geven). - Let op automatische uitlijning; het is vaak gewenst, maar kan tot NaN leiden als labels niet overeenkomen.
- Werk zoveel mogelijk kolomgewijs (vectorized) in plaats van rij‑loops voor performance en duidelijkheid.
- Gebruik
df.info()endf.dtypesvroeg om types en missende waarden te begrijpen.
13.7 Samenvatting
Je leerde de basis van pandas met Series en DataFrame: maken, selecteren, filteren, berekenen, en omgaan met missende waarden. In de volgende hoofdstukken duiken we in I/O (CSV/Excel/Parquet/SQL), wrangling en groeperen/pivoteren.
Hoofdstuk 14 — Data inlezen en schrijven: CSV, Excel, Parquet en SQL
Let op: De tekst is in het Nederlands; alle Python codevoorbeelden zijn in het Engels.
Doel van dit hoofdstuk:
- Data inlezen uit en wegschrijven naar veelgebruikte formaten: CSV, Excel, Parquet en SQL.
- Leren welke parameters belangrijk zijn voor correcte parsing (scheidingsteken, encoding, datums, dtypes, missende waarden).
- Begrijpen van voor- en nadelen per formaat en performance-/geheugentips.
Aanbevolen voorkennis: Hoofdstuk 13 (pandas‑basis), Python/NumPy.
14.1 CSV: de klassieker
CSV is een tekstformaat met kolommen gescheiden door een delimiter (vaak , of ;). Het is universeel, maar mist schema en types.
14.1.1 Lezen met pd.read_csv
import pandas as pd
# Basic
df = pd.read_csv("data.csv")
# Veelgebruikte opties
# - sep: scheidingsteken (bijv. ';' in Nederlandse exporten)
# - encoding: bronencoding (bijv. 'utf-8', 'latin-1')
# - header/names: eerste rij als header of kolomnamen expliciet aangeven
# - usecols: subset van kolommen lezen
# - dtype: kolomtypes forceren
# - parse_dates: kolommen als datums parsen
# - na_values: extra strings als NaN behandelen
# - on_bad_lines: 'error' (default), 'warn' of 'skip'
# - engine: 'c' (snel), 'python' (toleranter)
# - nrows/skiprows: subset van rijen
# - compression: 'gzip', 'zip', 'bz2', ...
sales = pd.read_csv(
"sales_2024.csv",
sep=';',
encoding='utf-8',
usecols=["date", "product", "qty", "price"],
dtype={"product": "string", "qty": "int32", "price": "float32"},
parse_dates=["date"],
na_values=["", "NA", "NULL", "-"],
)
Datum parsing tips:
parse_dates=["date"]laat pandas zelf detecteren. Voor exotische formaten kun jedayfirst=Trueofdate_format(nieuwe pandas versies) gebruiken.- Grote snelheidswinst is mogelijk met
date_format="%Y-%m-%d"indien constant formaat.
Grote bestanden:
# Streamen met chunksize om geheugen te sparen
it = pd.read_csv("big.csv", chunksize=100_000)
total_qty = 0
for chunk in it:
total_qty += chunk["qty"].sum()
14.1.2 Schrijven met DataFrame.to_csv
# Schrijf zonder indexkolom, met semicolon delimiter en UTF-8
sales.to_csv("sales_out.csv", index=False, sep=';', encoding='utf-8')
# Gecomprimeerd
sales.to_csv("sales_out.csv.gz", index=False, compression='gzip')
Valkuilen:
- Locale notaties (decimalen met
,en duizendtallen met.); gebruikdecimal=','enthousands='.'bij het lezen. - Automatische type-inferentie kan strings/ID’s als numeriek zien; forceer
dtype. - CSV heeft geen schema → verlies van types bij round-trips mogelijk.
14.2 Excel: werkbladen en tabbladen
Excel-bestanden zijn binair (XLSX). Je hebt meestal een extra engine nodig.
Installatie:
pip install openpyxl # voor .xlsx lezen/schrijven
14.2.1 Lezen met pd.read_excel
# Specifieke sheet, kolommen en dtype
orders = pd.read_excel(
"orders.xlsx",
sheet_name="2024-Q1",
usecols=["order_id", "date", "amount"],
dtype={"order_id": "string", "amount": "float32"},
parse_dates=["date"],
engine="openpyxl",
)
# Meerdere sheets in één keer → dict van DataFrames
all_sheets = pd.read_excel("orders.xlsx", sheet_name=None, engine="openpyxl")
14.2.2 Schrijven met DataFrame.to_excel
# Eén sheet
orders.to_excel("orders_out.xlsx", index=False, sheet_name="Q1", engine="openpyxl")
# Meerdere sheets
with pd.ExcelWriter("orders_multi.xlsx", engine="openpyxl") as xw:
orders.to_excel(xw, index=False, sheet_name="Q1")
orders.sample(100).to_excel(xw, index=False, sheet_name="Sample")
Valkuilen:
- Excel kan cellen als tekst/numeriek wisselend formatteren; dwing
dtypein pandas. - Grootte en performance minder efficiënt dan kolom-georiënteerde formaten.
14.3 Parquet: kolom-georiënteerd, snel en gecomprimeerd
Parquet is een kolomarig, gecomprimeerd, schematisch formaat. Ideaal voor analytische workloads.
Installatie:
pip install pyarrow # aanbevolen
# of
pip install fastparquet
14.3.1 Lezen met pd.read_parquet
import pandas as pd
df = pd.read_parquet("dataset.parquet")
# Alleen bepaalde kolommen
small = pd.read_parquet("dataset.parquet", columns=["date", "amount"])
14.3.2 Schrijven met DataFrame.to_parquet
# Compressie en engine instellen
orders.to_parquet("orders.parquet", engine="pyarrow", compression="snappy", index=False)
14.3.3 Partitionering en mappen
Veel data‑engineering workflows schrijven partities per sleutel (bijv. jaar/maand) als directorystructuur:
/transactions/
year=2024/
month=01/
part-0000.parquet
month=02/
part-0000.parquet
year=2025/
month=01/
Lezen met partitiekolommen (pyarrow nodig):
# Vereist pyarrow.dataset onder water
# df = pd.read_parquet("transactions/", engine="pyarrow")
Voordelen Parquet:
- Snellere I/O, kolomselectie, compressie (snappy/zstd/gzip), behoud van dtypes en datums. Nadelen:
- Vereist extra dependency; minder universeel deelbaar dan CSV.
14.4 SQL: relationele databases
pandas kan lezen/schrijven via SQLAlchemy of DB-API connecties naar o.a. SQLite, PostgreSQL, MySQL.
Installatie voorbeeld (SQLite inbegrepen bij Python):
pip install sqlalchemy
14.4.1 Verbinden (SQLAlchemy engine)
from sqlalchemy import create_engine
engine = create_engine("sqlite:///example.db")
14.4.2 Lezen: pd.read_sql, read_sql_query, read_sql_table
import pandas as pd
# Volledige tabel
users = pd.read_sql_table("users", con=engine)
# Query (met parameter binding voor veiligheid)
query = "SELECT * FROM users WHERE country = :country AND age >= :min_age"
users_de = pd.read_sql_query(query, con=engine, params={"country": "DE", "min_age": 18})
14.4.3 Schrijven: DataFrame.to_sql
# Maak een DataFrame en schrijf naar SQL
import pandas as pd
new_users = pd.DataFrame({
"user_id": [1, 2, 3],
"name": ["Alice", "Bob", "Charlie"],
"age": [25, 32, 29],
"country": ["NL", "DE", "BE"],
})
# if_exists: 'fail' (default), 'replace', 'append'
new_users.to_sql("users", con=engine, if_exists="append", index=False)
Prestatietips:
- Gebruik
chunksizebij to_sql en read_sql voor grote datasets. - Stel
dtypeexpliciet in bij to_sql voor kolomtypes (SQLAlchemy types).
Veiligheid:
- Gebruik nooit string‑concatenatie voor parameters in SQL; gebruik
params/bound parameters.
14.5 Encodings, NA’s, en locale issues
- Encodings: probeer altijd UTF‑8; als het niet lukt, test
latin-1of bron‑encoding van de export. - Missende waarden: specificeer
na_values(CSV), controleerna_filter, en pasfillna/dropnatoe. - Decimalen en duizendtallen:
decimal=',',thousands='.'bij lezen. - Dtypes: voorkom ongewenste object‑kolommen; gebruik
dtypeof achterafastype.
14.6 Performance en geheugen
- Gebruik kolomsubset (
usecolsvoor CSV/Excel,columnsvoor Parquet) om minder te lezen. - Voor zeer grote CSV’s: overweeg conversie naar Parquet voor sneller hergebruik.
- Werk met
chunksizeen verwerk streamend. - Let op
low_memorybij read_csv: standaard True kan type‑inconsistenties geven; zet uit en specificeerdtypeals mogelijk.
14.7 Mini‑workflow: CSV → schoonmaken → Parquet → SQL
import pandas as pd
from sqlalchemy import create_engine
# 1) Lees CSV met opties
raw = pd.read_csv(
"transactions.csv",
sep=';',
parse_dates=["date"],
dtype={"product_id": "string", "qty": "int32", "price": "float32"},
na_values=["", "NA", "NULL"]
)
# 2) Eenvoudige schoonmaak
raw = raw.dropna(subset=["product_id", "qty"]) # vereiste velden
raw["amount"] = raw["qty"] * raw["price"]
# 3) Schrijf naar Parquet voor snel hergebruik
raw.to_parquet("transactions.parquet", compression="snappy", index=False)
# 4) Laad subset kolommen en schrijf naar SQL
subset = pd.read_parquet("transactions.parquet", columns=["date", "product_id", "amount"])
engine = create_engine("sqlite:///analytics.db")
subset.to_sql("transactions", con=engine, if_exists="replace", index=False)
14.8 Samenvatting
Je leerde:
- CSV/Excel lezen en schrijven met cruciale parameters voor delimiter, encoding, dtypes en datums.
- Parquet gebruiken voor performance, compressie en schema‑behoud.
- SQL‑bronnen aan te spreken via SQLAlchemy met veilige parameterbinding en
to_sql/read_sql. Met deze I/O‑basis kun je betrouwbare data‑pijplijnen opzetten en datasets efficiënt hergebruiken.
Hoofdstuk 15 — Data schoonmaken en wrangling: missing values, typen en categorieën
Let op: De tekst is in het Nederlands; alle Python codevoorbeelden zijn in het Engels.
Doel van dit hoofdstuk:
- Systematisch omgaan met missende waarden: detecteren, filteren, invullen en imputeren.
- Datatypes begrijpen en beheren: numeriek, string, boolean, datetime, timedelta en categorisch.
- Tekstkolommen opschonen en normaliseren; duplicates en outliers aanpakken.
- Praktische wrangling‑patronen toepassen met method‑chaining (intro) voor leesbare pipelines.
Aanbevolen voorkennis: Hoofdstuk 13–14 (pandas basis en I/O).
15.1 Missende waarden herkennen en analyseren
import pandas as pd
import numpy as np
df = pd.DataFrame({
'id': [1, 2, 3, 4],
'age': [25, None, 31, 28],
'score': [88.0, 92.5, None, 86.0],
'city': ['Amsterdam', 'Rotterdam', None, 'Utrecht']
})
# Detectie
mask_na = df.isna()
count_na_per_col = df.isna().sum()
rows_with_any_na = df[df.isna().any(axis=1)]
# Verwijderen
drop_any = df.dropna() # rijen met een NaN weg
subset_req = df.dropna(subset=['age', 'score'])
Wanneer verwijderen, wanneer vullen?
- Verwijderen als de missings zeldzaam zijn en willekeurig verspreid (MCAR).
- Vullen/imputeren als informatieverlies te groot is of de kolom essentieel is.
15.2 Missende waarden vullen (fillna) en eenvoudige imputatie
# Eenvoudig vullen per kolom
filled = df.fillna({
'age': df['age'].median(),
'score': df['score'].mean(),
'city': 'Onbekend'
})
# Forward/backward fill op tijdreeksen of gesorteerde data
filled_ffill = df.sort_values('id').fillna(method='ffill')
filled_bfill = df.sort_values('id').fillna(method='bfill')
# Alleen numeriek vullen
num_cols = df.select_dtypes(include='number').columns
df[num_cols] = df[num_cols].fillna(df[num_cols].median())
Let op: Eenvoudige invulling kan statistieken vertekenen; documenteer keuzes en overweeg geavanceerde imputatie in ML‑pipelines (zie Deel VI).
15.3 Dtypes beheren en converteren
# Inspectie
print(df.dtypes)
# Numeriek
df['age'] = df['age'].astype('float32')
# String (pandas 1.0+ string dtype)
df['city'] = df['city'].astype('string')
# Boolean met NA‑ondersteuning
s = pd.Series([True, False, None], dtype='boolean')
# Datetime en timedelta
sales = pd.DataFrame({'date': ['2025-01-01','2025-02-03'], 'days': [10, 20]})
sales['date'] = pd.to_datetime(sales['date'], format='%Y-%m-%d')
sales['deadline'] = sales['date'] + pd.to_timedelta(sales['days'], unit='D')
15.4 Categorical dtypes: geheugen en performance
Categorische kolommen slaan herhalende labels efficiënt op en versnellen sommige operaties (groupby/sort).
sizes = pd.Series(['S','M','M','L','S','XL','M'])
sizes_cat = sizes.astype('category')
# Orde instellen
sizes_ord = pd.Categorical(sizes, categories=['S','M','L','XL'], ordered=True)
# Toepassing: binned levels
import numpy as np
scores = pd.Series([72, 88, 91, 67, 85])
bins = [0, 80, 90, 100]
labels = ['low', 'med', 'high']
levels = pd.cut(scores, bins=bins, labels=labels).astype('category')
15.5 Tekstopschoning en normalisatie
Veel real‑world datasets bevatten rommelige strings.
products = pd.Series([' Apple iPhone ', 'SAMSUNG Galaxy', 'xiaomi-REDMI', None], dtype='string')
clean = (products
.str.strip()
.str.replace('-', ' ', regex=False)
.str.title()
.str.replace('Iphone', 'iPhone', regex=False)
)
# Splitsen en extractie met regex
emails = pd.Series(['alice@example.com','bob@test.org'], dtype='string')
usernames = emails.str.extract(r'^(?P<user>[^@]+)@(?P<domain>.+)$')
15.6 Duplicaten en outliers
# Duplicaten
unique_df = df.drop_duplicates()
# Duplicaten op subset
unique_city = df.drop_duplicates(subset=['city'])
# Eenvoudige outlier detectie met z‑score
import numpy as np
vals = pd.Series([10, 11, 10.5, 200, 9.8, 10.1])
z = (vals - vals.mean()) / vals.std()
outliers = vals[abs(z) > 3]
15.7 Wrangling‑patronen met method‑chaining
# Voorbeeld pipeline: kolommen selecteren -> missings vullen -> nieuwe variabelen -> filter -> sort
result = (
df
.assign(age_filled=lambda d: d['age'].fillna(d['age'].median()))
.assign(score_z=lambda d: (d['score'] - d['score'].mean()) / d['score'].std())
.dropna(subset=['city'])
.query('age_filled >= 18')
.sort_values(['score_z'], ascending=False)
)
Tip: Gebruik tussenstappen met .pipe(func, ...) om grotere ketens te structureren (zie Hoofdstuk 19 voor meer over pipe).
15.8 Samenvatting
- Herken en behandel missende waarden met
isna,dropna,fillna, en forward/backward fills. - Beheer dtypes expliciet: numeriek, string, boolean, datetime/timedelta, en
categoryvoor efficiënte labels. - Reinig tekstkolommen met
.str‑methodes; verwijder duplicaten en signaleer outliers. - Combineer stappen in leesbare wrangling‑pipelines met method‑chaining.
Hoofdstuk 16 — Groeperen, aggregeren en pivoteren (groupby en pivot_table)
Let op: De tekst is in het Nederlands; alle Python codevoorbeelden zijn in het Engels.
Doel van dit hoofdstuk:
- Data groeperen en samenvatten met
groupbyen aggregaties. - Meervoudige aggregaties en kolomnamen beheren.
- Pivoteren met
pivot,pivot_tableen kruistabellen (crosstab). - Window‑functies voor rollende/expanderende berekeningen.
Aanbevolen voorkennis: Hoofdstuk 13–15.
16.1 GroupBy: basisconcepten
groupby splitst data per sleutel, voert een operatie uit en combineert het resultaat.
import pandas as pd
import numpy as np
df = pd.DataFrame({
'region': ['EU','EU','US','US','APAC','EU'],
'product': ['A','B','A','B','A','A'],
'qty': [2,3,1,5,2,4],
'price': [10.0, 12.0, 9.0, 11.0, 8.5, 10.0]
})
# Totaal per regio
sales_by_region = df.groupby('region')['qty'].sum()
# Meerdere keys
sales_by_reg_prod = df.groupby(['region','product'])['qty'].sum()
16.2 Aggregaties en meervoudige stats
# Eenvoudige aggregaties
agg1 = df.groupby('region').agg(qty_sum=('qty','sum'), price_mean=('price','mean'))
# Meervoudige aggregaties per kolom
agg2 = df.groupby('product').agg({
'qty': ['sum','mean','count'],
'price': ['mean','min','max']
})
# Kolomnamen flattenen
agg2.columns = ['_'.join(col).strip() for col in agg2.columns.to_flat_index()]
16.3 Transform vs. aggregate
transform retourneert een Series aligned met de oorspronkelijke index (handig voor normalisatie per groep).
# Z‑score van qty per product
df['qty_z_per_product'] = (
df.groupby('product')['qty']
.transform(lambda s: (s - s.mean())/s.std())
)
16.4 Filteren op groepsniveau
# Houd alleen producten met totale qty >= 5
filtered = df.groupby('product').filter(lambda g: g['qty'].sum() >= 5)
16.5 Pivot en pivot_table
# pivot: geen aggregatie; vereist unieke combinaties
no_agg = pd.DataFrame({
'day': ['Mon','Tue','Wed'],
'product': ['A','A','A'],
'qty': [5, 3, 7]
})
wide = no_agg.pivot(index='day', columns='product', values='qty')
# pivot_table: met aggregatie en duplicate handling
pt = pd.pivot_table(df, index='region', columns='product', values='qty', aggfunc='sum', fill_value=0)
16.6 Crosstab (frequentietabellen)
ct = pd.crosstab(df['region'], df['product'], margins=True, normalize='index')
16.7 Window‑functies: rolling en expanding
s = pd.Series([1, 2, 3, 4, 5], dtype='float64')
rolling_mean = s.rolling(window=3, min_periods=1).mean()
expanding_sum = s.expanding(min_periods=1).sum()
16.8 Prestaties en geheugen
- Gebruik categorische dtypes voor group keys met veel herhaling.
- Kies specifieke kolommen vóór groupby om geheugen te besparen.
- Voor grote datasets: overweeg
observed=Truebij categoricals om lege combinaties te vermijden.
16.9 Samenvatting
groupbyvoor splits‑apply‑combine;agg,transform,filtervoor verschillende doelen.pivot_tableencrosstabvoor brede tabellen en frequenties.rollingenexpandingvoor vensterberekeningen.
Hoofdstuk 17 — Tijdsreeksen en datum/tijd in pandas
Let op: De tekst is in het Nederlands; alle Python codevoorbeelden zijn in het Engels.
Doel van dit hoofdstuk:
- Datums en tijden correct parsen, opslaan en formatteren.
- Indexeren en resamplen van tijdreeksen; frequenties en offsets.
- Rolling/expanding op tijdsgebaseerde vensters; time zone handling.
- Use‑cases: kalenderfeatures, business‑dagen, periode‑objecten.
Aanbevolen voorkennis: Hoofdstuk 13–16.
17.1 Datums parsen en dtypes
import pandas as pd
import numpy as np
s = pd.Series(['2025-01-01', '2025-01-02', '2025-02-15'])
dates = pd.to_datetime(s, format='%Y-%m-%d')
# Datetime64[ns]
assert str(dates.dtype).startswith('datetime64')
# Tijdsdelta
delta = pd.to_timedelta(["1D", "12H", "30min"]) # dagen, uren, minuten
17.2 DatetimeIndex en frequenties
# Reeks met dagelijkse frequentie
ts = pd.Series(np.arange(5), index=pd.date_range('2025-01-01', periods=5, freq='D'))
# Subselectie op datum
jan = ts['2025-01']
specific = ts['2025-01-03']
# Herindexeren naar een andere frequentie
hourly = ts.resample('H').ffill() # upsample met forward‑fill
17.3 Resampling en aggregaties
rng = pd.date_range('2025-01-01', periods=1000, freq='H')
vals = pd.Series(np.random.default_rng(0).standard_normal(len(rng)), index=rng)
# Dagelijkse som en gemiddelde
by_day = vals.resample('D').agg(['sum','mean'])
# Business‑day frequentie met eind van de maand
month_end = vals.resample('M').mean()
17.4 Time‑based rolling windows
# Tijdsvenster van 7 dagen, min 1 observatie
roll7 = vals.rolling('7D', min_periods=1).mean()
17.5 Time zones (tz‑aware)
utc = pd.to_datetime(['2025-01-01 12:00', '2025-06-01 12:00'], utc=True)
# Convert naar Europa/Amsterdam
ams = utc.tz_convert('Europe/Amsterdam')
# Lokale naive -> lokaliseer eerst, dan converteren
naive = pd.to_datetime(['2025-01-01 12:00'])
ams_local = naive.tz_localize('Europe/Amsterdam')
17.6 Periodes en PeriodIndex
# Maandperioden
p = pd.period_range('2025-01', '2025-06', freq='M')
series_p = pd.Series(range(len(p)), index=p)
# Conversie tussen Timestamp en Period via asfreq / to_period
monthly = ts.to_period('M')
daily_back = monthly.to_timestamp(how='end')
17.7 Kalenderfeatures en business-dagen
# Weekdag, maand, jaar als features
features = pd.DataFrame({'date': pd.date_range('2025-01-01', periods=10, freq='D')})
features['weekday'] = features['date'].dt.weekday
features['is_month_end'] = features['date'].dt.is_month_end
# Business Day offset
bdr = pd.bdate_range('2025-01-01', periods=5)
17.8 Veelvoorkomende valkuilen
- Mixing naive (tz‑naive) en tz‑aware timestamps geeft fouten; maak alles tz‑aware of hou het consistent naive.
- Resampling vereist een datumindex; zet eerst
set_index('date'). - Herken DST‑overgangen bij tz‑convert/localize; sommige tijden bestaan dubbel of niet.
17.9 Samenvatting
- Gebruik
to_datetimeendate_range/bdate_rangevoor datumconstructies. - Resample naar gewenste frequentie en gebruik tijdsgebaseerde rolling vensters.
- Beheer time zones expliciet; PeriodIndex voor periodieke data zoals maanden/kwartalen.
Hoofdstuk 18 — Samenvoegen en joinen: merge, concat en align
Let op: De tekst is in het Nederlands; alle Python codevoorbeelden zijn in het Engels.
Doel van dit hoofdstuk:
- Tabelverbindingen uitvoeren met
merge(inner/left/right/outer/indicator, suffixes). - Verticale/horizontale concatenatie met
concaten uitlijning met indexen. - Uitlijning en wiskunde tussen tabellen met verschillende indexen/labels (
align). - Geavanceerde joins: many‑to‑many, validatie en performance‑tips.
Aanbevolen voorkennis: Hoofdstuk 13–17.
18.1 Merge basis
import pandas as pd
left = pd.DataFrame({'id': [1,2,3], 'name': ['Alice','Bob','Charlie']})
right = pd.DataFrame({'id': [1,2,2,4], 'score': [88, 92, 85, 77]})
inner = pd.merge(left, right, on='id', how='inner')
left_join = pd.merge(left, right, on='id', how='left')
outer = pd.merge(left, right, on='id', how='outer', indicator=True)
18.2 Merge op verschillende kolomnamen en suffixes
sales = pd.DataFrame({'prod_id':[1,2], 'qty':[5,3]})
prods = pd.DataFrame({'id':[1,2], 'name':['A','B']})
merged = sales.merge(prods, left_on='prod_id', right_on='id', how='left', suffixes=('_s','_p'))
18.3 Many‑to‑one, one‑to‑one en many‑to‑many validatie
# validatie dwingt relatie af en voorkomt onverwachte duplicatie
mto = left.merge(right, on='id', how='left', validate='one_to_many')
oto = left.merge(left, on='id', how='left', validate='one_to_one')
18.4 Concat: stapelen en naast elkaar zetten
# Verticaal stapelen (rijen)
part1 = pd.DataFrame({'id':[1,2], 'qty':[5,3]})
part2 = pd.DataFrame({'id':[3,4], 'qty':[2,7]})
stacked = pd.concat([part1, part2], axis=0, ignore_index=True)
# Horizontaal (kolommen)
feat1 = pd.DataFrame({'id':[1,2,3], 'a':[10,20,30]})
feat2 = pd.DataFrame({'id':[1,2,3], 'b':[5,6,7]})
joined = pd.concat([feat1.set_index('id'), feat2.set_index('id')], axis=1).reset_index()
18.5 Align: uitlijning voor wiskunde
s1 = pd.Series([1, 2, 3], index=['a','b','c'])
s2 = pd.Series([10, 20, 30], index=['b','c','d'])
# Align retourneert gere‑indexeerde kopieën
A, B = s1.align(s2, join='outer', fill_value=0)
result = A + B
18.6 Joins op index
left_i = left.set_index('id')
right_i = right.set_index('id')
joined_i = left_i.join(right_i, how='left', lsuffix='_l', rsuffix='_r')
18.7 Prestatie, geheugen en valkuilen
- Zet join keys naar een compact dtype (bijv.
int32,category) voor minder geheugen. - Sorteer op keys voor snellere merges in sommige gevallen.
- Controleer duplicaten en cardinaliteit; gebruik
validateom onverwachte cartesiaanse explosies te voorkomen. - Bij concat: gebruik
ignore_index=Trueom herindexering te voorkomen.
18.8 Samenvatting
mergevoor SQL‑achtige joins;concatvoor stapelen of kolommen combineren.alignvoor veilige wiskunde met verschillende indexen.- Let op relatie‑type validatie en geheugenoptimalisaties.
Hoofdstuk 19 — Prestatie, memory en method‑chaining (pipe)
Let op: De tekst is in het Nederlands; alle Python codevoorbeelden zijn in het Engels.
Doel van dit hoofdstuk:
- Begrijpen van geheugenverbruik en dtypes; kolom‑selectie en kopieën vermijden.
- Vectorisatie, blokgewijze verwerking en efficiënte I/O.
- Method‑chaining en functionele stijl met
.pipevoor onderhoudbare pipelines. - Profiling en meetbare optimalisaties.
Aanbevolen voorkennis: Hoofdstuk 13–18.
19.1 Geheugeninspectie en dtypes
import pandas as pd
import numpy as np
df = pd.DataFrame({
'id': np.arange(1_000_000),
'cat': np.random.choice(list('ABCDE'), size=1_000_000),
'x': np.random.rand(1_000_000)
})
mem_before = df.memory_usage(deep=True).sum() / 1e6
# Optimaliseer dtypes
df['id'] = df['id'].astype('int32')
df['cat'] = df['cat'].astype('category')
df['x'] = df['x'].astype('float32')
mem_after = df.memory_usage(deep=True).sum() / 1e6
print(mem_before, mem_after)
19.2 Kopieën vermijden en chained assignment
- Gebruik
.locvoor veilige toewijzing:df.loc[mask, 'col'] = value. - Vermijd kettingindexering
df[df.a>0]['b'] = ...(SettingWithCopyWarning). - Werk kolomgewijs en benut ufuncs; vermijd per‑rij loops.
19.3 Vectorisatie en blokgewijze verwerking
# Vectorized
df['y'] = df['x'] * 2 + 1
# Blokgewijs lezen/verwerken bij grote CSV's
it = pd.read_csv('big.csv', chunksize=100_000)
acc = 0.0
for chunk in it:
acc += (chunk['qty'] * chunk['price']).sum()
19.4 Efficiënte I/O en opslag
- Gebruik Parquet met compressie (snappy/zstd) i.p.v. CSV voor herhaald lezen.
- Sla alleen benodigde kolommen op; gebruik
columns=bij Parquet. - Partitioneer datasets op logische sleutels (jaar/maand) voor slice‑toegang.
19.5 Method‑chaining en .pipe
def add_amount(d):
return d.assign(amount=d['qty'] * d['price'])
def filter_valid(d):
return d.dropna(subset=['product', 'qty', 'price'])
def normalize_amount(d):
mean = d['amount'].mean(); std = d['amount'].std()
return d.assign(amount_z=(d['amount'] - mean)/std)
result = (
pd.read_csv('transactions.csv', sep=';', parse_dates=['date'])
.pipe(filter_valid)
.pipe(add_amount)
.query('qty > 0 and price > 0')
.pipe(normalize_amount)
.sort_values(['date'])
)
19.6 Profiling en timing
# Eenvoudig tijd meten
import time
start = time.perf_counter()
_ = pd.read_csv('big.csv')
elapsed = time.perf_counter() - start
print(f"read_csv: {elapsed:.3f}s")
# memory_usage en info
mu = result.memory_usage(deep=True).sum()/1e6
print(f"Result MB: {mu:.2f}")
19.7 Parallel/alternatieve engines (kort)
- Gebruik
pyarrowengine voorread_csv(indien beschikbaar in recente pandas) voor snelheidswinst. - Voor zeer grote data: overweeg Dask/Polars/Spark (buiten scope), maar principes blijven gelijk.
19.8 Samenvatting
- Beperk geheugen via dtypes en kolomselectie; voorkom onnodige kopieën.
- Vectoriseer berekeningen en verwerk streamend met chunks.
- Structureer data pipelines met method‑chaining en
.pipevoor onderhoudbaarheid. - Meet, vergelijk, en documenteer performance‑winst.
Hoofdstuk 20 — Geavanceerde indexen: MultiIndex en reshaping (stack/unstack/melt)
Let op: De tekst is in het Nederlands; alle Python codevoorbeelden zijn in het Engels.
Doel van dit hoofdstuk:
- Begrijpen en gebruiken van MultiIndex voor rijen en kolommen.
- Reshaping‑technieken:
stack,unstack,meltenwide↔long. - Gecombineerd gebruik met
groupby,pivot_tableen sorteren. - Praktische patronen voor analyse en rapportage.
Aanbevolen voorkennis: Hoofdstuk 13–19.
20.1 MultiIndex maken en basisoperaties
import pandas as pd
import numpy as np
arrays = [
['EU','EU','US','US'],
['A','B','A','B']
]
idx = pd.MultiIndex.from_arrays(arrays, names=['region','product'])
s = pd.Series([10, 12, 9, 11], index=idx)
# Selectie
sel_eu = s.loc['EU']
sel_us_a = s.loc[('US','A')]
# Sorteren en herindexeren
s_sorted = s.sort_index()
20.2 MultiIndex op DataFrame kolommen
data = pd.DataFrame({
('sales','qty'): [2,3,1,5],
('sales','price'): [10.0, 12.0, 9.0, 11.0],
('meta','region'): ['EU','EU','US','US']
})
data.columns = pd.MultiIndex.from_tuples(data.columns)
20.3 Stack en unstack
unstack verplaatst een niveau van de rijindex naar kolommen; stack doet het omgekeerde.
# Van long (MultiIndex rows) naar wide (kolommen)
df = s.reset_index(name='value').set_index(['region','product'])
unstacked = df['value'].unstack('product') # kolommen A, B
# Terug naar long
stacked = unstacked.stack().rename('value').reset_index()
20.4 Melt: wide naar long
wide = pd.DataFrame({
'id': [1,2,3],
'A_qty': [5,3,1],
'B_qty': [2,4,6]
})
long = (wide
.melt(id_vars='id', var_name='var', value_name='qty')
.assign(product=lambda d: d['var'].str[0])
.drop(columns=['var'])
)
20.5 Combineren met groupby en pivot_table
raw = pd.DataFrame({
'region': ['EU','EU','US','US','EU'],
'product': ['A','B','A','B','A'],
'qty': [2,3,1,5,4]
})
pt = pd.pivot_table(raw, index='region', columns='product', values='qty', aggfunc='sum', fill_value=0)
# MultiIndex kolommen flattenen
pt.columns = pt.columns.astype(str)
pt_reset = pt.reset_index()
20.6 Index‑niveau’s hernoemen en wisselen
midx = df.index # ['region','product']
renamed = df.rename_axis(index={'region':'reg','product':'prod'})
swapped = df.reorder_levels(['product','region']).sort_index()
20.7 Veelvoorkomende valkuilen
unstackkan NaN introduceren voor ontbrekende combinaties; gebruikfill_value=offillna.- Let op volgorde van index‑niveaus bij selectie en sorteren; gebruik
swaplevel/reorder_levels. - Kolom‑MultiIndex maakt code cryptischer; flatten kolomnamen waar zinvol.
20.8 Samenvatting
- MultiIndex ondersteunt hiërarchische labeling voor flexibele selectie en reshaping.
- Gebruik
stack/unstack/meltom tussen long en wide te transformeren. - Combineer met
groupbyenpivot_tablevoor krachtige samenvattingen.
Hoofdstuk 21 — Matplotlib basis: Figure, Axes, styles en layouts
Let op: De tekst is in het Nederlands; alle Python codevoorbeelden zijn in het Engels.
Doel van dit hoofdstuk:
- Begrijpen van de Matplotlib‑architectuur: Figure, Axes en Artists.
- Leren werken met de object‑georiënteerde (OO) interface i.p.v. de stateful
pyplotshortcuts. - Styles, kleuren, markers en lijnen instellen.
- Layouts en meerdere subplot‑rasterindelingen maken en beheren.
Aanbevolen voorkennis: Hoofdstuk 13–20 (pandas/NumPy). Installeer Matplotlib:
pip install matplotlib
21.1 Figure en Axes: de kern
Matplotlib tekent op Axes (plotvlak) binnen een Figure (canvas/figuur). Gebruik bij voorkeur de OO‑stijl.
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(0, 2*np.pi, 200)
y = np.sin(x)
fig, ax = plt.subplots(figsize=(6, 4), dpi=120)
ax.plot(x, y, label='sin(x)')
ax.set(title='Sine', xlabel='x', ylabel='y')
ax.legend()
fig.tight_layout()
plt.show()
Belangrijkste concepten:
- Figure: het gehele canvas (afbeelding), bevat 1+ Axes.
- Axes: de plot zelf met assen, ticks, labels, titel en artists (lijnen, scatter, patches).
- OO‑API:
ax.plot(...)i.p.v.plt.plot(...)voor expliciete controle.
21.2 Lijnen, markers, kleuren en stijlen
fig, ax = plt.subplots()
ax.plot(x, y, color='C0', linewidth=2, linestyle='-', marker='o', markersize=4)
ax.plot(x, np.cos(x), color='#d62728', lw=1.5, ls='--', label='cos(x)')
ax.set_xlabel('x'); ax.set_ylabel('f(x)'); ax.legend()
Tips:
- Gebruik cycler‑kleuren (
C0..C9) of hex‑kleuren. - Transparantie via
alpha=. - Markers:
'o','^','s','.', etc.
21.3 Assen, limieten, ticks en grids
fig, ax = plt.subplots()
ax.plot(x, y)
ax.set_xlim(0, 2*np.pi); ax.set_ylim(-1.1, 1.1)
ax.set_xticks([0, np.pi, 2*np.pi]); ax.set_xticklabels(['0', 'π', '2π'])
ax.grid(True, axis='both', linestyle=':', alpha=0.6)
21.4 Meerdere subplots en layouts
# 1x2 raster
fig, axes = plt.subplots(1, 2, figsize=(8, 3), sharey=True)
axes[0].plot(x, np.sin(x)); axes[0].set_title('sin')
axes[1].plot(x, np.cos(x)); axes[1].set_title('cos')
fig.suptitle('Trig'); fig.tight_layout()
Geavanceerde layout hulpmiddelen:
fig.tight_layout()ofplt.tight_layout()voor automatische marges.fig.subplots_adjust(...)voor handmatige marge‑tuning.matplotlib.gridspecvoor ongelijke rasters.
21.5 Styles en theming
Matplotlib heeft ingebouwde styles (themes).
import matplotlib.pyplot as plt
plt.style.available[:5]
with plt.style.context('seaborn-v0_8'):
fig, ax = plt.subplots()
ax.plot(x, y)
ax.set(title='Seaborn‑style')
Eigen rcParams aanpassen:
import matplotlib as mpl
mpl.rcParams['figure.dpi'] = 150
mpl.rcParams['axes.grid'] = True
21.6 Tekst, annotaties en legendes (basis)
fig, ax = plt.subplots()
ax.plot(x, y, label='sin')
ax.legend(loc='best')
ax.set_title('Titel')
ax.annotate('maximum', xy=(np.pi/2, 1), xytext=(2, 1.2),
arrowprops=dict(arrowstyle='->'))
21.7 Opslaan van figuren
fig.savefig('plot.png', dpi=150, bbox_inches='tight')
fig.savefig('plot.pdf') # vector
21.8 Veelvoorkomende valkuilen
- Gemixte OO‑ en pyplot‑stijl kan verwarrend zijn. Kies consequent voor OO.
- Vergeet
tight_layout()ofbbox_inches='tight'niet bij saven om afgesneden labels te voorkomen. - Houd rekening met DPI en figsize voor leesbaarheid.
21.9 Samenvatting
- Gebruik
plt.subplots()om Figure en Axes te krijgen. - Werk met de OO‑API (
ax.plot,ax.set,ax.legend) voor controle en compositie. - Styles/rcParams bepalen de look; layouts regelen de rangschikking van subplots.
Hoofdstuk 22 — Veelgebruikte grafieken: line, bar, histogram, box en scatter
Let op: De tekst is in het Nederlands; alle Python codevoorbeelden zijn in het Engels.
Doel van dit hoofdstuk:
- Leren wanneer je lijn-, staaf-, histogram-, box- en scatterplots gebruikt.
- OO‑stijl van Matplotlib toepassen voor deze grafieken.
- Assen schalen, labels, legendes en error bars toevoegen.
- Veelvoorkomende varianten (stacked bars, twin axes, log‑schalen) en valkuilen.
Aanbevolen voorkennis: Hoofdstuk 21 (Matplotlib basis).
import numpy as np
import matplotlib.pyplot as plt
rng = np.random.default_rng(0)
22.1 Lijngrafieken (line)
Gebruik voor trends over een continue as (tijd, meting per x).
x = np.linspace(0, 10, 200)
y = np.sin(x)
fig, ax = plt.subplots()
ax.plot(x, y, label='sin(x)', color='C0')
ax.set(xlabel='x', ylabel='y', title='Line plot')
ax.legend(); fig.tight_layout()
Varianten:
- Meerdere lijnen per Axes; zorg voor goed onderscheid in kleur/linestyle.
- Log‑schalen:
ax.set_yscale('log')ofax.semilogy(x, y).
22.2 Staafdiagrammen (bar)
Categorische vergelijkingen of geaggregeerde waarden.
cats = ['A','B','C']
vals = [5, 3, 7]
fig, ax = plt.subplots()
ax.bar(cats, vals, color='C1', alpha=0.9)
ax.set(ylabel='count', title='Bar chart')
Horizontaal en gegroepeerd/gestapeld:
# Horizontaal
fig, ax = plt.subplots()
ax.barh(cats, vals)
# Gegroepeerd
vals2 = [4, 2, 6]
idx = np.arange(len(cats))
width = 0.4
fig, ax = plt.subplots()
ax.bar(idx - width/2, vals, width, label='2024')
ax.bar(idx + width/2, vals2, width, label='2025')
ax.set_xticks(idx); ax.set_xticklabels(cats)
ax.legend()
# Gestapeld
fig, ax = plt.subplots()
ax.bar(cats, vals, label='A')
ax.bar(cats, vals2, bottom=vals, label='B')
ax.legend()
Error bars:
means = np.array([5.0, 3.2, 7.1]); errs = np.array([0.6, 0.4, 0.8])
fig, ax = plt.subplots()
ax.bar(cats, means, yerr=errs, capsize=4)
22.3 Histogrammen (hist)
Verdelingen van continue variabelen.
data = rng.normal(loc=0, scale=1, size=10_000)
fig, ax = plt.subplots()
ax.hist(data, bins=40, density=True, alpha=0.7, color='C2')
ax.set(title='Histogram', xlabel='value', ylabel='density')
Tips:
- Kies aantal bins bewust (
bins=of regels zoals Freedman–Diaconis via numpy). - Voor meerdere reeksen:
histtype='step'of transparantie (alpha).
22.4 Boxplots en violins
Samenvatten van verdelingen en outliers.
g1 = rng.normal(0.0, 1.0, 500)
g2 = rng.normal(1.5, 0.8, 500)
fig, ax = plt.subplots()
ax.boxplot([g1, g2], labels=['G1','G2'], showmeans=True)
ax.set(title='Box plot')
# Violinplot
fig, ax = plt.subplots()
ax.violinplot([g1, g2], showmeans=True, showmedians=True)
ax.set_xticks([1,2]); ax.set_xticklabels(['G1','G2'])
22.5 Scatterplots
Relatie tussen twee continue variabelen; kleur/marker/maat voor extra dimensies.
x = rng.normal(0, 1, 1000)
y = 2*x + rng.normal(0, 1, 1000)
size = 20 + 80*rng.random(1000)
color = x
fig, ax = plt.subplots()
sc = ax.scatter(x, y, c=color, s=size, cmap='viridis', alpha=0.7)
ax.set(xlabel='x', ylabel='y', title='Scatter')
cb = fig.colorbar(sc, ax=ax, label='x value')
Regressielijn (simpel) met NumPy:
coef = np.polyfit(x, y, deg=1)
xs = np.linspace(x.min(), x.max(), 100)
ax.plot(xs, coef[0]*xs + coef[1], color='k', lw=2, label='fit')
ax.legend()
22.6 Twin axes en secundaire as
x = np.linspace(0, 10, 200)
y1 = np.sin(x); y2 = np.cos(x)
fig, ax1 = plt.subplots()
ax2 = ax1.twinx()
ax1.plot(x, y1, color='C0'); ax1.set_ylabel('sin')
ax2.plot(x, y2, color='C3'); ax2.set_ylabel('cos')
22.7 Log‑schalen en breaks
x = np.logspace(0, 4, 200)
y = 1/x
fig, ax = plt.subplots()
ax.loglog(x, y)
ax.set(title='Log–log')
22.8 Veelvoorkomende valkuilen
- Gebruik geen 3D voor 2D‑relaties; vermijdt chartjunk.
- Y‑as nul‑afsnede belangrijk bij staafdiagrammen (anders misleidend), tenzij goed gemotiveerd.
- Overplotting in scatter → gebruik alpha, kleinere markers, of hexbin/density.
- Denk aan kleurenblindvriendelijke paletten (bv.
tab10,colorcetbuiten scope).
22.9 Samenvatting
- Line: trends; Bar: categorieën; Hist/Box/Violin: verdelingen; Scatter: correlaties.
- Voeg labels, legendes, error bars en passende schalen toe.
- OO‑stijl blijft de aanbevolen werkwijze.
Hoofdstuk 23 — Aangepaste visualisaties, annotaties en subplots
Let op: De tekst is in het Nederlands; alle Python codevoorbeelden zijn in het Engels.
Doel van dit hoofdstuk:
- Complexere composities maken met meerdere Axes en GridSpec.
- Annotaties, pijlen, tekstkaders en highlight‑technieken toepassen.
- Balken/pijlen/patches (Rectangle, Arrow), twin axes, inset axes en broken axes.
- Reusable functies en kleine helper‑API’s bouwen voor consistente visuals.
Aanbevolen voorkennis: Hoofdstuk 21–22.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib as mpl
from matplotlib.patches import Rectangle, FancyArrow, Circle
23.1 Subplot‑composities met GridSpec
plt.subplots dekt veel gevallen. Voor asymmetrische layouts gebruik je GridSpec.
import matplotlib.gridspec as gridspec
fig = plt.figure(figsize=(8, 5))
G = gridspec.GridSpec(nrows=2, ncols=3, height_ratios=[2, 1], width_ratios=[1, 2, 1], figure=fig)
ax_main = fig.add_subplot(G[0, :]) # hele bovenste rij
ax_left = fig.add_subplot(G[1, 0])
ax_mid = fig.add_subplot(G[1, 1])
ax_right= fig.add_subplot(G[1, 2])
x = np.linspace(0, 10, 200)
ax_main.plot(x, np.sin(x))
ax_left.bar(['A','B','C'], [3,2,5])
ax_mid.plot(x, np.cos(x), color='C3')
ax_right.hist(np.random.default_rng(0).normal(size=1000), bins=30)
fig.suptitle('GridSpec compositie'); fig.tight_layout()
23.2 Inset Axes (zoom) en inset colorbar
from mpl_toolkits.axes_grid1.inset_locator import inset_axes, mark_inset
fig, ax = plt.subplots(figsize=(6, 4))
x = np.linspace(0, 4*np.pi, 400); y = np.sin(x) + 0.2*np.sin(10*x)
ax.plot(x, y)
ax.set(title='Inset demo')
# Inset
axins = inset_axes(ax, width="40%", height="40%", loc='upper right')
axins.plot(x, y, color='C1')
axins.set_xlim(2, 3); axins.set_ylim(-1.2, 1.2)
mark_inset(ax, axins, loc1=2, loc2=4, fc="none", ec="0.5")
23.3 Broken axes (gapped axis)
fig, (ax, ax2) = plt.subplots(2, 1, sharex=True, figsize=(6, 4))
y = np.concatenate([np.random.normal(10, 1, 100), np.random.normal(100, 5, 100)])
ax.boxplot(y)
ax2.boxplot(y)
ax.set_ylim(95, 110) # bovenste bereik
ax2.set_ylim(8, 12) # onderste bereik
# diagonale streepjes om de break te tonen
ax.spines['bottom'].set_visible(False)
ax2.spines['top'].set_visible(False)
ax.xaxis.tick_top(); ax.tick_params(labeltop=False)
ax2.xaxis.tick_bottom()
kwargs = dict(marker=[(-1, -1), (1, 1)], markersize=12, linestyle='none', color='k', mec='k', mew=1, clip_on=False)
ax.plot([0, 1], [0, 0], transform=ax.transAxes, **kwargs)
ax2.plot([0, 1], [1, 1], transform=ax2.transAxes, **kwargs)
fig.tight_layout()
23.4 Annotaties, pijlen en tekstkaders
fig, ax = plt.subplots(figsize=(6, 4))
x = np.linspace(0, 2*np.pi, 400); y = np.sin(x)
ax.plot(x, y)
ax.annotate('maximum', xy=(np.pi/2, 1.0), xytext=(2.5, 1.2),
arrowprops=dict(arrowstyle='->', lw=1.5),
bbox=dict(boxstyle='round,pad=0.2', fc='w', ec='0.5'))
ax.text(0.1, -1.1, 'Note: sample', fontsize=10, color='0.2')
23.5 Patches: rechthoeken, cirkels, highlighters
fig, ax = plt.subplots(figsize=(6, 4))
ax.plot(np.arange(10), np.arange(10)**0.5)
# highlight bereik x in [2,5]
ax.add_patch(Rectangle((2, ax.get_ylim()[0]), width=3, height=np.diff(ax.get_ylim())[0],
color='yellow', alpha=0.2, zorder=0))
# markeer punt met cirkel
ax.add_patch(Circle((6, np.sqrt(6)), radius=0.2, color='C3', alpha=0.5))
23.6 Twin axes en secundaire as met functies
fig, ax1 = plt.subplots()
x = np.linspace(0.1, 10, 200)
y = np.log(x)
ax1.plot(x, y, color='C0'); ax1.set_ylabel('log(x)')
ax2 = ax1.secondary_yaxis('right', functions=(np.exp, np.log))
ax2.set_ylabel('exp(y)')
23.7 Kleine helper‑functies voor herbruikbaarheid
def theme_axes(ax, title=None, xlabel=None, ylabel=None):
ax.grid(True, ls=':', alpha=0.4)
if title: ax.set_title(title)
if xlabel: ax.set_xlabel(xlabel)
if ylabel: ax.set_ylabel(ylabel)
return ax
fig, ax = plt.subplots()
ax.plot(np.linspace(0,1,50), np.linspace(0,1,50)**2, label='x^2')
ax = theme_axes(ax, title='Demo', xlabel='x', ylabel='y')
ax.legend()
23.8 Valkuilen en tips
- Te veel annotaties → visuele ruis. Kies spaarzaam en gericht.
- Inset axes kunnen overlap veroorzaken; gebruik
tight_layout()ofconstrained_layout=Truebij figuur. - Gebruik z‑order om lagen te sturen (lijnen voor patches of omgekeerd).
23.9 Samenvatting
GridSpecen inset/broken axes geven flexibiliteit voor complexe figuren.- Annotaties, patches en secundaire assen maken verhalen duidelijker.
- Bouw kleine helpers voor consistente styling en hergebruik.
Hoofdstuk 24 — Visualisatie‑best practices en data storytelling
Let op: De tekst is in het Nederlands; alle Python codevoorbeelden zijn in het Engels.
Doel van dit hoofdstuk:
- Leren kiezen van geschikte grafiekvormen voor verschillende vragen.
- Best practices voor schaal, kleur, labels, ordening, en reductie van ruis.
- Storytelling‑principes: context, focus, hiërarchie, en begeleidende annotaties.
- Toegankelijkheid: kleurenblindvriendelijke paletten, fontgrootte, contrast.
Aanbevolen voorkennis: Hoofdstuk 21–23.
24.1 Kies de juiste grafiek voor de vraag
- Vergelijking per categorie: staaf (bar), eventueel horizontaal (barh) voor lange labels.
- Verdeling: histogram, KDE (via andere libs), box/violin voor meerdere groepen.
- Relatie: scatter; met maat/kleur voor extra variabelen; hexbins/density bij overplotting.
- Tijd: line (geen staaf), gebruik consistente tijdschaal en duidelijke ticks.
24.2 As‑schalen en baselines
- Begin bij 0 voor staafdiagrammen (misleiding voorkomen), tenzij goed gemotiveerd met annotatie.
- Log‑schalen voor multiplicatieve verhoudingen of grote orders van grootte.
- Vermijd onnodige dubbele y‑assen; als gebruikt, label duidelijk en geef context.
import numpy as np
import matplotlib.pyplot as plt
x = np.logspace(0, 4, 200); y = 1/x
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(8,3))
ax1.plot(x, y); ax1.set_title('Lineair')
ax2.loglog(x, y); ax2.set_title('Log–log')
fig.tight_layout()
24.3 Ordening, sortering en groepering
- Sorteer categorieën op waarde of logische volgorde.
- Groepeer gerelateerde elementen, gebruik tussenkopjes of subtiele grids.
- Vermijd 3D‑staven en onnodige effecten; houd het vlak en leesbaar.
cats = ['C','A','B']; vals = [7,5,3]
order = np.argsort(vals)[::-1]
fig, ax = plt.subplots()
ax.bar(np.array(cats)[order], np.array(vals)[order])
ax.set_title('Gesorteerd')
24.4 Kleurgebruik en paletten
- Gebruik kleur spaarzaam om nadruk te leggen; houd overige elementen grijs/blauwachtig (low‑saturation).
- Kies kleurenblindvriendelijke paletten (bijv. Matplotlib
tab10,tab20). - Gebruik semantische kleur (rood voor negatief, groen voor positief) consistent en met voldoende contrast.
fig, ax = plt.subplots()
vals = [5, 7, 3, 6]
colors = ['0.7','0.7','0.7','C3'] # highlight laatste balk
ax.bar(['Q1','Q2','Q3','Q4'], vals, color=colors)
ax.set_title('Highlight met kleur')
24.5 Labels, annotaties en data‑ink ratio
- Label assen en units; voeg titel en subtitel toe voor context.
- Direct labeling (lijnen voorzien van tekst) voorkomt legende‑sprongen.
- Verhoog de data‑ink ratio: verwijder chartjunk, lichte grids, beperkte ticks.
x = np.linspace(0, 10, 200)
fig, ax = plt.subplots()
ax.plot(x, np.sin(x), color='C0')
ax.plot(x, np.cos(x), color='C1')
ax.text(10.1, np.sin(10), 'sin', color='C0', va='center')
ax.text(10.1, np.cos(10), 'cos', color='C1', va='center')
ax.legend().remove()
ax.grid(True, ls=':', alpha=0.4)
24.6 Onzekerheid tonen: error bars, band en bootstrap CI
- Error bars (standaarddeviatie of standaardfout) op staaf of lijn.
- Confidence band rond een trendlijn.
rng = np.random.default_rng(0)
x = np.linspace(0, 10, 50)
y = np.sin(x) + 0.2*rng.standard_normal(len(x))
fig, ax = plt.subplots()
ax.plot(x, y, color='C0', lw=1)
# Smooth gemiddelde en band (voorbeeld: eenvoudige rollende mean)
import pandas as pd
s = pd.Series(y).rolling(5, center=True, min_periods=1)
mean = s.mean().to_numpy(); std = s.std().fillna(0).to_numpy()
ax.fill_between(x, mean-2*std, mean+2*std, color='C0', alpha=0.2, label='±2σ')
ax.plot(x, mean, color='C0', lw=2, label='mean')
ax.legend()
24.7 Small multiples en facetting
- Breek complexe verhalen op in kleine, vergelijkbare panelen.
- Gebruik gedeelde assen en consistente schalen voor vergelijking.
import pandas as pd
cats = ['A','B','C','D']
fig, axes = plt.subplots(2, 2, figsize=(7,5), sharex=True, sharey=True)
for ax, c in zip(axes.ravel(), cats):
x = np.linspace(0, 10, 100)
ax.plot(x, np.sin(x)+(cats.index(c))*0.3)
ax.set_title(f'Category {c}')
fig.suptitle('Small multiples'); fig.tight_layout()
24.8 Typografie, maatvoering en toegankelijkheid
- Minimale fontgrootte 9–10 pt op scherm; groter voor projectie.
- Gebruik
dpienfigsizebewust; test export (PNG/PDF) op scherpte. - Gebruik markers/linestyles naast kleur om onderscheid te waarborgen.
- Voeg alternatieve teksten/onderschriften toe buiten de figuur (documentatie).
24.9 Storytelling‑structuur
- Begin met vraag of claim; geef context (units, periode, sampleomvang).
- Presenteer de kernvisual met highlight/annotatie.
- Geef nuance (onzekerheid, uitzonderingen) en next steps.
24.10 Checklist voor review
- Is de grafiek het juiste type voor de vraag?
- Zijn assen gelabeld met units? Zijn schalen passend (lineair/log)?
- Is ordening logisch? Is kleurgebruik toegankelijk en betekenisvol?
- Zijn ruis en chartjunk geminimaliseerd? Is onzekerheid duidelijk?
24.11 Samenvatting
- Keuze van grafiektype, schaal en ordening bepaalt begrijpelijkheid.
- Gebruik kleur en annotaties doelgericht, met aandacht voor toegankelijkheid.
- Vertel een verhaal met context, highlight en nuance; vermijd chartjunk.
Hoofdstuk 25 — Integratie met pandas en NumPy; theming en rcParams
Let op: De tekst is in het Nederlands; alle Python codevoorbeelden zijn in het Engels.
Doel van dit hoofdstuk:
- Efficiënt plotten met pandas (
DataFrame.plot,Series.plot) op Matplotlib‑Axes. - NumPy‑integratie:
imshow,contour,pcolormesh, en colormaps. - Theming met styles, rcParams en color cyclers; consistente lay‑out voor projecten.
- Praktische tips voor lettertypen, colormaps, en globale figuurinstellingen.
Aanbevolen voorkennis: Hoofdstuk 21–24, basis pandas/NumPy.
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib as mpl
25.1 pandas plot‑API op Matplotlib‑Axes
pandas bouwt bovenop Matplotlib. Gebruik OO‑stijl door ax= te mee te geven.
# Voorbeeld‑data
rng = np.random.default_rng(0)
dates = pd.date_range('2025-01-01', periods=50, freq='D')
df = pd.DataFrame({
'A': np.cumsum(rng.normal(0, 1, 50)),
'B': np.cumsum(rng.normal(0, 1, 50)),
}, index=dates)
fig, ax = plt.subplots(figsize=(7, 3))
df.plot(ax=ax)
ax.set(title='pandas line plot', xlabel='date', ylabel='value')
ax.legend(title='series')
fig.tight_layout()
Veelgebruikte opties:
kind='bar'|'barh'|'hist'|'box'|'kde'|'area'|'scatter'|'hexbin'.subplots=Trueom elke kolom apart te plotten (terug: array van Axes).layout=(rows, cols)voor subplots raster.sharex=True|False,sharey=True|Falsevoor gedeelde assen.
fig, axes = plt.subplots(1, 2, figsize=(8, 3), sharey=True)
df[['A']].plot(kind='area', alpha=0.4, ax=axes[0])
df[['B']].plot(kind='area', alpha=0.4, ax=axes[1], color='C3')
for ax, t in zip(axes, ['A','B']):
ax.set_title(t)
fig.suptitle('pandas area'); fig.tight_layout()
25.2 pandas scatter en hexbin
N = 2000
xy = pd.DataFrame({
'x': rng.normal(size=N),
'y': rng.normal(size=N)
})
fig, ax = plt.subplots(figsize=(5, 4))
xy.plot(kind='scatter', x='x', y='y', s=10, alpha=0.5, ax=ax)
ax.set_title('pandas scatter')
# Hexbin voor hoge dichtheid
fig, ax = plt.subplots(figsize=(5, 4))
xy.plot(kind='hexbin', x='x', y='y', gridsize=30, cmap='viridis', ax=ax)
ax.set_title('pandas hexbin')
25.3 NumPy‑beelden met imshow/pcolormesh/contour
# 2D rasterdata
gx, gy = np.mgrid[-3:3:0.05, -3:3:0.05]
Z = np.exp(-(gx**2 + gy**2)) * np.cos(2*gx) * np.sin(2*gy)
fig, axes = plt.subplots(1, 3, figsize=(12, 3))
im = axes[0].imshow(Z, origin='lower', cmap='viridis')
fig.colorbar(im, ax=axes[0], shrink=0.8, label='value')
pc = axes[1].pcolormesh(gx, gy, Z, shading='auto', cmap='magma')
fig.colorbar(pc, ax=axes[1], shrink=0.8)
cs = axes[2].contour(gx, gy, Z, levels=12, colors='k', linewidths=0.7)
axes[2].clabel(cs, inline=True, fontsize=8)
for ax in axes:
ax.set(xlabel='x', ylabel='y')
fig.suptitle('NumPy rasterplots'); fig.tight_layout()
Colormap tips:
- Gebruik perceptueel uniforme colormaps (
viridis,magma,cividis). - Gebruik
norm=mpl.colors.LogNorm()voor positieve grootheden met grote range.
25.4 Stylesheet en rcParams voor projectthema
Je kunt projectbrede stijl vastleggen via rcParams of een custom stylesheet.
# Tijdelijk een style toepassen
with plt.style.context('seaborn-v0_8'):
fig, ax = plt.subplots()
df.plot(ax=ax); ax.set_title('Seaborn context')
# rcParams: globaal aanpassen
mpl.rcParams.update({
'figure.dpi': 150,
'axes.grid': True,
'axes.titlesize': 12,
'axes.labelsize': 10,
'legend.frameon': False,
})
Eigen stylebestand (optioneel): maak een .mplstyle file en laad met plt.style.use('pad/naar/custom.mplstyle').
25.5 Color cycler en consistente palettes
from cycler import cycler
cycle = cycler(color=['#1f77b4','#ff7f0e','#2ca02c','#d62728']) + cycler(linestyle=['-','--','-.',':'])
mpl.rcParams['axes.prop_cycle'] = cycle
fig, ax = plt.subplots()
for k in range(4):
ax.plot(np.linspace(0, 1, 50), np.linspace(0, 1, 50)**(k+1), label=f'k={k+1}')
ax.legend(); ax.set_title('Custom cycler')
25.6 Lettertypen en symbolen
# Kies een font familie (let op beschikbaarheid in je systeem)
mpl.rcParams['font.family'] = 'DejaVu Sans'
# Wetenschappelijke notatie en minus teken correct
mpl.rcParams['axes.unicode_minus'] = True
Voor LaTeX‑rendering (optioneel, vereist TeX‑installatie):
# mpl.rcParams['text.usetex'] = True
25.7 Integratiepatronen met pandas → Matplotlib
- Gebruik pandas voor aggregatie/reshaping; plot op door jou aangemaakte Axes.
- Geef
ax=door en zet titels/labels via Matplotlib, niet mixen met pandas titels. - Werk met
constrained_layout=Truebijplt.figure(..., constrained_layout=True)voor automatische layout.
agg = (df.resample('7D').mean()
.rename(columns={'A':'A_mean','B':'B_mean'}))
fig, ax = plt.subplots(figsize=(7,3), constrained_layout=True)
agg.plot(ax=ax)
ax.set(title='7‑daags gemiddelde', xlabel='date', ylabel='value')
25.8 Valkuilen en best practices
- pandas
plot(kind=...)is handig, maar voor maatwerk: werk op Matplotlib‑Axes. - Mix niet te veel automatische styling van pandas met rcParams; kies één pad.
- Standaard colormap
jetvermijden; kies perceptueel uniforme opties.
25.9 Samenvatting
- pandas integreert direct met Matplotlib via
ax=; gebruik het voor snelle exploratie. - NumPy‑rasterdata visualiseer je met
imshow/pcolormesh/contour+ passende colormap/norm. - Stylesheets, rcParams en cyclers zorgen voor consistente theming in projecten.
Hoofdstuk 26 — Exporteren, vector vs raster en publicatie‑kwaliteit figuren
Let op: De tekst is in het Nederlands; alle Python codevoorbeelden zijn in het Engels.
Doel van dit hoofdstuk:
- Begrijpen wanneer je raster (PNG/JPEG) of vector (PDF/SVG) gebruikt.
- DPI, figsize en layout correct instellen voor scherpe, consistente output.
savefig‑opties, transparantie, achtergronden, metadata en fonts beheersen.- Publicatie‑tips voor rapporten, artikelen en presentaties.
Aanbevolen voorkennis: Hoofdstuk 21–25.
26.1 Raster versus vector
- Raster (PNG, JPEG): pixel‑gebaseerd. Voordelen: voorspelbare weergave, goed voor scherm/presentatie; Nadeel: inzoomen geeft pixelatie; tekst is raster.
- Vector (PDF, SVG): schaalbaar zonder kwaliteitsverlies; tekst en lijnen blijven scherp. Nadeel: complexe figuren (miljoenen punten) maken zware bestanden; sommige viewers/renderers verschillen.
- Keuze:
- Scherm/presentatie: PNG (geen compressieartefacten), of SVG als ondersteund.
- Drukwerk/papers: PDF (vector) is vaak vereist.
26.2 Grootte en resolutie: figsize en DPI
Matplotlib meet figuurafmetingen in inches. Pixels = inches * DPI.
import matplotlib.pyplot as plt
fig, ax = plt.subplots(figsize=(6, 4), dpi=150)
ax.plot([0,1],[0,1])
ax.set_title('Voorbeeld')
fig.tight_layout()
fig.savefig('figure-6x4in-150dpi.png', dpi=150, bbox_inches='tight')
Richtlijnen:
- Voor A4 rapport: vaak figuren tussen 4–6 inch breed. Voor twee‑koloms papers: 3.5 inch (enkel kolom) of ~7.2 inch (dubbel kolom).
- DPI 150–200 voor scherm; 300+ voor print. Vectorformaten negeren DPI (behalve embedded rasters binnen de figuur).
26.3 savefig‑opties die je moet kennen
fig.savefig('plot.png', dpi=200, bbox_inches='tight', pad_inches=0.02)
fig.savefig('plot.pdf') # vector
fig.savefig('plot.svg', facecolor='white', transparent=False)
fig.savefig('plot.tiff', dpi=300) # voor sommige publicaties
bbox_inches='tight'voorkomt afgesneden labels/legendes;pad_inchesvoegt marge toe.facecolor/edgecolorvoor achtergrond en rand.transparent=Truemaakt achtergrond transparant (let op leesbaarheid).- Metadata (PDF/SVG):
fig.savefig('plot.pdf', metadata={'Title': 'Mijn figuur', 'Author': 'Team'})
26.4 Achtergrond, transparantie en anti‑aliasing
- Transparante achtergrond (
transparent=True) is handig voor slides over een gekleurde achtergrond. Controleer contrast van tekst/assen. - Anti‑aliasing is standaard aan; lijnen/tekst zijn vloeiend. Voor pixel‑perfecte rasterbeelden kan
antialiased=Falseop specifieke artists.
26.5 Fonts en embedding
- Gebruik een gangbaar font (DejaVu Sans standaard bij Matplotlib). Voor volledige controle: stel rcParams in.
import matplotlib as mpl
mpl.rcParams['font.family'] = 'DejaVu Sans'
mpl.rcParams['pdf.fonttype'] = 42 # Type 42 (TrueType) embedding voor betere compatibiliteit
mpl.rcParams['ps.fonttype'] = 42
mpl.rcParams['svg.fonttype'] = 'none' # bewaar tekst als tekst (niet als paden)
- Sommige tijdschriften eisen specifieke fontinstellingen; test PDF in Acrobat/Preview.
26.6 Rasterization strategie voor zware figuren
Voor figuren met heel veel punten/patches kun je specifieke artists rasterizen in een verder vector‑PDF.
import numpy as np
fig, ax = plt.subplots()
x = np.random.rand(1_000_000); y = np.random.rand(1_000_000)
sc = ax.scatter(x, y, s=1, rasterized=True, alpha=0.5)
fig.savefig('scatter.pdf') # vector PDF met gerasterde scatter‑laag
rasterized=Trueop de artist maakt bestanden kleiner en sneller te renderen.- Gebruik z‑order zodat raster‑laag onder tekst/assen ligt.
26.7 Kleurbeheer en profielen (kort)
- Matplotlib werkt in sRGB. Voor drukwerk kan CMYK vereist zijn; exporteer dan naar PDF en converteer in de publicatietool/drukproces (buiten scope van Matplotlib).
- Zorg voor voldoende contrast; test print‑proef indien mogelijk.
26.8 Layout en marges voor publicatie
- Gebruik
constrained_layout=Trueoftight_layout()maar let op: niet altijd perfect met colorbars/insets. Fine‑tune metsubplots_adjustofgridspec_kw.
fig, ax = plt.subplots(constrained_layout=True, figsize=(6, 4))
# ... plot ...
fig.savefig('pub.png', bbox_inches='tight')
- Houd consistente fontgroottes (titel/labels/ticks) en lijn‑diktes aan; legende zonder rand (
frameon=False) tenzij noodzakelijk.
26.9 Workflowcheck voor publicatie‑kwaliteit
- Bepaal doel en formaat (scherm/print, kolombreedte).
- Stel
figsizeendpi(raster) of gebruik vectorformaat. - Controleer labels, units, leesbaarheid, contrast.
- Minimaliseer chartjunk; zorg voor juiste kleurpalet.
- Exporteer met
bbox_inches='tight', metadata en correcte fontinstellingen. - Open het resultaat in een viewer; zoom in (vector) of bekijk op 100% (raster).
26.10 Praktische voorbeelden
# 1) Slide‑ready PNG
fig, ax = plt.subplots(figsize=(10, 5), dpi=150)
ax.plot(range(10))
fig.savefig('slide.png', dpi=150, bbox_inches='tight')
# 2) Paper‑ready enkelkoloms PDF (~3.5 inch breed)
fig, ax = plt.subplots(figsize=(3.5, 2.2))
ax.plot(range(10))
ax.set_xlabel('x'); ax.set_ylabel('y')
fig.savefig('paper.pdf')
# 3) SVG met transparante achtergrond voor web
fig, ax = plt.subplots(figsize=(6, 4))
ax.scatter(np.linspace(0,1,200), np.linspace(0,1,200)**2, s=8, alpha=0.6)
fig.savefig('web.svg', transparent=True)
26.11 Veelvoorkomende valkuilen
- Afgesneden labels/legendes: gebruik
bbox_inches='tight'en controleertight_layout(). - Wazige afbeeldingen in documenten: verkeerde schaal/DPI. Plaats op 100% of exporteer op doelpixeldimensies.
- Zware vector‑PDF’s door miljoenen punten: rasterize dichte lagen.
- Onverwachte fonts in PDF/SVG: stel fonttype rcParams juist in en test in viewer.
26.12 Samenvatting
- Kies raster of vector passend bij het doel; stel
figsizeen DPI bewust in. - Gebruik
savefig‑opties voor randen, achtergrond, metadata en fonts. - Voor publicatie‑kwaliteit: consistente typografie, contrast, en layout; test de export in de doelflow.
Hoofdstuk 27 — Introductie tot machine learning met scikit‑learn: API‑filosofie
Let op: De tekst is in het Nederlands; alle Python codevoorbeelden zijn in het Engels.
Doelen van dit hoofdstuk:
- Begrijpen van de uniforme API van scikit‑learn (fit/transform/predict/score).
- Leren werken met schatters (Estimators), transformators en pipelines.
- Vertrouwd raken met datasets en train/test‑splits.
Aanbevolen voorkennis: NumPy/pandas (Deel III–IV) en basis statistiek.
Installatie:
pip install scikit-learn numpy pandas
27.1 Kernconcepten van scikit‑learn
- Estimator: elk object met
.fit(X, y=None)dat parameters leert uit data. - Transformer: een Estimator met
.transform(X)(of.fit_transform(X)), bv.StandardScaler. - Predictor: een Estimator met
.predict(X)(en soms.predict_proba(X)). - Scorer: methode
.score(X, y)of losse scoringsfunctie (bv.accuracy_score). - Consistente API:
fit(X, y)leert;transform(X)zet om;predict(X)voorspelt;set_params/get_paramsvoor hyperparameters;- werkt met arrays (NumPy), sparse matrices of pandas DataFrames.
27.2 Dataset laden en train/test‑split
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
import pandas as pd
iris = load_iris(as_frame=True)
X = iris.data # features als DataFrame
y = iris.target # 0,1,2
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y
)
print(X_train.shape, X_test.shape)
27.3 Eerste model: een eenvoudige classifier
from sklearn.linear_model import LogisticRegression
clf = LogisticRegression(max_iter=1000, n_jobs=None)
clf.fit(X_train, y_train)
print('Accuracy (train):', clf.score(X_train, y_train))
print('Accuracy (test): ', clf.score(X_test, y_test))
Belangrijk: gebruik een vaste random_state voor reproduceerbaarheid waar nodig.
27.4 Transformers en Pipelines
Waarom Pipelines?
- Voorkom dat je lekken introduceert (leakage) door transformaties uitsluitend op train uit te voeren en dezelfde transformatie op test toe te passen via de pipeline.
- Combineer preprocessing en een model in één object dat
fit/predictondersteunt.
Voorbeeld met standaardiseren van features:
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
pipe = Pipeline([
('scaler', StandardScaler()),
('model', LogisticRegression(max_iter=1000))
])
pipe.fit(X_train, y_train)
print('Pipeline accuracy (test):', pipe.score(X_test, y_test))
27.5 Hyperparameters en set_params
pipe.set_params(model__C=0.5, model__penalty='l2')
pipe.fit(X_train, y_train)
print(pipe.get_params()['model__C'])
27.6 Evaluatie en cross‑validation (sneak peek)
from sklearn.model_selection import cross_val_score
scores = cross_val_score(pipe, X, y, cv=5, scoring='accuracy')
print('CV accuracy:', scores.mean(), '+/-', scores.std())
27.7 Veelvoorkomende valkuilen
- Data leakage door scaler/encoder op volledige dataset te fitten buiten een pipeline.
- Onjuiste metriek (accuracy bij scheve klassenverdeling); kies passend bij probleem.
- Geen vaste
random_stateen daardoor onstabiele resultaten.
27.8 Samenvatting
- scikit‑learn biedt een uniforme API met Estimators, Transformers, Predictors.
- Splits data in train/test; gebruik Pipelines voor preprocessing + model.
- Gebruik cross‑validation voor robuuste evaluatie en
set_params/get_paramsvoor tunen.
Hoofdstuk 28 — Data voorbereiden: preprocessing, Pipelines en ColumnTransformer
Let op: De tekst is in het Nederlands; alle Python codevoorbeelden zijn in het Engels.
Doelen:
- Standaardiseren, normaliseren en omgaan met missende waarden via transformers.
- Categorische variabelen encoderen met OneHotEncoder.
- Samengestelde preprocessing met ColumnTransformer en Pipelines.
Installatie:
pip install scikit-learn pandas numpy
28.1 Veelgebruikte transformers
- Imputers:
SimpleImputer,KNNImputer. - Schalen/normaliseren:
StandardScaler,MinMaxScaler,RobustScaler. - Encoders:
OneHotEncoder,OrdinalEncoder. - Feature selectors:
SelectKBest,VarianceThreshold.
28.2 Voorbeeld dataset en missende waarden
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
rng = np.random.default_rng(42)
N = 200
X = pd.DataFrame({
'age': rng.integers(18, 80, size=N),
'income': rng.normal(50000, 15000, size=N),
'city': rng.choice(['Ams', 'Rtm', 'Utr'], size=N),
'owns_house': rng.choice([0, 1], size=N)
})
# introduceer missend
X.loc[rng.choice(X.index, size=20, replace=False), 'income'] = np.nan
y = (X['age'] + (X['income'].fillna(50000)/1000) + 10*X['owns_house'] > 90).astype(int)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=0, stratify=y)
28.3 Kolommen scheiden in numeriek en categorisch
num_cols = ['age', 'income']
cat_cols = ['city', 'owns_house'] # owns_house als categorie (0/1)
28.4 ColumnTransformer met Pipelines
from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import OneHotEncoder, StandardScaler
from sklearn.impute import SimpleImputer
from sklearn.linear_model import LogisticRegression
numeric_pipeline = Pipeline([
('imputer', SimpleImputer(strategy='median')),
('scaler', StandardScaler())
])
categorical_pipeline = Pipeline([
('imputer', SimpleImputer(strategy='most_frequent')),
('onehot', OneHotEncoder(handle_unknown='ignore'))
])
preprocess = ColumnTransformer([
('num', numeric_pipeline, num_cols),
('cat', categorical_pipeline, cat_cols)
])
clf = Pipeline([
('preprocess', preprocess),
('model', LogisticRegression(max_iter=1000))
])
clf.fit(X_train, y_train)
print('Accuracy (test):', clf.score(X_test, y_test))
28.5 Alternatieven en tips
- Voor robuustheid bij outliers:
RobustScaler. - Voor hoge cardinaliteit: overweeg target encoding (niet in scikit‑learn core) of hashing (
FeatureHasher). remainder='passthrough'inColumnTransformerom niet‑gespecificeerde kolommen door te laten.
28.6 Lekken voorkomen
- Fit alle preprocessing uitsluitend binnen de pipeline op
X_train. - Gebruik
cross_val_scoreop de volledige pipeline i.p.v. op voorbewerkte arrays.
28.7 Samenvatting
- Combineer Imputer/Scaler/Encoder per kolomgroep in Pipelines.
- Verbind deze met ColumnTransformer en sluit aan op een eindmodel.
- Train/evalueer altijd de gehele pipeline.
Hoofdstuk 29 — Supervised learning: regressie (LinearRegression, Ridge, Lasso)
Let op: De tekst is in het Nederlands; alle Python codevoorbeelden zijn in het Engels.
Doelen:
- Begrijpen van lineaire regressie en regularisatie (L2 Ridge, L1 Lasso).
- Train/test‑split, metriekkeuze (MSE, MAE, R^2) en feature scaling.
Installatie:
pip install scikit-learn numpy pandas
29.1 Data genereren of laden
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
rng = np.random.default_rng(0)
X = pd.DataFrame({
'x1': rng.normal(size=300),
'x2': rng.normal(size=300),
'x3': rng.normal(size=300)
})
# Waarheid: y = 3*x1 - 2*x2 + noise; x3 is ruis
y = 3*X['x1'] - 2*X['x2'] + rng.normal(scale=0.5, size=300)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
29.2 Lineaire regressie
from sklearn.linear_model import LinearRegression
lin = LinearRegression()
lin.fit(X_train, y_train)
print('R^2 (train):', lin.score(X_train, y_train))
print('R^2 (test): ', lin.score(X_test, y_test))
print('Coefficients:', lin.coef_, 'Intercept:', lin.intercept_)
29.3 Ridge en Lasso
Regularisatie helpt overfitting voorkomen en kan stabiliteit verhogen.
from sklearn.linear_model import Ridge, Lasso
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
ridge = Pipeline([
('scaler', StandardScaler()),
('model', Ridge(alpha=1.0, random_state=42))
]).fit(X_train, y_train)
lasso = Pipeline([
('scaler', StandardScaler()),
('model', Lasso(alpha=0.1, random_state=42, max_iter=10000))
]).fit(X_train, y_train)
print('Ridge R^2 (test):', ridge.score(X_test, y_test))
print('Lasso R^2 (test):', lasso.score(X_test, y_test))
29.4 Metrieken: MSE, MAE, R^2
from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score
pred = lin.predict(X_test)
print('MSE:', mean_squared_error(y_test, pred))
print('MAE:', mean_absolute_error(y_test, pred))
print('R^2:', r2_score(y_test, pred))
29.5 Feature selectie met Lasso
coef = lasso.named_steps['model'].coef_
print(coef)
# Coefs die exact 0 zijn, worden effectief verwijderd
29.6 Cross‑validation varianten
from sklearn.model_selection import cross_val_score
scores = cross_val_score(ridge, X, y, cv=5, scoring='r2')
print('CV R^2:', scores.mean())
29.7 Samenvatting
- LinearRegression voor basismodellen; Ridge/Lasso voor regularisatie.
- Schaal features bij Ridge/Lasso voor consistente regulering.
- Evalueer met passende regressiemetrieken en gebruik CV voor robuustheid.
Hoofdstuk 30 — Supervised learning: classificatie (LogisticRegression, SVM, Decision Trees)
Let op: De tekst is in het Nederlands; alle Python codevoorbeelden zijn in het Engels.
Doelen:
- Basis van classificatieproblemen, decision boundaries, probabilistische output.
- Vergelijken van drie klassiekers: Logistic Regression, SVM en Decision Tree.
- Evaluatie met accuracy, precision/recall/F1 en confusion matrix.
Installatie:
pip install scikit-learn numpy pandas matplotlib
30.1 Voorbeelddata en splitsing
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
X, y = load_breast_cancer(return_X_y=True, as_frame=True)
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y
)
30.2 Logistic Regression
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
logreg = Pipeline([
('scaler', StandardScaler()),
('clf', LogisticRegression(max_iter=2000))
])
logreg.fit(X_train, y_train)
print('LogReg accuracy:', logreg.score(X_test, y_test))
30.3 Support Vector Machine (SVM)
from sklearn.svm import SVC
svm = Pipeline([
('scaler', StandardScaler()),
('clf', SVC(kernel='rbf', probability=True, random_state=42))
]).fit(X_train, y_train)
print('SVM accuracy:', svm.score(X_test, y_test))
30.4 Decision Tree
from sklearn.tree import DecisionTreeClassifier
tree = DecisionTreeClassifier(max_depth=4, random_state=42)
tree.fit(X_train, y_train)
print('Tree accuracy:', tree.score(X_test, y_test))
30.5 Evaluatiemetrieken
from sklearn.metrics import classification_report, confusion_matrix
for name, model in [('LogReg', logreg), ('SVM', svm), ('Tree', tree)]:
y_pred = model.predict(X_test)
print(name)
print(confusion_matrix(y_test, y_pred))
print(classification_report(y_test, y_pred, digits=3))
30.6 Probabilities en ROC‑AUC
from sklearn.metrics import roc_auc_score, RocCurveDisplay
import matplotlib.pyplot as plt
y_prob = logreg.predict_proba(X_test)[:, 1]
auc = roc_auc_score(y_test, y_prob)
print('ROC‑AUC:', auc)
RocCurveDisplay.from_estimator(logreg, X_test, y_test)
plt.show()
30.7 Samenvatting
- Logistic Regression: lineair, snel, goed baseline‑model.
- SVM: krachtig met kernels, vaak sterke performance; let op schaal en hyperparameters.
- Decision Trees: interpreteerbaar, weinig preprocessing nodig, maar kunnen overfitten.
Hoofdstuk 31 — Modelselectie en evaluatie: cross‑validation, metrics en GridSearchCV
Let op: De tekst is in het Nederlands; alle Python codevoorbeelden zijn in het Engels.
Doelen:
- Cross‑validation begrijpen en toepassen (KFold, StratifiedKFold, GroupKFold).
- Juiste metriek kiezen en meerdere metriekken rapporteren.
- Hyperparameter tuning met GridSearchCV en RandomizedSearchCV.
Installatie:
pip install scikit-learn numpy pandas
31.1 Cross‑validation basics
from sklearn.model_selection import cross_val_score
from sklearn.linear_model import LogisticRegression
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.datasets import load_breast_cancer
X, y = load_breast_cancer(return_X_y=True)
pipe = Pipeline([
('scaler', StandardScaler()),
('clf', LogisticRegression(max_iter=2000))
])
scores = cross_val_score(pipe, X, y, cv=5, scoring='accuracy')
print('Accuracy 5‑fold:', scores.mean(), '+/-', scores.std())
31.2 StratifiedKFold en meerdere metrics
from sklearn.model_selection import StratifiedKFold, cross_validate
from sklearn.metrics import make_scorer, f1_score, roc_auc_score
cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
res = cross_validate(
pipe, X, y, cv=cv,
scoring={'acc':'accuracy', 'f1':make_scorer(f1_score), 'auc':'roc_auc'},
return_train_score=False
)
print({k: res[f'test_{k}'].mean() for k in ['acc','f1','auc']})
31.3 GridSearchCV
from sklearn.model_selection import GridSearchCV
param_grid = {
'clf__C': [0.1, 1.0, 10.0],
'clf__penalty': ['l2'],
'clf__solver': ['lbfgs', 'liblinear']
}
grid = GridSearchCV(pipe, param_grid=param_grid, cv=5, scoring='roc_auc', n_jobs=-1)
grid.fit(X, y)
print('Best params:', grid.best_params_)
print('Best score:', grid.best_score_)
31.4 RandomizedSearchCV en distributions
from sklearn.model_selection import RandomizedSearchCV
from scipy.stats import loguniform
param_dist = {
'clf__C': loguniform(1e-3, 1e2)
}
rand = RandomizedSearchCV(pipe, param_distributions=param_dist, n_iter=20, cv=5, scoring='roc_auc', random_state=42)
rand.fit(X, y)
print('Best params:', rand.best_params_)
31.5 Groeps‑CV (kort)
from sklearn.model_selection import GroupKFold
import numpy as np
X_small = X[:200]
y_small = y[:200]
# Stel dat elke 20 rijen tot dezelfde groep behoren
groups = np.repeat(np.arange(10), 20)
cv = GroupKFold(n_splits=5)
for train_idx, test_idx in cv.split(X_small, y_small, groups=groups):
pass # normal fit/eval flow
31.6 Samenvatting
- Gebruik CV om variantie te reduceren en robuuste schattingen te krijgen.
- Rapporteer meerdere relevante metriekken.
- Stem hyperparameters af met GridSearchCV of RandomizedSearchCV; houd data‑lekken tegen met pipelines.
Hoofdstuk 32 — Ongecontroleerd leren: clustering (KMeans) en dimensionality reduction (PCA)
Let op: De tekst is in het Nederlands; alle Python codevoorbeelden zijn in het Engels.
Doelen:
- Begrijpen van ongecontroleerd leren: doelen en beperkingen.
- KMeans gebruiken voor clustering en evalueren met inertia en silhouette.
- PCA toepassen voor dimensionality reduction en visualisatie.
Installatie:
pip install scikit-learn numpy pandas matplotlib
32.1 Dataset en standaardisatie
from sklearn.datasets import load_iris
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
X, y = load_iris(return_X_y=True)
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
32.2 KMeans clustering
from sklearn.cluster import KMeans
kmeans = KMeans(n_clusters=3, n_init='auto', random_state=42)
labels = kmeans.fit_predict(X_scaled)
print('Inertia:', kmeans.inertia_)
32.3 Evaluatie met silhouette score
from sklearn.metrics import silhouette_score
sil = silhouette_score(X_scaled, labels)
print('Silhouette score:', sil)
32.4 PCA voor reductie en visualisatie
from sklearn.decomposition import PCA
import matplotlib.pyplot as plt
pca = PCA(n_components=2, random_state=42)
X2 = pca.fit_transform(X_scaled)
print('Explained variance ratio:', pca.explained_variance_ratio_.round(3))
plt.scatter(X2[:, 0], X2[:, 1], c=labels, cmap='tab10', s=40)
plt.xlabel('PC1'); plt.ylabel('PC2'); plt.title('KMeans clusters (PCA 2D)')
plt.show()
32.5 Het kiezen van k (elleboog‑methode)
import numpy as np
inertias = []
for k in range(2, 8):
km = KMeans(n_clusters=k, n_init='auto', random_state=42).fit(X_scaled)
inertias.append(km.inertia_)
print(inertias)
# Plot inertias versus k en zoek een knikpunt (elleboog)
32.6 Samenvatting
- KMeans groepeert data op basis van afstand; kies k zorgvuldig.
- Silhouette score beoordeelt clusterkwaliteit (hoger is beter).
- PCA reduceert dimensies en helpt visualiseren; verlies aan informatie is mogelijk.
Hoofdstuk 33 — Feature engineering, leakage‑valkuilen en model interpretatie (Permutation Importance, SHAP)
Let op: De tekst is in het Nederlands; alle Python codevoorbeelden zijn in het Engels.
Doelen:
- Principes van feature engineering: interacties, niet‑lineariteit, datum/tijd features.
- Dataleakage herkennen en voorkomen.
- Modelinterpretatie met Permutation Importance; korte introductie tot SHAP.
Installatie:
pip install scikit-learn numpy pandas shap
Opmerking: shap kan extra afhankelijkheden vereisen en is soms traag; optioneel.
33.1 Feature engineering principes
- Begin simpel; laat het model aangeven waar winst te behalen valt.
- Gebruik
PolynomialFeaturesvoor interacties/niet‑lineariteit in lineaire modellen. - Maak zinvolle domeinspecifieke features (ratio’s, aggregaties, datum‑extracties).
import pandas as pd
from sklearn.preprocessing import PolynomialFeatures
from sklearn.pipeline import Pipeline
from sklearn.linear_model import LinearRegression
from sklearn.model_selection import train_test_split
# Speeldata met niet‑lineariteit
df = pd.DataFrame({'x': range(100)})
df['y'] = 0.1*df['x']**2 - 2*df['x'] + 5
X = df[['x']]; y = df['y']
poly_model = Pipeline([
('poly', PolynomialFeatures(degree=2, include_bias=False)),
('lin', LinearRegression())
])
X_tr, X_te, y_tr, y_te = train_test_split(X, y, test_size=0.2, random_state=42)
poly_model.fit(X_tr, y_tr)
print('R^2 (test):', poly_model.score(X_te, y_te))
33.2 Leakage‑valkuilen
- Imputers/scalers op de hele dataset fitten voor de split of buiten pipelines.
- Doelfeatures die afgeleid zijn van y of informatie uit de toekomst (tijdreeksen).
- Target encoding zonder CV‑schema.
Checklist:
- Gebruik Pipelines en voer alle preprocessing binnen CV/splits uit.
- Voor tijdreeksen: TimeSeriesSplit en alleen verleden gebruiken.
33.3 Permutation Importance
Permutation Importance meet de afname in performance wanneer een feature willekeurig wordt geschud.
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
from sklearn.inspection import permutation_importance
import pandas as pd
X, y = load_breast_cancer(return_X_y=True, as_frame=True)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42, stratify=y)
pipe = Pipeline([
('scaler', StandardScaler()),
('clf', LogisticRegression(max_iter=2000))
]).fit(X_train, y_train)
result = permutation_importance(pipe, X_test, y_test, scoring='roc_auc', n_repeats=10, random_state=42)
importances = pd.Series(result.importances_mean, index=X.columns).sort_values(ascending=False)
print(importances.head(10))
33.4 SHAP (kort)
import shap
# Voor lineaire/logistische modellen werkt KernelExplainer of LinearExplainer
explainer = shap.LinearExplainer(pipe.named_steps['clf'], shap.sample(X_train, 200))
shap_values = explainer.shap_values(shap.sample(X_test, 200))
shap.summary_plot(shap_values, shap.sample(X_test, 200), plot_type='bar')
Let op: SHAP vereist toegang tot onderliggende modelinvoer (na scaling/encoding); voor pipelines is extra werk nodig om getransformeerde features te reconstrueren.
33.5 Samenvatting
- Feature engineering kan prestaties sterk beïnvloeden; voorkom lekken met correcte workflows.
- Permutation Importance is model‑agnostisch en werkt goed met pipelines.
- SHAP geeft gedetailleerde lokale en globale uitleg, maar kan zwaar zijn.