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.