Data Opschonen met Pandas 3.0: De Complete Gids met Praktische Voorbeelden

Leer data opschonen met Pandas 3.0 — van ontbrekende waarden en duplicaten tot uitschieters en herbruikbare cleaning pipelines. Inclusief werkende Python-codevoorbeelden die je direct kunt gebruiken.

Waarom Data Opschonen de Belangrijkste Stap in Je Data-Analyse Is

Vraag het aan elke data scientist en je krijgt hetzelfde antwoord: 80% van hun tijd gaat naar het opschonen en voorbereiden van data. Niet naar het bouwen van fancy machine learning modellen of het maken van mooie visualisaties — nee, gewoon naar het in orde brengen van rommelige, onvolledige en inconsistente datasets.

En eerlijk? Dat is volkomen logisch.

Want als je data niet klopt, klopt je analyse ook niet. Garbage in, garbage out — het is een cliché, maar het is gewoon waar. Een ML-model dat getraind is op vervuilde data levert onbetrouwbare voorspellingen. Een dashboard dat gebouwd is op inconsistente cijfers geeft misleidende inzichten. Ik heb het zelf meegemaakt bij een project waar we weken aan een voorspelmodel werkten, om er uiteindelijk achter te komen dat de brondata vol zat met dubbele entries.

In deze gids leer je alles over data opschonen met Pandas 3.0 — de versie die in januari 2026 is uitgebracht. We lopen elke stap door, van het opsporen van problemen tot het bouwen van een geautomatiseerde cleaning pipeline. En ja, er zitten werkende codevoorbeelden bij die je direct kunt kopiëren en aanpassen.

Je Omgeving Opzetten: Pandas 3.0 Installeren

Voordat we de diepte in gaan, moet je even zorgen dat je de nieuwste versie van Pandas hebt draaien. Pandas 3.0 brengt namelijk een aantal veranderingen mee die direct invloed hebben op hoe je data opschoont.

# Pandas 3.0 installeren of upgraden
pip install pandas --upgrade

# Versie controleren
import pandas as pd
print(pd.__version__)  # Moet 3.0.x tonen

Wat is er nieuw in Pandas 3.0 voor data cleaning?

Drie veranderingen vallen op:

  • Copy-on-Write is nu standaard: In eerdere versies kon je per ongeluk je originele DataFrame wijzigen via een view. Dat is verleden tijd. Elke bewerking retourneert nu een kopie, tenzij je expliciet anders aangeeft. Eindelijk geen subtiele bugs meer bij het opschonen.
  • Nieuw standaard StringDtype: Strings worden niet langer opgeslagen als het trage object-type. Het nieuwe StringDtype is sneller, geheugenefficiënter en type-veilig. Best een fijne verbetering als je veel met tekstkolommen werkt.
  • pd.col() expressie-API: Hiermee kun je kolomoperaties schrijven die beter leesbaar zijn, vooral bij method chaining in cleaning pipelines.

Stap 1: Je Data Laden en Verkennen

Het eerste wat je altijd moet doen — nog vóór je ook maar één waarde aanpast — is je data verkennen. Je moet snappen wat er mis is voordat je het kunt repareren. Klinkt voor de hand liggend, maar je zou versteld staan hoe vaak mensen deze stap overslaan.

import pandas as pd
import numpy as np

# Voorbeelddata laden (vervang door jouw bestand)
df = pd.read_csv("verkopen_2026.csv")

# Overzicht van de dataset
print(f"Aantal rijen: {df.shape[0]}")
print(f"Aantal kolommen: {df.shape[1]}")
print(f"Geheugengebruik: {df.memory_usage(deep=True).sum() / 1024**2:.1f} MB")
print()

# Datatypes en ontbrekende waarden
print(df.info())
print()

# Statistische samenvatting
print(df.describe())
print()

# Eerste paar rijen bekijken
print(df.head(10))

Waar moet je op letten bij de verkenning?

Bij het verkennen van een nieuwe dataset let ik altijd op deze signalen:

  1. Ontbrekende waarden: Hoeveel NaN-waarden heeft elke kolom? Zit er een patroon in?
  2. Datatypes: Staan getallen opgeslagen als strings? Zijn datums correct herkend?
  3. Bereik van waarden: Zijn er onmogelijke waarden? Denk aan negatieve leeftijden of bedragen van miljoenen waar je duizenden verwacht.
  4. Duplicaten: Zijn er rijen die exact hetzelfde zijn?
  5. Consistentie: Worden dezelfde categorieën op verschillende manieren geschreven?
