Ga naar de inhoud

Handleidingen

Online experimenten (A/B-tests)

Je offline vergelijking zei dat de nieuwe versie beter is. Een A/B-test in productie stelt echte gebruikers dezelfde vraag. Je eigen router verdeelt eenheden (gebruikers of sessies) willekeurig over twee armen; oloproof experiment leest zijn logs en antwoordt met dezelfde vier toestanden als elke andere regel. De methode is door een onafhankelijke audit toegelaten op genoemde voorwaarden: observed_until is jouw woord voor wanneer het uitkomstlog werd geëxtraheerd, eenheden werden onafhankelijk gerandomiseerd, de uitkomst van geen enkele eenheid hangt af van de arm van een andere, uitkomsten verschoven niet terwijl de verdeling veranderde, en elke blik leest het hele log. Daarbuiten is het antwoord niet gedekt.

De logs en de declaratie

Twee bestanden, CSV of JSONL: een toewijzingslog met één rij per eenheid (de eenheidskolom die je declareert, en arm), en een uitkomstlog met de eenheid en haar value.

experiment:
  name: checkout-v2
  unit: user
  allocation: {control: 0.5, treatment: 0.5}
  control: control
  treatment: treatment
  assignments: logs/assignments.csv
  outcomes: logs/outcomes.csv
  kind: rate
  rules:
    - {id: lift, kind: superiority}
    - {id: no-harm, kind: non_inferiority, margin: 0.01}
oloproof experiment experiment.yaml --policy release.yaml --json

kind: mean neemt een gedeclareerde range: [low, high]; direction: max leest lager als beter. Een toegewezen eenheid zonder uitkomstrij ontbreekt en wordt aan elke kant op haar slechtste waarde begrensd, tenzij missing_outcome: zero zegt dat geen rij nul betekent. Log een rij voor elke eenheid, nullen inbegrepen: een conversielog met alleen converteerders leest elke andere eenheid als ontbrekend, en het antwoord blijft INSUFFICIENT_EVIDENCE. Aanvullen met nul veronderstelt dat het uitkomstvenster van elke getelde eenheid gesloten is; een eenheid die een uur geleden is toegewezen heeft nog niet geconverteerd, en haar als nul tellen bevoordeelt de arm die sneller converteert. Declareer outcome_window_hours, as_of en observed_until (wanneer de uitkomstlog is geëxtraheerd), met een kolom assigned_at in de toewijzingslog, en alleen eenheden waarvan het venster op as_of gesloten is worden geteld; missing_outcome: zero wordt zonder hen geweigerd, welke monitoring ook. Schrijf elke tijd met een offset of Z (2026-10-11T00:00:00Z); een tijd zonder wordt geweigerd. as_of mag niet later zijn dan observed_until, en geen van beide mag in de toekomst liggen: een uitkomst na de extractie staat niet in de log. Het venster kiest de vraag: een venster van 48 uur beantwoordt "binnen 48 uur geconverteerd", dus het moet de vertraging dekken van de uitkomst die u bedoelt. Eenheid-id's worden als tekst vergeleken na het weghalen van spaties, waarbij 42.0 als 42 wordt gelezen. De exitcode is die van de slechtste regel: 0 PASS, 1 FAIL, 3 INSUFFICIENT_EVIDENCE, 4 MANUAL_REVIEW.

Wat het eerst controleert

  • Steekproefverhouding. De eenheidsaantallen van de armen worden getoetst tegen de gedeclareerde verdeling (exacte binomiaaltoets bij twee armen, chikwadraat bij meer, drempel 0,001). Een afwijking maakt elke regel MANUAL_REVIEW: een kapotte verdeling betekent dat de armen niet meer vergelijkbaar zijn, en meer data van dezelfde soort repareert dat niet. Zoek de oorzaak (een redirect die gebruikers kwijtraakt, een botfilter op één arm) voordat je een verschil leest.
  • Een eenheid in twee armen. De toewijzing is gelekt; elke regel leest MANUAL_REVIEW.
  • Meerdere uitkomstrijen voor één eenheid. Geweigerd, omdat gebeurtenissen van één gebruiker niet onafhankelijk zijn. Declareer aggregate: unit_mean om het eigen gemiddelde van elke eenheid haar uitkomst te maken: het antwoord gaat dan over de gemiddelde gebruiker, niet over de gemiddelde gebeurtenis.
  • Een lege arm. INSUFFICIENT_EVIDENCE totdat ze uitkomsten heeft.
  • Uitkomstrijen die bij geen toegewezen eenheid horen, onder missing_outcome: zero. De nullen steunen op de koppeling, dus een kapotte koppeling zou echte eenheden als nullen lezen; elke regel leest MANUAL_REVIEW totdat de id's overeenkomen. Standaard worden ze geteld en weggelaten.

