Skip to content

Data Cleaning & Preprocessing

Data cleaning en preprocessing vormen een groot deel van elk AI-project. Ruwe data is zelden direct bruikbaar — je moet ontbrekende waarden oplossen, features transformeren en de data in het juiste formaat krijgen voor je model.


Werken met Pandas

Pandas is de standaard Python-library voor datamanipulatie. Hieronder de meest gebruikte operaties.

Data inladen en verkennen

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
import pandas as pd

# Inladen
df = pd.read_csv("data.csv")

# Eerste indruk
df.head()           # Eerste 5 rijen
df.shape            # (rijen, kolommen)
df.info()           # Datatypes en missing values
df.describe()       # Statistische samenvatting
df.columns          # Kolomnamen

Selecteren en filteren

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# Kolom selecteren
df["kolom_naam"]
df[["kolom1", "kolom2"]]

# Rijen filteren
df[df["leeftijd"] > 30]
df[(df["leeftijd"] > 30) & (df["stad"] == "Amsterdam")]

# Specifieke cel
df.loc[5, "kolom_naam"]     # Op label
df.iloc[5, 2]               # Op positie

Aanpassen en toevoegen

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# Nieuwe kolom
df["nieuwe_kolom"] = df["kolom1"] + df["kolom2"]

# Kolom verwijderen
df = df.drop(columns=["ongewenste_kolom"])

# Waarden vervangen
df["status"] = df["status"].replace({"oud": "nieuw"})

# Datatype wijzigen
df["kolom"] = df["kolom"].astype(float)

Oplossen van missing values

Ontbrekende waarden (NaN) komen in bijna elke dataset voor. De aanpak hangt af van hoeveel data ontbreekt en waarom.

Detecteren

1
2
3
4
5
6
7
8
# Hoeveel missing per kolom
df.isnull().sum()

# Percentage missing
df.isnull().sum() / len(df) * 100

# Rijen met missing values
df[df.isnull().any(axis=1)]

Strategieën

Strategie Wanneer toepassen Code
Verwijderen (rijen) Weinig missing, willekeurig verdeeld df.dropna()
Verwijderen (kolommen) Kolom heeft >50% missing df.drop(columns=["kolom"])
Vullen met gemiddelde Numeriek, willekeurig missing df["kolom"].fillna(df["kolom"].mean())
Vullen met mediaan Numeriek met uitschieters df["kolom"].fillna(df["kolom"].median())
Vullen met modus Categorisch df["kolom"].fillna(df["kolom"].mode()[0])
Forward/backward fill Tijdreeksen df["kolom"].fillna(method="ffill")
Voorspellende imputatie Patronen in data nuttig Gebruik KNNImputer of IterativeImputer
1
2
3
4
5
6
7
8
9
from sklearn.impute import SimpleImputer, KNNImputer

# Simpele imputatie
imputer = SimpleImputer(strategy="mean")
df[["kolom"]] = imputer.fit_transform(df[["kolom"]])

# KNN imputatie
knn_imputer = KNNImputer(n_neighbors=5)
df_imputed = knn_imputer.fit_transform(df)

!!! warning "Let op" Analyseer altijd waarom waarden ontbreken. Als data niet willekeurig ontbreekt (bijv. mensen met laag inkomen vullen inkomensvraag niet in), kan imputatie je analyse vertekenen.


One-Hot Encoding

Veel ML-modellen werken alleen met numerieke data. One-hot encoding zet categorische variabelen om naar binaire kolommen.

Voorbeeld

Kleur Kleur_Rood Kleur_Blauw Kleur_Groen
Rood 1 0 0
Blauw 0 1 0
Groen 0 0 1

Implementatie

1
2
3
4
5
6
7
8
# Met Pandas
df_encoded = pd.get_dummies(df, columns=["kleur"], prefix="kleur")

# Met scikit-learn
from sklearn.preprocessing import OneHotEncoder

encoder = OneHotEncoder(sparse=False, handle_unknown="ignore")
encoded = encoder.fit_transform(df[["kleur"]])

Wanneer wel/niet gebruiken

Situatie Aanpak
Categorieën zonder rangorde (nominaal) One-hot encoding
Categorieën met rangorde (ordinaal) Label encoding (0, 1, 2, ...)
Zeer veel categorieën (>20) Target encoding of embeddings
Tree-based modellen Label encoding vaak voldoende