# Snelle diagnose: ontbrekende waarden per kolom
ontbrekend = df.isnull().sum()
ontbrekend_pct = (df.isnull().sum() / len(df) * 100).round(1)
diagnose = pd.DataFrame({
    "Ontbrekend": ontbrekend,
    "Percentage": ontbrekend_pct
})
print(diagnose[diagnose["Ontbrekend"] > 0].sort_values("Percentage", ascending=False))

Stap 2: Ontbrekende Waarden Behandelen

Ontbrekende waarden zijn waarschijnlijk het meest voorkomende probleem dat je tegenkomt. Ze ontstaan door fouten bij het invoeren van data, systeemuitval, of simpelweg omdat bepaalde info niet beschikbaar was.

Detecteren van ontbrekende waarden

Pandas herkent NaN, None en pd.NA als ontbrekende waarden. Maar pas op: soms worden ontbrekende waarden vermomd als strings zoals "n/a", "N/A", "-", "onbekend" of lege strings. Die moet je er zelf uit vissen.

# Ontbrekende waarden detecteren, inclusief verborgen placeholders
placeholders = ["n/a", "N/A", "nvt", "-", "onbekend", "geen", ""]

# Bij het laden al aangeven welke waarden als NaN moeten worden gezien
df = pd.read_csv("verkopen_2026.csv", na_values=placeholders)

# Of achteraf verborgen placeholders vervangen
df = df.replace(placeholders, np.nan)

# Totaaloverzicht
print(f"Totaal ontbrekende waarden: {df.isnull().sum().sum()}")
print(f"Percentage ontbrekend: {(df.isnull().sum().sum() / df.size * 100):.1f}%")

Strategie 1: Rijen of kolommen verwijderen

De eenvoudigste aanpak, maar ook de meest drastische. Gebruik dit alleen als het percentage ontbrekende waarden klein is en ze willekeurig verdeeld zijn. Anders gooi je misschien waardevolle data weg.

# Rijen verwijderen waar ALLE waarden ontbreken
df = df.dropna(how="all")

# Rijen verwijderen waar specifieke kolommen ontbreken
df = df.dropna(subset=["klant_id", "bedrag"])

# Kolommen verwijderen met meer dan 50% ontbrekende waarden
drempel = len(df) * 0.5
df = df.dropna(axis=1, thresh=drempel)

Strategie 2: Ontbrekende waarden invullen (imputatie)

Vaak is het beter om ontbrekende waarden in te vullen dan hele rijen weg te gooien. Welke methode je kiest, hangt af van het type data waarmee je werkt.

# Numerieke kolommen: invullen met het gemiddelde of de mediaan
df["bedrag"] = df["bedrag"].fillna(df["bedrag"].median())

# Categorische kolommen: invullen met de meest voorkomende waarde (modus)
df["categorie"] = df["categorie"].fillna(df["categorie"].mode()[0])

# Tijdreeksdata: forward fill (vorige waarde overnemen)
df["voorraad"] = df["voorraad"].ffill()

# Of backward fill (volgende waarde overnemen)
df["voorraad"] = df["voorraad"].bfill()

Strategie 3: Geavanceerde imputatie met Scikit-learn

Voor complexere situaties kun je imputatie-methoden uit scikit-learn gebruiken. KNN-imputatie is daar een mooi voorbeeld van — het kijkt naar vergelijkbare rijen om ontbrekende waarden in te schatten. Werkt verrassend goed in de praktijk.

from sklearn.impute import KNNImputer

# Alleen numerieke kolommen selecteren
numerieke_cols = df.select_dtypes(include=[np.number]).columns
imputer = KNNImputer(n_neighbors=5)

df[numerieke_cols] = imputer.fit_transform(df[numerieke_cols])
print(f"Ontbrekende waarden na KNN-imputatie: {df[numerieke_cols].isnull().sum().sum()}")

Stap 3: Duplicaten Opsporen en Verwijderen

