- 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.
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.
| Invoer-ID | Sleutel | Beslissing | Daarna gezien |
|---|---|---|---|
| " A " | a | bewaren | a |
| "a" | a | overslaan | a |
| "B" | b | bewaren | a 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.
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.
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.