Werkplaats / De werkplaats
In jouw eigen tempo
← Data
MODULE 02 · LES 06

Kies een split die je echte vraag nabootst

Houd groepen bijeen en evalueer toekomstvoorspellingen uitsluitend vanuit het verleden.

55 min3 checkpoints
DIT GA JE ONTDEKKEN
  • Je maakt een groepssplit zonder dezelfde groep in train en test.
  • Je maakt tijdsplits met een gat en opeenvolgende evaluatievensters.

Je meet vier planten op drie dagen. Twaalf rijen lijken veel voorbeelden, maar het zijn nog steeds maar vier planten. Als dezelfde plant in training en test belandt, onderzoek je iets anders dan voorspellen voor een geheel nieuwe plant.

De verdeling moet daarom passen bij je evaluatievraag. Wil je nieuwe planten beoordelen, houd planten bijeen. Wil je de toekomst van bekende planten voorspellen, houd toekomstige metingen apart.

1. Nieuwe planten blijven volledig onbekend

Een groepssplit (group split) wijst een hele groep aan training of test toe. Hier is een groep één plant; alle records van plant B gaan dus naar dezelfde kant. De record-ID benoemt een meting, de groep benoemt de plant die meerdere metingen kan hebben.

Neem drie records: A met ID 1, B met ID 2 en weer A met ID 3. Kies B als testgroep: training krijgt ID's 1 en 3, test krijgt ID 2. Je bewaart de volgorde waarin die records binnenkwamen.

rows = [
    {"groep": "A", "id": 1},
    {"groep": "B", "id": 2},
    {"groep": "A", "id": 3}
]
testgroepen = {"B"}
train = []
test = []
for i in range(len(rows)):
    groep = rows[i]["groep"]
    if groep in testgroepen:
        test.append(rows[i])
    if groep not in testgroepen:
        train.append(rows[i])
print(train[0]["id"], train[1]["id"]) # 1 3
print(test[0]["id"])                  # 2

{"B"} maakt een set met één groepsnaam. Anders dan bij een dictionary staat er geen dubbele punt met een bijbehorende waarde. De twee voorwaarden sluiten elkaar uit: dezelfde groep kan niet tegelijk wel en niet in de set zitten.

Maak split_groepen(rows, testgroepen) en geef (train, test) terug. Maak nieuwe lijsten en verander de oorspronkelijke records niet. Een opgegeven testgroep die niet in de gegevens voorkomt voegt geen records toe.

01
CHECKPOINTNieuwe groepen horen volledig in test

In de visualisatie zie je vier planten met ieder drie metingen. Per rij geeft zes trainingsrijen, zes testrijen en vier gedeelde planten. Kies Per groep: de aantallen blijven zes en zes, maar er zijn nul gedeelde planten.

DE VRAAG BEPAALT DE GRENSNieuwe planten of een nieuwe dag?

Vier planten, elk gemeten op drie dagen. Kies een split en volg welke metingen samen blijven.

Plant ADag 1Training
Plant BDag 1Training
Plant CDag 1Test
Plant DDag 1Test
Plant ADag 2Test
Plant BDag 2Training
Plant CDag 2Training
Plant DDag 2Test
Plant ADag 3Training
Plant BDag 3Test
Plant CDag 3Test
Plant DDag 3Training
Training6Test6Gedeelde planten4

Alle vier de planten staan aan beide kanten. Dit toetst geen generalisatie naar onbekende planten.

Kies daarna Op tijd. Dag 1 en 2 vormen acht trainingsrijen; dag 3 vormt vier testrijen, met alle vier planten aan beide kanten. Dat is passend voor een nieuwe dag van bekende planten, maar geen proef met onbekende planten.

2. Neem alleen informatie die op tijd beschikbaar is

Veelgemaakte fout: een eerdere meetdatum voldoende vinden. Het antwoord bij die meting kan pas dagen later bekend worden. Dan zou training toch informatie gebruiken die bij de voorspelling nog toekomst was.

We laten daarom zo nodig een gat (gap) vóór het testbegin. Stel de tijdgrens t op 4 en de gap g op 1. Van tijden 0, 1, 2, 3, 4 en 5 gaan 0–2 naar training, 3 naar het gat en 4–5 naar test.

trainings<t−ggatt−g≤s<ttests≥t

s is de tijd van één record, t de testgrens en g de niet-negatieve gap. Beide grenzen gebruiken dezelfde tijdseenheid als de metingen. Onze oefenrijen staan al chronologisch en bevatten eindige numerieke tijden.

rows = []
for i in range(6):
    rows.append({"tijd": i})
t = 4
gap = 1
train = []
test = []
gat = []
for i in range(len(rows)):
    tijd = rows[i]["tijd"]
    if tijd < t - gap:
        train.append(rows[i])
    if tijd >= t:
        test.append(rows[i])
    if tijd >= t - gap and tijd < t:
        gat.append(rows[i])
print(train) # tijden 0, 1, 2
print(gat)   # [{'tijd': 3}]
print(test)  # [{'tijd': 4}, {'tijd': 5}]

