Werkplaats / De werkplaats
In jouw eigen tempo
← Data
MODULE 01 · LES 03

Wanneer zijn twee rijen hetzelfde?

Definieer identiteit, verwijder echte dubbelen en signaleer tegenstrijdige labels.

45 min3 checkpoints
DIT GA JE ONTDEKKEN
  • Je dedupliceert op een expliciet gedefinieerde sleutel.
  • Je signaleert labelconflicten in plaats van ze stil te verbergen.

Twee planten kunnen allebei twaalf centimeter hoog zijn zonder dezelfde plant te zijn. Andersom kan één meting tweemaal in een bestand staan doordat een export opnieuw is toegevoegd. Alleen gelijke getallen zoeken is dus geen betrouwbare manier om dubbelen te herkennen.

Deduplicatie (deduplication) begint bij de betekenis van identiteit. In deze oefening benoemt een ID één meetrecord, niet één plant gedurende haar hele leven. Anders zou je verschillende, geldige tijdmetingen per ongeluk samenvoegen.

1. Maak dezelfde identiteit vergelijkbaar

Ons fictieve broncontract zegt dat hoofdletters niet meetellen in record-ID's en dat spaties aan de randen importfouten zijn. Daarom verwijzen " A " en "a" naar hetzelfde record. "B" verwijst naar een ander record, ook als zijn meting toevallig dezelfde waarde heeft.

Je kent .strip() voor randspaties. .casefold() maakt tekst geschikt voor vergelijking zonder hoofdletterverschil; dit behandelt ook sommige bijzondere lettervormen. Zo wordt het Duitse "Straße" vergelijkbaar met "STRASSE": beide krijgen sleutel "strasse".

ids = [" A ", "a", "B"]
sleutels = []
for i in range(len(ids)):
    tekst = ids[i].strip()
    sleutels.append(tekst.casefold())
print(sleutels)   # ['a', 'a', 'b']
print("Straße".casefold())   # strasse

We veranderen alleen de vergelijkingssleutel, niet de oorspronkelijke tekst. Dat maakt later zichtbaar hoe de bron de ID had aangeleverd. Maak record_sleutel(id) met deze twee stappen voor een tekst-ID.

01
CHECKPOINTMaak de afgesproken identiteit expliciet

Veelgemaakte fout: deze normalisatie op iedere dataset toepassen. Een ander contract kan hoofdletters juist betekenis geven; dan zouden twee verschillende records samenvallen. Controleer daarom eerst de afspraak, net zoals je bij hoogte de eenheid controleerde.

2. Onthoud welke sleutels je al zag

Loop in de oorspronkelijke volgorde langs drie rijen met ID's " A ", "a" en "B". De eerste sleutel a is nieuw: bewaar die rij. De tweede sleutel a is al gezien: sla die kopie over; sleutel b is weer nieuw.

Bijhouden wat al gezien is
Invoer-IDSleutelBeslissingDaarna gezien
" A "abewarena
"a"aoverslaana
"B"bbewarena en b

Een set bewaart unieke waarden zonder een afgesproken volgorde. set() maakt een lege set en .add(sleutel) voegt een waarde toe; nogmaals dezelfde waarde toevoegen verandert niets. Met sleutel not in gezien controleer je of die waarde er nog niet in zit.

rows = [
    {"id": " A ", "label": 0},
    {"id": "a", "label": 1},
    {"id": "B", "label": 0}
]
gezien = set()
uniek = []
for i in range(len(rows)):
    tekst = rows[i]["id"].strip()
    sleutel = tekst.casefold()
    if sleutel not in gezien:
        gezien.add(sleutel)
        uniek.append(rows[i])
print(uniek)
# [{'id': ' A ', 'label': 0},
#  {'id': 'B', 'label': 0}]
print(len(rows))   # 3, origineel blijft

De uitvoerlijst volgt de invoervolgorde, ook al heeft de set zelf geen afgesproken volgorde. Alleen de eerste rij per sleutel wordt behouden. Maak unieke_records(rows) met je sleutelhelper en een nieuwe lijst; laat de oorspronkelijke rijen intact.

02
CHECKPOINTBehoud één rij per meetrecord

Deze functie beslist niet welke rij waar is. In ons voorbeeld spreken de twee a-rijen elkaar zelfs tegen: de eerste heeft label 0, de tweede label 1. Gebruik het unieke resultaat daarom niet verder voordat je de oorspronkelijke rijen op conflicten hebt gecontroleerd.

3. Onderzoek tegenstrijdigheid voordat je haar verbergt

Een labelconflict (label conflict) betekent hier dat dezelfde sleutel zowel label 0 als label 1 heeft. Bij onze drie rijen is alleen a zo'n conflict; b heeft alleen 0. We willen dus de lijst ["a"] terug, ook als hetzelfde conflict vaker voorkomt.