Hoe het beslist

Het gemiddelde van elke arm krijgt het interval dat Oloproof al voor één run gebruikt (exacte Clopper-Pearson voor een rate, anders het begrensde gemiddelde via weddenschappen), elk met de helft van het foutbudget, en het verschil wordt begrensd door de tegenovergestelde uiteinden van beide. Het geldt bij elke steekproefgrootte en is breder dan een normaal interval uit het tekstboek. Een superioriteitsregel slaagt wanneer het hele interval boven nul ligt; een non-inferioriteitsregel slaagt wanneer haar ondergrens op of boven min de marge ligt.

monitoring: anytime gebruikt het weddenschapsinterval voor beide armen, dat geldt hoe vaak je ook kijkt en wanneer je ook stopt. Dat geldt zolang elke blik de hele log tot dat moment doorgeeft: laat na het lezen nooit rijen per periode weg en filter ze nooit. De standaard, fixed, wordt één keer gelezen bij de steekproefgrootte die je gepland hebt.

Meerdere behandelingen

Declareer treatments: [b, c] in plaats van treatment: en geef elke regel de behandeling die ze vergelijkt (treatment: b). Het interval van elke arm wordt genomen op 1 - alpha/m voor de m armen, zodat elke behandeling min de controle tegelijk wordt gedekt door de union bound: één regel per behandeling, elk één bewering, en de familie houdt samen stand. Elk interval is breder dan met twee armen, omdat elke arm voor de andere betaalt. Bij meerdere behandelingen noemt het rapport de beste onder de behandelingen die elke regel die ze noemt doorstaan, en of die gescheiden is van elke andere behandeling die slaagt: elk verschil, begrensd door dezelfde simultane intervallen, sluit nul uit. Gelijke standen houden de gedeclareerde volgorde en worden nooit gescheiden. De controle van de steekproefverhouding leest al elke arm. De gezamenlijke garantie dekt de armen van één experimentbestand: wie de behandelingen over meerdere bestanden verdeelt, geeft haar op.

Ratiometrieken

Een doorklikratio is klikken gedeeld door sessies, en een gebruiker met veel sessies is niet veel onafhankelijke gebruikers. Declareer de ratio over één rij per eenheid, met bereiken waarin de waarden van elke eenheid liggen:

  ratio: {numerator: clicks, denominator: sessions, numerator_range: [0, 50], denominator_range: [1, 50]}
  aggregate: unit_sum   # only if a unit has several rows: they are added

De schattingsgrootheid is de gemiddelde teller van elke arm gedeeld door zijn gemiddelde noemer. Beide gemiddelden worden begrensd door het toegelaten interval, elk op zijn deel van het foutbudget, en de ratio door hun uiteinden te delen, dus ze geldt bij elke steekproefgrootte zonder normale benadering. Een noemer waarvan het interval nul raakt, geeft geen interval. Een waarde buiten haar bereik is een configuratiefout die de eenheid noemt, nooit afgekapt. Het interval van de deltamethode, meestal smaller, wordt ernaast getoond en beslist nooit.

Een covariaat van vóór de test

Een uitkomst die iets volgt dat vóór de test is gemeten (de uitgaven van vorige maand, de sessies van vorige week) varieert minder zodra dat eruit wordt gehaald. Breng de waarde van elke eenheid van vóór de test mee en een coëfficiënt die vastligt voordat de uitkomsten van deze test worden gelezen:

  covariate: {values: logs/pre_period.csv, range: [0, 500], theta_from: logs/history.csv}