and betekent dat beide voorwaarden tegelijk moeten gelden. Maak split_tijd(rows, t, gap=0) met resultaat (train, test, gat); de standaardwaarde 0 laat geen gat. Weiger een negatieve gap met ValueError vóór je splitst: anders kunnen dezelfde tijden zowel training als test worden.

02
CHECKPOINTLaat een gat als informatie vertraagd beschikbaar komt

De benodigde gap volgt uit meetvensters en wanneer antwoorden beschikbaar komen. Een willekeurig gekozen gat bewijst geen veiligheid; controleer de grenzen tegen het echte meetproces.

3. Herhaal met een groeiend verleden

Bij rolling-origin evaluation schuift het toetsmoment vooruit. Voor zeven chronologische punten zijn de indices 0 tot en met 6. Met drie eerste trainingspunten en een testhorizon van twee ontstaat eerst training 0–2 met test 3–4, daarna training 0–4 met test 5–6.

Hier gebruiken we regelmatige meetmomenten en informatie die meteen beschikbaar is, dus geen gap. Eerdere testpunten mogen pas in een latere ronde naar training. Binnen iedere ronde blijven alle testindices na alle trainingsindices liggen.

n = 7
initial = 3
horizon = 2
vensters = []
stop = n - horizon + 1
for end in range(initial, stop, horizon):
    train = list(range(end))
    test = list(range(end, end + horizon))
    vensters.append((train, test))
print(vensters[0]) # ([0,1,2], [3,4])
print(vensters[1]) # ([0,1,2,3,4], [5,6])

range(start, stop, stap) begint bij start, stopt vóór stop en springt telkens stap verder. End is de eerste testindex: 3 en daarna 5. De laatste toegestane start is n−horizon; de +1 maakt die grens bereikbaar ondanks de exclusieve stop.

Maak tijdvensters(n, initial, horizon) voor gehele aantallen, met n minstens nul en beide venstergroottes minstens één. Weiger ongeldige aantallen met ValueError. Gebruik alleen volledige testvensters: voor n=4, initial=3 en horizon=2 is het resultaat [].

03
CHECKPOINTHerhaal de toets met een groeiend verleden

Wat neem je mee?

  • Groepen bijeenhouden toetst nieuwe groepen; een tijdgrens toetst een latere periode.
  • Houd rekening met vertraagde informatie en controleer een gap vóór je gegevens verdeelt.
  • Groeiend trainen gebruikt per ronde alleen het beschikbare verleden.

Hierna bereiden we kenmerken voor, steeds binnen de eerlijke grenzen die je nu kunt bouwen.

Verder lezen & bronnen

Inhoud gecontroleerd op 2026-10-28.
DE VOLGENDE STAPVerander de eenheid, behoud de betekenis
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
Nieuwe groepen horen volledig in test

Maak split_groepen(rows, testgroepen): geef (train, test), met alle records van een testgroep in test. Behoud volgorde en oorspronkelijke records.

Een hint

Gebruik row["groep"] in testgroepen voor test en not in voor training; voeg de oorspronkelijke rij aan een nieuwe lijst toe.

02
Laat een gat als informatie vertraagd beschikbaar komt

Maak split_tijd(rows, t, gap=0) voor chronologische records met eindige tijden en een eindige gap. Geef (train, test, gat) volgens de drie grenzen; weiger gap < 0 met ValueError.

Een hint

Controleer gap vooraf. Training: tijd < t-gap; test: tijd >= t; gat: beide tijd >= t-gap en tijd < t.

03
Herhaal de toets met een groeiend verleden

Maak tijdvensters(n, initial, horizon) voor gehele aantallen: n >= 0, initial >= 1, horizon >= 1. Geef alleen volledige vensters als (trainindices, testindices); weiger ongeldige aantallen.

Een hint

Loop end met range(initial, n-horizon+1, horizon). Maak de twee indexlijsten met range(end) en range(end, end+horizon).

Vastgelopen? Bekijk de voorbeeldoplossing
def split_groepen(rows, testgroepen):
    train = []
    test = []
    for i in range(len(rows)):
        groep = rows[i]["groep"]
        if groep in testgroepen:
            test.append(rows[i])
        if groep not in testgroepen:
            train.append(rows[i])
    return (train, test)


def split_tijd(rows, t, gap=0):
    if gap < 0:
        raise ValueError("Gap mag niet negatief zijn")
    train = []
    test = []
    gat = []
    for i in range(len(rows)):
        tijd = rows[i]["tijd"]
        if tijd < t - gap:
            train.append(rows[i])
        if tijd >= t:
            test.append(rows[i])
        if tijd >= t - gap and tijd < t:
            gat.append(rows[i])
    return (train, test, gat)


def tijdvensters(n, initial, horizon):
    if n < 0:
        raise ValueError("Aantal mag niet negatief zijn")
    if initial < 1:
        raise ValueError("Training moet positief zijn")
    if horizon < 1:
        raise ValueError("Horizon moet positief zijn")
    vensters = []
    stop = n - horizon + 1
    for end in range(initial, stop, horizon):
        train = list(range(end))
        test = list(range(end, end + horizon))
        vensters.append((train, test))
    return vensters

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