Bewaar per sleutel het eerste label in een dictionary. Een later afwijkend label bewijst dat er binnen die sleutel meer dan één antwoord voorkomt. Voeg de sleutel dan toe aan een set conflicten, zodat hij maar eenmaal wordt gemeld.

eerste = {}
conflicten = set()
for i in range(len(rows)):
    tekst = rows[i]["id"].strip()
    sleutel = tekst.casefold()
    label = rows[i]["label"]
    if sleutel not in eerste:
        eerste[sleutel] = label
    if eerste[sleutel] != label:
        conflicten.add(sleutel)
print(sorted(conflicten))   # ['a']

{} maakt een lege dictionary. eerste[sleutel] = label bewaart een waarde onder die sleutel; bij in of not in kijkt een dictionary naar haar sleutels. sorted(conflicten) maakt een lijst in tekstvolgorde, net zoals sorted eerder getallen ordende.

Let op de invoer van die lus: rows, niet uniek. Na het wegfilteren van de tweede a-rij ontbreekt het bewijs van de tegenspraak. Maak labelconflicten(rows) voor de oorspronkelijke rijen, met labels 0 of 1, en geef unieke conflict-ID's gesorteerd terug.

03
CHECKPOINTTegenstrijdigheid verdient een melding

Bij een conflict onderzoek je bron, meetmoment en de betekenis van het label. “Eerste rij behouden” is een herhaalbare keuze, maar maakt een van twee tegenstrijdige antwoorden niet automatisch juist. Bepaal eerst wat het juiste record is voordat je het resultaat als opgeschoonde data gebruikt.

Later verdelen we gegevens over een verzameling om te leren en een aparte verzameling om te controleren. Een kopie van dezelfde meting in beide verzamelingen maakt die controle misleidend: het model kan iets herkennen dat het al zag. Recordidentiteit moet daarom bekend zijn vóór die verdeling.

Wat neem je mee?

  • Een sleutel vertegenwoordigt een afgesproken identiteit; gelijke metingen zijn niet vanzelf dubbelen.
  • Een set helpt om de eerste rij per sleutel te bewaren, zonder de oorspronkelijke lijst te wijzigen.
  • Controleer labelconflicten op alle oorspronkelijke rijen; deduplicatie kan het bewijs anders verbergen.

De volgende module verdeelt gegevens eerlijk over leren en controleren. Je weet nu waarom de betekenis van een record daarbij onmisbaar is.

Verder lezen & bronnen

Inhoud gecontroleerd op 2026-10-27.
DE VOLGENDE STAPOefenen, kiezen en pas dan toetsen
JOUW EXPERIMENT

Probeer het zelf

Python
main.py

Python start zodra jij code uitvoert.

UITVOER

Je uitvoer verschijnt hier.
Alles rekent in je eigen browser.

Jouw checkpoints0 / 3
01
Maak de afgesproken identiteit expliciet

Schrijf record_sleutel(id) voor een tekst-ID volgens dit broncontract: randspaties verwijderen en hoofdletterverschillen negeren.

Een hint

Gebruik eerst strip(), daarna casefold(); de oorspronkelijke rij hoeft niet te veranderen.

02
Behoud één rij per meetrecord

Schrijf unieke_records(rows): behoud de eerste rij per genormaliseerde tekst-ID in oorspronkelijke volgorde en wijzig rows niet.

Een hint

Gebruik een set van geziene sleutels en een nieuwe lijst. Voeg alleen bij een nieuwe sleutel zowel de sleutel als de rij toe.

03
Tegenstrijdigheid verdient een melding

Schrijf labelconflicten(rows) voor de oorspronkelijke rijen met labels 0/1. Geef gesorteerde unieke sleutels waarvoor beide labels voorkomen.

Een hint

Bewaar in een dictionary het eerste label per sleutel. Voeg een sleutel aan een conflictset toe zodra een ander label verschijnt; gebruik alle oorspronkelijke rijen.

Vastgelopen? Bekijk de voorbeeldoplossing
def record_sleutel(id):
    tekst = id.strip()
    return tekst.casefold()


def unieke_records(rows):
    gezien = set()
    uniek = []
    for i in range(len(rows)):
        sleutel = record_sleutel(rows[i]["id"])
        if sleutel not in gezien:
            gezien.add(sleutel)
            uniek.append(rows[i])
    return uniek


def labelconflicten(rows):
    eerste = {}
    conflicten = set()
    for i in range(len(rows)):
        sleutel = record_sleutel(rows[i]["id"])
        label = rows[i]["label"]
        if sleutel not in eerste:
            eerste[sleutel] = label
        if eerste[sleutel] != label:
            conflicten.add(sleutel)
    return sorted(conflicten)

Voer de code uit en verklaar voor jezelf waarom elke stap werkt.