Duplicaten kunnen je analyses behoorlijk vertekenen. Ze sluipen er vaak in door dubbele invoer, het samenvoegen van datasets, of fouten in ETL-processen. En het vervelende is: je merkt het pas als je er specifiek naar zoekt.

# Hoeveel duplicaten zijn er?
print(f"Aantal duplicaten: {df.duplicated().sum()}")

# Welke rijen zijn duplicaten?
duplicaten = df[df.duplicated(keep=False)]
print(f"\nDuplicaat-rijen:")
print(duplicaten.sort_values(by=df.columns.tolist()).head(10))

# Duplicaten verwijderen (eerste voorkomen behouden)
df_schoon = df.drop_duplicates(keep="first")
print(f"\nRijen verwijderd: {len(df) - len(df_schoon)}")

Duplicaten op basis van specifieke kolommen

Soms zijn rijen niet exact gelijk, maar bevatten ze wel dezelfde klant of transactie. In dat geval check je op een subset van kolommen. Dit komt vaker voor dan je denkt.

# Duplicaten op basis van klant_id en datum
df_schoon = df.drop_duplicates(
    subset=["klant_id", "datum"],
    keep="last"  # Behoud de meest recente versie
)

# Geavanceerder: duplicaten detecteren met een tijdsvenster
# (bijv. dezelfde klant die binnen 1 minuut twee keer bestelt)
df["datum"] = pd.to_datetime(df["datum"])
df = df.sort_values(["klant_id", "datum"])
df["tijdsverschil"] = df.groupby("klant_id")["datum"].diff()
mogelijke_duplicaten = df[df["tijdsverschil"] < pd.Timedelta(minutes=1)]
print(f"Mogelijke duplicaten binnen 1 minuut: {len(mogelijke_duplicaten)}")

Stap 4: Datatypes Corrigeren

Verkeerde datatypes — je komt ze overal tegen. Getallen die als strings zijn opgeslagen, datums die niet worden herkend, of valuta-kolommen met eurotekens en punten. Het is een van die dingen die niet spectaculair klinken maar je enorm veel tijd kosten als je het niet aanpakt.

# Huidige datatypes bekijken
print(df.dtypes)

# Strings naar numeriek converteren
# errors="coerce" zet ongeldige waarden om naar NaN
df["bedrag"] = pd.to_numeric(df["bedrag"], errors="coerce")

# Strings naar datetime converteren
df["datum"] = pd.to_datetime(df["datum"], format="%d-%m-%Y", errors="coerce")

# Kolom naar categorie-type converteren (geheugenefficiënt)
df["status"] = df["status"].astype("category")

# Boolean-kolom aanmaken vanuit tekst
df["is_betaald"] = df["betaalstatus"].map({"ja": True, "nee": False})

Valuta en getallen met speciale opmaak

In Nederlandse datasets zie je vaak bedragen als "€ 1.234,56". Pandas kan daar niet direct mee rekenen, dus die moet je eerst omzetten. Gelukkig is dat met een paar string-operaties zo gepiept.

# Euroteken, punten en komma's opruimen
df["prijs"] = (
    df["prijs"]
    .str.replace("€", "", regex=False)
    .str.replace(".", "", regex=False)    # Duizendtallen-punt verwijderen
    .str.replace(",", ".", regex=False)   # Komma naar punt voor decimalen
    .str.strip()
    .pipe(pd.to_numeric, errors="coerce")
)

print(df["prijs"].describe())

Stap 5: Tekstdata Standaardiseren

Inconsistente tekstdata is eerlijk gezegd een van de meest frustrerende problemen. Dezelfde stad kan voorkomen als "Amsterdam", "amsterdam", "AMSTERDAM" of zelfs " Amsterdam " met extra spaties. Herkenbaar?

# Alle tekstkolommen in één keer opschonen
tekst_kolommen = df.select_dtypes(include=["string", "object"]).columns

for kolom in tekst_kolommen:
    df[kolom] = (
        df[kolom]
        .str.strip()           # Spaties aan begin en einde verwijderen
        .str.lower()           # Naar kleine letters
        .str.replace(r"\s+", " ", regex=True)  # Dubbele spaties verwijderen
    )

