- Je herkent None en NaN zonder echte nullen weg te gooien.
- Je legt uit waarom invullen een keuze is en ontbrekende data niet automatisch herstelt.
Een hoogtesensor geeft bij één plant 0 centimeter en bij een andere plant geen meting. Die twee gevallen betekenen iets anders: nul is een waarneming, leeg zegt dat je de hoogte niet kent. Een ontbrekende waarde zomaar door nul vervangen kan dus een verzonnen meting aan je gegevens toevoegen.
Een sensor kan uitvallen of een plant kan te groot zijn voor het instrument. Beide leveren lege vakken op, maar alleen het tweede mechanisme verbergt juist grote waarden. We onderzoeken zowel het herkennen van leegte als de gevolgen van een invulkeuze.
1. Maak zichtbaar welke posities ontbreken
Neem de lijst [0, None, 4, NaN]. Op posities 0 en 2 staan metingen; op posities 1 en 3 ontbreekt de waarde. Een ontbreekmasker (missingness mask) bewaart dat onderscheid als [False, True, False, True].
Een masker heeft dezelfde lengte en volgorde als de oorspronkelijke lijst. True betekent hier “ontbreekt”, niet “geldige meting”. In deze oefening zijn alleen None en een float-NaN ontbrekend; overige getallen zijn eindig.
import math
xs = [0, None, 4, float("nan")]
masker = []
for i in range(len(xs)):
mist = False
if xs[i] is None:
mist = True
if type(xs[i]) == float:
if math.isnan(xs[i]):
mist = True
masker.append(mist)
print(masker)
# [False, True, False, True]
is None is de expliciete Pythoncontrole op de bijzondere waarde None. math.isnan controleert een float op NaN; de extra inspringing betekent dat die controle alleen binnen de voorafgaande typecontrole draait. De bekende import math maakt deze functie beschikbaar.
Veelgemaakte fout: NaN met == proberen te herkennen. Zelfs NaN vergelijken met zichzelf geeft False; daarom gebruiken we math.isnan. Maak nu ontbreekmasker(xs), ook voor een lege lijst; die levert een leeg masker op.
2. Leer één invulwaarde uit wat bekend is
Van onze lijst blijven 0 en 4 over. Hun som is 4 en hun aantal is 2, dus het gemiddelde is 2. Nul telt mee in het aantal: alleen waarden weglaten omdat ze niet positief zijn zou het gemiddelde ten onrechte 4 maken.
m noemt de gekozen invulwaarde. Gebruik het masker uit de vorige berekening om alleen bekende metingen op te tellen:
totaal = 0
aantal = 0
for i in range(len(xs)):
if masker[i] == False:
totaal = totaal + xs[i]
aantal = aantal + 1
invulwaarde = totaal / aantal
print(invulwaarde) # 2.0
Maak fit_gemiddelde(xs) met je maskerfunctie. Fit betekent hier “een instelling uit gegevens leren”: je berekent één getal, niet een ingevulde lijst. Als geen bekende waarde overblijft, geef je None vóór de deling; een lege groep heeft geen te schatten gemiddelde.
Een gemiddelde uit de zichtbare waarden hoeft niet representatief te zijn voor de verborgen waarden. Het volgende diagram kent als oefening de volledige reeks 2, 4, 6, 8, 10, 12, 14, 16: gemiddelde 9. Verberg bij Hoogste waarden twee metingen; de resterende som is 42, gedeeld door zes geeft 7.
In deze simulatie kennen we de volledige reeks: 2, 4, 6, 8, 10, 12, 14, 16. Verberg daarna een deel.
Vergelijk met Verspreid bij hetzelfde aantal. Er zijn evenveel lege vakken, maar andere waarden verdwijnen en dus verandert het zichtbare gemiddelde anders. De getoonde volledige reeks is in deze simulatie bekend; bij echte ontbrekende metingen heb je dat controleantwoord meestal niet.
3. Pas de gekozen waarde toe zonder opnieuw te leren
Imputeren (imputation) betekent ontbrekende waarden volgens een afgesproken regel invullen. Met invulwaarde 2 wordt onze lijst [0, 2, 4, 2]. De nul en vier blijven echt gemeten; de twee ingevulde waarden krijgen niet ineens dezelfde bewijskracht.
nieuw = []
for i in range(len(xs)):
waarde = xs[i]
if masker[i] == True:
waarde = invulwaarde
nieuw.append(waarde)
print(nieuw) # [0, 2.0, 4, 2.0]
print(xs[1] is None) # True
We maken een nieuwe lijst zodat de oorspronkelijke ontbrekende posities bewaard blijven. Maak vul_in(xs, waarde): gebruik de meegegeven waarde en je masker, zonder daarin nog een gemiddelde te berekenen. Dit toepassen van een eerder geleerde instelling heet transform.
Later leer je modellen op trainingsgegevens en controleer je ze op apart gehouden gegevens. Leer ook de invulwaarde uitsluitend uit de trainingsgegevens, anders beïnvloedt de controleverzameling vooraf je bewerking. Bij een nieuwe controlelijst [10, None] blijft onze eerder geleerde invulwaarde daarom 2:
controle = [10, None]
ingevuld = [controle[0], invulwaarde]
print(ingevuld) # [10, 2.0]
Opnieuw leren uit die controlelijst zou 10 als invulwaarde geven. Dan is de regel veranderd door de gevallen waarop je hem juist wilde beoordelen. Fit en transform apart houden maakt dit verschil controleerbaar.
Vergelijk nu drie invulregels voor een andere lijst. De vijf bekende waarden 2, 3, 5, 50 en 8 tellen op tot 68; hun gemiddelde is 13.6, hun mediaan 5. Twee keer 5 invullen geeft een totaalsom van 78 over zeven waarden:
print((68 + 2 * 5) / 7) # ongeveer 11.143
Gegeven: 2, 3, leeg, 5, 50, leeg, 8. Oranje punten zijn ingevuld, groene punten echt gemeten.
De echte ontbrekende waarden zijn onbekend. Dit experiment vergelijkt aannames; het vertelt niet welke invulling waar is.
Kies Mediaan en controleer dat resultaat. Wissel daarna naar Gemiddelde en Nul: het gemiddelde van de hele lijst verandert door jouw keuze. Geen van deze knoppen onthult wat oorspronkelijk in de lege vakken stond.
Wat neem je mee?
- Bewaar een masker: nul en ontbreken zijn verschillende toestanden.
- Leer een invulwaarde alleen uit bekende trainingsmetingen; geen bekende waarden betekent hier None.
- Pas diezelfde waarde later toe in een nieuwe lijst. Invullen voegt een aanname toe, geen teruggevonden meting.
Hierna onderzoek je rijen die vaker voorkomen. Ook daar begint de juiste behandeling bij de betekenis van de gegevens.