!!! tip "Drop first" Bij one-hot encoding kun je één kolom weglaten om multicollineariteit te voorkomen: pd.get_dummies(df, drop_first=True)


Normalisatie en Standaardisatie

Veel modellen presteren beter als features op dezelfde schaal liggen.

Methoden

Methode Formule Bereik Wanneer gebruiken
Min-Max Normalisatie $(x - min) / (max - min)$ [0, 1] Geen uitschieters, bounded output nodig
Standaardisatie (Z-score) $(x - \mu) / \sigma$ Geen vast bereik Normaal verdeelde data, uitschieters aanwezig
Robust Scaling $(x - mediaan) / IQR$ Geen vast bereik Veel uitschieters

Implementatie

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
from sklearn.preprocessing import MinMaxScaler, StandardScaler, RobustScaler

# Min-Max
scaler = MinMaxScaler()
df_scaled = scaler.fit_transform(df[["kolom1", "kolom2"]])

# Standaardisatie
scaler = StandardScaler()
df_scaled = scaler.fit_transform(df[["kolom1", "kolom2"]])

# Robust
scaler = RobustScaler()
df_scaled = scaler.fit_transform(df[["kolom1", "kolom2"]])

!!! warning "Fit op training, transform op test" Fit de scaler alleen op trainingsdata om data leakage te voorkomen:

1
2
3
scaler.fit(X_train)
X_train_scaled = scaler.transform(X_train)
X_test_scaled = scaler.transform(X_test)

Train, Validatie en Test sets

Je data moet worden opgesplitst om je model eerlijk te evalueren.

De drie sets

Set Doel Typisch percentage
Training Model trainen 60-80%
Validatie Hyperparameters tunen, modelkeuze 10-20%
Test Finale, onpartijdige evaluatie 10-20%

Implementatie

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
from sklearn.model_selection import train_test_split

# Eerst train+val vs test
X_temp, X_test, y_temp, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42
)

# Dan train vs val
X_train, X_val, y_train, y_val = train_test_split(
    X_temp, y_temp, test_size=0.25, random_state=42  # 0.25 van 0.8 = 0.2
)

Cross-validation

Bij beperkte data kun je k-fold cross-validation gebruiken:

1
2
3
4
from sklearn.model_selection import cross_val_score

scores = cross_val_score(model, X, y, cv=5)  # 5-fold
print(f"Gemiddelde score: {scores.mean():.3f} (+/- {scores.std():.3f})")

!!! note "Stratified split" Bij ongebalanceerde classificatie, gebruik stratify=y om de klasseverdeling te behouden:

1
2
3
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, stratify=y, random_state=42
)

Feature Creation

Feature creation (of feature engineering) is het maken van nieuwe features uit bestaande data om het model te helpen patronen te vinden.

Technieken

Techniek Voorbeeld
Combineren df["bmi"] = df["gewicht"] / (df["lengte"] ** 2)
Datum extractie Dag van de week, maand, seizoen uit datetime
Binning Leeftijd → leeftijdsgroepen (18-25, 26-35, ...)
Aggregatie Gemiddelde aankoop per klant over laatste 30 dagen
Text features Woordenfrequentie, tekstlengte, sentiment
Interacties df["feature1_x_feature2"] = df["f1"] * df["f2"]
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# Datum features
df["dag_van_week"] = df["datum"].dt.dayofweek
df["maand"] = df["datum"].dt.month
df["is_weekend"] = df["dag_van_week"].isin([5, 6]).astype(int)

# Binning
df["leeftijdsgroep"] = pd.cut(
    df["leeftijd"], 
    bins=[0, 18, 30, 50, 100], 
    labels=["kind", "jong", "midden", "senior"]
)

PCA (Principal Component Analysis)

PCA is een techniek voor dimensiereductie: het reduceert het aantal features terwijl zoveel mogelijk informatie behouden blijft.

Wanneer gebruiken?

  • Veel features (hoge dimensionaliteit)
  • Features zijn gecorreleerd
  • Visualisatie van hoog-dimensionale data
  • Snellere training door minder features

Hoe werkt het (simpel)?

  1. PCA vindt de richtingen (componenten) waarin de data het meeste varieert
  2. De eerste component vangt de meeste variantie, de tweede de op-één-na meeste, etc.
  3. Je behoudt alleen de eerste N componenten