# Specifieke correcties voor bekende fouten
stad_correcties = {
    "a'dam": "amsterdam",
    "den haag": "s-gravenhage",
    "r'dam": "rotterdam"
}
df["stad"] = df["stad"].replace(stad_correcties)

Postcodes en telefoonnummers valideren

Nederlandse postcodes hebben een vast formaat: vier cijfers gevolgd door twee letters (bijv. 1234 AB). Met een reguliere expressie kun je ze makkelijk valideren en standaardiseren.

import re

# Postcode valideren en standaardiseren
def valideer_postcode(pc):
    if pd.isna(pc):
        return np.nan
    pc = str(pc).strip().upper().replace(" ", "")
    match = re.match(r"^(\d{4})([A-Z]{2})$", pc)
    if match:
        return f"{match.group(1)} {match.group(2)}"
    return np.nan

df["postcode"] = df["postcode"].apply(valideer_postcode)

# Hoeveel ongeldige postcodes?
ongeldige = df["postcode"].isna().sum()
print(f"Ongeldige postcodes: {ongeldige}")

Stap 6: Uitschieters (Outliers) Detecteren en Behandelen

Uitschieters zijn waarden die ver buiten het normale bereik vallen. Ze kunnen wijzen op echte extremen (een dure aankoop) of op fouten in de data (een leeftijd van 999). Het is cruciaal dat je het verschil herkent — want een uitschieter verwijderen die eigenlijk een echte waarde is, kan je analyse net zo goed verknoeien.

De IQR-methode

Dit is de meest gebruikte methode. Waarden die meer dan 1,5 keer de IQR (Interquartile Range) onder Q1 of boven Q3 liggen, worden als uitschieters beschouwd. Simpel, effectief, en werkt goed voor de meeste datasets.

def detecteer_uitschieters_iqr(df, kolom):
    """Detecteer uitschieters met de IQR-methode."""
    Q1 = df[kolom].quantile(0.25)
    Q3 = df[kolom].quantile(0.75)
    IQR = Q3 - Q1
    ondergrens = Q1 - 1.5 * IQR
    bovengrens = Q3 + 1.5 * IQR

    uitschieters = df[(df[kolom] < ondergrens) | (df[kolom] > bovengrens)]
    print(f"Kolom '{kolom}':")
    print(f"  Bereik: {ondergrens:.2f} - {bovengrens:.2f}")
    print(f"  Uitschieters: {len(uitschieters)} ({len(uitschieters)/len(df)*100:.1f}%)")
    return uitschieters

# Uitschieters detecteren voor numerieke kolommen
for kolom in ["bedrag", "aantal", "leeftijd"]:
    if kolom in df.columns:
        detecteer_uitschieters_iqr(df, kolom)

De Z-score methode

Een alternatief is de Z-score methode. Die meet hoeveel standaarddeviaties een waarde van het gemiddelde afwijkt. Alles boven 3 (of onder -3) is verdacht.

from scipy import stats

# Z-scores berekenen
z_scores = stats.zscore(df["bedrag"].dropna())
uitschieters_zscore = df.loc[df["bedrag"].dropna().index[abs(z_scores) > 3]]
print(f"Uitschieters (Z-score > 3): {len(uitschieters_zscore)}")

Uitschieters behandelen: afkappen (winsorisatie)

In plaats van uitschieters te verwijderen kun je ze ook afkappen naar een redelijke grens. Je behoudt de rij maar beperkt de extreme waarde. In mijn ervaring is dit vaak de betere keuze, tenzij je zeker weet dat het om fouten gaat.

# Winsorisatie: waarden afkappen op het 1e en 99e percentiel
ondergrens = df["bedrag"].quantile(0.01)
bovengrens = df["bedrag"].quantile(0.99)
df["bedrag"] = df["bedrag"].clip(lower=ondergrens, upper=bovengrens)

print(f"Bedragen nu begrensd tussen {ondergrens:.2f} en {bovengrens:.2f}")

Stap 7: Een Herbruikbare Cleaning Pipeline Bouwen

Oké, nu je alle individuele stappen kent, wordt het pas echt leuk. We gaan ze samenvoegen tot een herbruikbare pipeline. Dit is waar method chaining in Pandas tot z'n recht komt — en eerlijk gezegd is dit het deel waar ik zelf het meest enthousiast over ben.