De beslissende uitkomst is Y - theta (X - c). theta wordt gedeclareerd (theta:, met een optionele center:, standaard het midden van het bereik), of geschat op een historiebestand uit een periode vóór de test (kolommen covariate en outcome), nooit op de eigen uitkomsten van deze test. Omdat X vóór de toewijzing is gemeten, en ook vóór de toewijzing vaststond welke eenheden een X hebben, heeft het gecorrigeerde verschil dezelfde schattingsgrootheid. Een eenheid zonder waarde in het bestand wordt niet gecorrigeerd. Een geldige correctie kan toch breder zijn dan geen, omdat het bereik van de gecorrigeerde uitkomst breder is, dus het rapport toont beide intervallen en zegt welk smaller is; het claimt geen besparing.

Nieuwigheid en interferentie, getoond

novelty: {exposure_column: exposed_at, time_column: at, days: 7} leest de eerste blootstelling van elke eenheid uit het toewijzingslog en de tijd van elke uitkomstrij, en toont het verschil in de eerste days van blootstelling en daarna. Een beslist vroeg verschil met een onbeslist of tegengesteld later verschil wordt "possibly a novelty effect" genoemd. De splitsing beslist nooit een regel: eenheden zonder rijen in een band worden eruit weggelaten, niet op hun slechtst begrensd. Ze neemt één uitkomst per rij, geen ratio of covariaat.

cluster_column: team telt de teams die eenheden van meerdere armen bevatten en vraagt of de uitkomst van één eenheid kan afhangen van de arm van een andere. Als dat kan, randomiseer dan per team en declareer dat als de eenheid.

Meerdere behandelingen, ratio's en covariaten zijn op 3 november 2026 toegelaten door een onafhankelijke audit en de bevestiging ervan, op dezelfde voorwaarden als twee armen.

Offline en online

Noem de offline vergelijking en haar metriek (offline_comparison: <id>, offline_metric: pass_rate) en het antwoord meldt of de twee besliste richtingen overeenkomen. Dat wordt gerapporteerd en beslist nooit iets: een onenigheid is een reden om naar de suite te kijken, geen oordeel over de release.

Valkuilen

  • Tussentijds kijken. Een test met vaste horizon elke dag lezen en op een goede dag stoppen blaast valse winsten op. Plan de steekproefgrootte, of declareer monitoring: anytime.
  • Nieuwigheid en leren. Gebruikers reageren op alles wat nieuw is en komen daarna tot rust. Een winst in week één kan wegebben; laat lang genoeg lopen om de cycli van je gebruikers te dekken, en declareer novelty: om de splitsing te zien.
  • Interferentie. Het interval veronderstelt dat de uitkomst van één eenheid niet afhangt van de arm van een andere. Gedeelde voorraad, marktplaatsen, sociale functies of een cache die door één arm is opgewarmd breken dat; randomiseer dan per grotere eenheid (een regio, een team), en declareer cluster_column: om te zien hoe gemengd de clusters zijn.
  • Drift bij een veranderende verdeling. Het interval veronderstelt dat de verdeling tijdens de test constant bleef, of dat de uitkomsten niet driftten terwijl ze veranderde. Een opbouw van 10% naar 50%, of 90/10 en daarna 10/90 over een werkdag en een weekend, weegt de perioden in elke arm anders en mengt een tijdseffect in het verschil; de controle van de steekproefverhouding leest samengevoegde aantallen en ziet het niet. Houd de verdeling vast, of beslis alleen over de periode na de opbouw.
  • De eenheid wisselen. Randomiseer en analyseer per dezelfde eenheid. Gebruikers randomiseren en sessies als onafhankelijk tellen maakt het interval ten onrechte smaller.

Wat dit je niet kan vertellen

  • Of het resultaat geldt voor gebruikers buiten het tijdvenster, de regio of het verkeer van de test.
  • Iets over een metriek die je niet vóór de test hebt gedeclareerd; een achteraf gekozen metriek is een beschrijving.
  • Of je router echt heeft gerandomiseerd: de controle van de steekproefverhouding vangt grote fouten, geen subtiele.
  • Welke behandeling de beste is onder de behandelingen die niet slaagden: alleen behandelingen die slagen worden gerangschikt. Gestratificeerde toewijzing en switchback-ontwerpen zijn niet gebouwd.