Implementatie

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
from sklearn.decomposition import PCA
from sklearn.preprocessing import StandardScaler

# Eerst standaardiseren (belangrijk voor PCA)
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)

# PCA toepassen
pca = PCA(n_components=0.95)  # Behoud 95% van de variantie
X_pca = pca.fit_transform(X_scaled)

print(f"Origineel: {X.shape[1]} features")
print(f"Na PCA: {X_pca.shape[1]} componenten")

!!! tip "Hoeveel componenten?" - Gebruik n_components=0.95 om 95% van de variantie te behouden - Of plot de "explained variance ratio" om een knik te vinden


Under- en Oversampling

Bij ongebalanceerde datasets (bijv. 95% negatief, 5% positief) kan je model de minderheidsklasse negeren.

Technieken

Techniek Beschrijving Risico
Undersampling Verwijder voorbeelden uit de meerderheidsklasse Verlies van informatie
Oversampling Dupliceer voorbeelden uit de minderheidsklasse Overfitting
SMOTE Genereer synthetische voorbeelden voor minderheidsklasse Kan ruis introduceren

Implementatie

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
from imblearn.over_sampling import SMOTE, RandomOverSampler
from imblearn.under_sampling import RandomUnderSampler

# Random oversampling
ros = RandomOverSampler(random_state=42)
X_resampled, y_resampled = ros.fit_resample(X_train, y_train)

# SMOTE
smote = SMOTE(random_state=42)
X_resampled, y_resampled = smote.fit_resample(X_train, y_train)

# Random undersampling
rus = RandomUnderSampler(random_state=42)
X_resampled, y_resampled = rus.fit_resample(X_train, y_train)

!!! warning "Alleen op trainingsdata" Pas sampling alleen toe op de trainingsdata, nooit op validatie of test. Anders krijg je een vertekend beeld van de werkelijke performance.


Data Augmentatie

Data augmentatie vergroot je dataset door variaties van bestaande data te maken. Dit is vooral populair bij beelddata.

Beeldaugmentatie

Techniek Beschrijving
Rotatie Beeld draaien (bijv. -15° tot +15°)
Flip Horizontaal of verticaal spiegelen
Crop Willekeurig deel van het beeld uitsnijden
Zoom In- of uitzoomen
Brightness/Contrast Helderheid en contrast aanpassen
Noise Ruis toevoegen
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
from tensorflow.keras.preprocessing.image import ImageDataGenerator

datagen = ImageDataGenerator(
    rotation_range=20,
    width_shift_range=0.2,
    height_shift_range=0.2,
    horizontal_flip=True,
    zoom_range=0.2
)

# Augmented batches genereren
augmented_images = datagen.flow(X_train, y_train, batch_size=32)

Tekstaugmentatie

Techniek Beschrijving
Synonym replacement Woorden vervangen door synoniemen
Random insertion Willekeurige woorden invoegen
Random deletion Willekeurige woorden verwijderen
Back-translation Vertalen naar andere taal en terug

Synthetische Data

Synthetische data is kunstmatig gegenereerde data die de eigenschappen van echte data nabootst.

Toepassingen

Toepassing Reden
Privacy Echte data bevat gevoelige informatie
Schaarste Te weinig echte data beschikbaar
Balancering Minderheidsklasse aanvullen
Testing Edge cases simuleren

Generatiemethoden

Methode Beschrijving
Statistische methoden Sampling uit geschatte verdelingen
SMOTE Interpolatie tussen bestaande datapunten
GANs Generative Adversarial Networks voor realistische data
VAEs Variational Autoencoders
LLMs Taalmodellen voor tekstgeneratie
1
2
3
4
5
6
7
8
9
# Simpele synthetische data met numpy
import numpy as np

# Genereer data die lijkt op originele verdeling
synthetic_data = np.random.normal(
    loc=original_data.mean(),
    scale=original_data.std(),
    size=(1000, original_data.shape[1])
)

Aandachtspunten

Aandachtspunt Toelichting
Kwaliteitscontrole Check of synthetische data realistische patronen heeft
Geen nieuwe informatie Synthetische data kan geen patronen bevatten die niet in originele data zitten
Privacy niet gegarandeerd Sommige methoden kunnen originele datapunten "onthouden"
Validatie apart Test altijd op echte data, niet op synthetische