def schoon_verkoopdata(bestandspad):
    """
    Complete cleaning pipeline voor verkoopdata.
    Retourneert een opgeschoond DataFrame.
    """
    df = (
        pd.read_csv(
            bestandspad,
            na_values=["n/a", "N/A", "nvt", "-", "onbekend", ""]
        )
        # Stap 1: Volledig lege rijen verwijderen
        .dropna(how="all")

        # Stap 2: Exacte duplicaten verwijderen
        .drop_duplicates()

        # Stap 3: Kolomnamen standaardiseren
        .rename(columns=lambda x: x.strip().lower().replace(" ", "_"))

        # Stap 4: Datatypes corrigeren
        .assign(
            datum=lambda x: pd.to_datetime(x["datum"], errors="coerce"),
            bedrag=lambda x: pd.to_numeric(
                x["bedrag"]
                .astype(str)
                .str.replace("€", "", regex=False)
                .str.replace(".", "", regex=False)
                .str.replace(",", ".", regex=False)
                .str.strip(),
                errors="coerce"
            ),
        )

        # Stap 5: Ongeldige waarden filteren
        .query("bedrag > 0")

        # Stap 6: Index resetten
        .reset_index(drop=True)
    )

    # Stap 7: Ontbrekende waarden invullen
    if "categorie" in df.columns:
        df["categorie"] = df["categorie"].fillna("onbekend")

    # Stap 8: Uitschieters afkappen
    if "bedrag" in df.columns:
        q99 = df["bedrag"].quantile(0.99)
        df["bedrag"] = df["bedrag"].clip(upper=q99)

    return df

# Pipeline uitvoeren
df_schoon = schoon_verkoopdata("verkopen_2026.csv")
print(f"Opgeschoonde dataset: {df_schoon.shape[0]} rijen, {df_schoon.shape[1]} kolommen")

Validatie na het opschonen

Je bent er nog niet. Na het opschonen moet je controleren of alles goed is gegaan. Vergelijk de statistieken voor en na — je wilt zeker weten dat je niet per ongeluk de helft van je data hebt weggegooid.

def valideer_dataset(df, naam="Dataset"):
    """Voer basisvalidatie uit op een opgeschoond DataFrame."""
    print(f"=== Validatie: {naam} ===")
    print(f"Rijen: {len(df)}")
    print(f"Kolommen: {len(df.columns)}")
    print(f"Ontbrekende waarden: {df.isnull().sum().sum()}")
    print(f"Duplicaten: {df.duplicated().sum()}")

    # Controleer datatypes
    print(f"\nDatatypes:")
    for kolom, dtype in df.dtypes.items():
        print(f"  {kolom}: {dtype}")

    # Controleer numerieke bereiken
    print(f"\nNumerieke samenvatting:")
    print(df.describe().round(2))

valideer_dataset(df_schoon, "Verkoopdata 2026")

Copy-on-Write in Pandas 3.0: Wat Het Betekent voor Jouw Code

Een van de grootste veranderingen in Pandas 3.0 is dat Copy-on-Write (CoW) nu standaard aanstaat. En dat heeft directe gevolgen voor hoe je data opschoont.

Het probleem in oudere versies

In Pandas 2.x kon je per ongeluk het originele DataFrame wijzigen wanneer je dacht met een kopie te werken. Iedereen die een tijdje met Pandas heeft gewerkt kent de beruchte SettingWithCopyWarning. Frustrerend? Absoluut.

# Pandas 2.x: dit kon onverwacht gedrag veroorzaken
subset = df[df["status"] == "actief"]
subset["bedrag"] = subset["bedrag"] * 1.21  # SettingWithCopyWarning!

# Pandas 3.0 met CoW: dit werkt nu veilig
# subset is altijd een onafhankelijke kopie
subset = df[df["status"] == "actief"]
subset["bedrag"] = subset["bedrag"] * 1.21  # Geen warning, geen onverwachte mutatie

Wat verandert er in de praktijk?

Het goede nieuws: je cleaning code wordt veiliger en voorspelbaarder. Het minder goede nieuws: als je code afhankelijk was van het oude view-gedrag, kan die breken. Maar dat is eigenlijk een feature, geen bug — je code had sowieso niet op dat gedrag moeten leunen.

# Best practice in Pandas 3.0: gebruik method chaining
# Dit is sowieso de schoonste manier om data op te schonen
df_schoon = (
    df
    .dropna(subset=["klant_id"])
    .drop_duplicates()
    .assign(bedrag_incl_btw=lambda x: x["bedrag"] * 1.21)
)

# Of gebruik .copy() expliciet wanneer je een subset wilt wijzigen
subset = df[df["status"] == "actief"].copy()
subset["bedrag"] = subset["bedrag"] * 1.21

Veelgemaakte Fouten bij Data Opschonen (en Hoe Je Ze Vermijdt)

Na jaren van data opschonen (en ja, ik heb ze allemaal zelf gemaakt) zijn er een paar fouten die steeds terugkomen:

  • Geen backup maken van de originele data: Sla altijd het ruwe bestand apart op. Je kunt niet teruggaan naar data die je hebt overschreven. Klinkt simpel, maar het gebeurt vaker dan je denkt.
  • Blindelings rijen verwijderen: Controleer altijd waarom data ontbreekt. Als alle ontbrekende waarden uit dezelfde regio komen, heb je mogelijk een systematisch probleem in je datacollectie.
  • Vergeten om wijzigingen te documenteren: Houd bij wat je hebt veranderd en waarom. Je toekomstige zelf (of je collega) zal je dankbaar zijn.
  • Te veel in één keer doen: Werk stap voor stap en controleer tussentijds. Eén enorme keten van bewerkingen maakt het lastig om fouten op te sporen.
  • Uitschieters verwijderen zonder na te denken: Niet elke uitschieter is een fout. Die klant die voor €50.000 bestelt? Dat is misschien je belangrijkste klant.

Veelgestelde Vragen

Hoeveel tijd besteden data scientists aan data opschonen?

Onderzoek laat consistent zien dat data scientists gemiddeld 60 tot 80 procent van hun tijd besteden aan data opschonen en voorbereiding. Dat omvat het detecteren van ontbrekende waarden, corrigeren van datatypes, verwijderen van duplicaten en standaardiseren van tekst. Met geautomatiseerde cleaning pipelines in Pandas kun je die tijd flink terugbrengen.

Wat is het verschil tussen dropna() en fillna() in Pandas?

dropna() verwijdert rijen of kolommen met ontbrekende waarden, terwijl fillna() ontbrekende waarden vervangt door een waarde die je opgeeft — zoals het gemiddelde, de mediaan of een constante. Gebruik dropna() wanneer het percentage ontbrekende waarden klein is en willekeurig verdeeld. Gebruik fillna() wanneer je data wilt behouden en een redelijke schatting kunt maken.

Hoe detecteer ik uitschieters in mijn dataset?

De twee populairste methoden zijn de IQR-methode en de Z-score methode. Bij de IQR-methode bereken je het verschil tussen het 75e en 25e percentiel en markeer je waarden die meer dan 1,5 keer die afstand buiten dit bereik vallen. Bij de Z-score methode markeer je waarden die meer dan 3 standaarddeviaties van het gemiddelde afwijken. Welke je kiest hangt af van de verdeling van je data — voor scheef verdeelde data werkt IQR doorgaans beter.

Kan ik data opschonen automatiseren met Pandas?

Zeker. Door herbruikbare functies en method chaining te combineren bouw je een volledige cleaning pipeline die je op elke nieuwe dataset kunt loslaten. Definieer functies voor elke stap en keten ze samen. Zo pas je dezelfde kwaliteitsstandaarden toe op al je data, zonder steeds handmatig dezelfde stappen te herhalen.

Wat is Copy-on-Write in Pandas 3.0 en waarom is het belangrijk?

Copy-on-Write (CoW) is een geheugenoptimalisatie die in Pandas 3.0 standaard aanstaat. Het zorgt ervoor dat bewerkingen op een subset van een DataFrame nooit onbedoeld het originele DataFrame wijzigen. Onder de motorkap gebruikt Pandas views waar mogelijk (voor betere prestaties) maar maakt automatisch een kopie zodra je data probeert te wijzigen. Dit elimineert de beruchte SettingWithCopyWarning en maakt je cleaning code een stuk veiliger.

Over de Auteur Editorial Team

Our team of expert writers and editors.