Zwei reale A/B-Test-Datensätze mit kstats analysieren: Proportions-z-Test, Bayessche Posteriors, gepaarter t-Test, Effektgrößen, Power-Analyse und Korrektur für multiples Testen.
Kotlin Notebook
Führen Sie dieses Tutorial als Kotlin Notebook mit interaktiven Kandy-Diagrammen aus — alle Codezellen, DataFrame-Ausgaben und Visualisierungen inklusive.
Dieses Tutorial führt Sie End-to-End durch zwei reale A/B-Test-Datensätze:
E-Commerce-Landingpage (Kaggle) — ~294K Nutzer, zufällig der alten oder neuen Checkout-Seite zugewiesen, binäres Ergebnis (konvertiert / nicht)
Marketingkampagne (Kaggle) — tägliche Kampagnenmetriken (Ausgaben, Klicks, Impressionen, Käufe) für Kontroll- vs. Testkampagne
Dieses Tutorial verwendet Kotlin DataFrame zum Laden der Daten und Kandy zur Visualisierung. Dies sind Kotlin-Notebook-Abhängigkeiten — die statistischen kstats-Funktionen funktionieren in jeder Kotlin-Umgebung.
Vor jedem Hypothesentest muss überprüft werden, ob die Randomisierung korrekt funktioniert hat. Weicht das Aufteilungsverhältnis stärker als zufällig erwartet von 50/50 ab, ist das Experiment kompromittiert — alle nachfolgenden Ergebnisse sind nicht vertrauenswürdig. wird mit einem Ein-Stichproben-Proportions-z-Test geprüft: Ist die beobachtete Aufteilung mit einem 50/50-Verhältnis vereinbar?
Die sollte vor der Datenerhebung stattfinden, um die benötigte Stichprobengröße zu bestimmen. Wir fragen: „Wie viele Nutzer brauchen wir, um einen 1-Prozentpunkt-Anstieg von einer 12%-Baseline mit 80% Power bei α = 0,05 zu erkennen?”
// Effect size for a 1 pp lift: 12% → 13%val h = cohensH(p1 = 0.13, p2 = 0.12)println("Cohen's h for 12% → 13%: ${h.fmt(4)}")val requiredN = proportionZTestRequiredN(effectSize = h, power = 0.8)println("Required per group: $requiredN users")println("Total: ${requiredN * 2} users")
Cohen's h for 12% → 13%: 0.0302Required per group: 17164 usersTotal: 34328 users
// With ~145K per group, what power do we actually have?val actualPower = proportionZTestPower(effectSize = h, n = 145_000)println("Power with N=145K per group: ${(actualPower * 100).fmt(1)}%")val mde = proportionZTestMinimumEffect(n = 145_000, power = 0.8)println("MDE (Cohen's h): ${mde.fmt(4)}")
Power with N=145K per group: 100.0%MDE (Cohen's h): 0.0104
Mit ~145K Nutzern pro Gruppe ist das Experiment für einen 1-pp-Anstieg massiv überpowert — die Power beträgt effektiv 100%. Der beträgt Cohens h = 0,0104, d.h. das Experiment kann extrem kleine Unterschiede erkennen.
Die Kernfrage: Unterscheidet sich die Konversionsrate zwischen Kontroll- und Behandlungsgruppe?
val conversionTest = proportionZTest( successes1 = treatmentConverted, trials1 = treatmentTotal, successes2 = controlConverted, trials2 = controlTotal)val ci = conversionTest.confidenceInterval!!println("Two-Sample Proportion z-Test")println(" z-statistic: ${conversionTest.statistic.fmt(4)}")println(" p-value: ${conversionTest.pValue.fmt(4)}")println(" 95% CI for Δ(p): [${ci.lower.fmt(4)}, ${ci.upper.fmt(4)}]")println(" Significant at α=0.05: ${conversionTest.isSignificant()}")
Two-Sample Proportion z-Test z-statistic: -1.3109 p-value: 0.1899 95% CI for Δ(p): [-0.0039, 0.0008] Significant at α=0.05: false
p > 0,05 — wir können die Nullhypothese nicht ablehnen. Die neue Seite verändert die Konversionsrate nicht signifikant. Das 95%- für die Differenz enthält Null, was mit keinem Effekt übereinstimmt.
Das Unternehmen interessiert nur, ob die neue Seite besser ist. Ein einseitiger Test hat mehr Power, aber p ≈ 0,905 deutet stark darauf hin, dass die Behandlung tatsächlich schlechter ist (oder bestenfalls gleich).
val oneSided = proportionZTest( successes1 = treatmentConverted, trials1 = treatmentTotal, successes2 = controlConverted, trials2 = controlTotal, alternative = Alternative.GREATER)println("One-sided (treatment > control) p = ${oneSided.pValue.fmt(4)}")println("Significant: ${oneSided.isSignificant()}")
One-sided (treatment > control) p = 0.9051Significant: false
Ein beantwortet „gibt es einen Unterschied?”. Die beantwortet „wie groß ist der Unterschied?”Mit N=290K können selbst winzige Unterschiede „signifikant” werden. Cohens h stellt den Unterschied auf einer standardisierten Skala dar, unabhängig von der Stichprobengröße.
Cohens h
Interpretation
< 0,2
Vernachlässigbar
0,2
Klein
0,5
Mittel
0,8+
Groß
val effectH = cohensH(p1 = treatmentRate, p2 = controlRate)println("Cohen's h = ${effectH.fmt(4)}")println("Interpretation: negligible (|h| < 0.2)")
Cohen's h = -0.0049Interpretation: negligible (|h| < 0.2)
Das Diagramm zeigt Cohens h für verschiedene Konversionssteigerungen von einer 12%-Baseline. Die rote Linie markiert die Schwelle für einen „kleinen Effekt” (h = 0,2). Ein 1-pp-Anstieg registriert kaum; man braucht mindestens 3-5 pp, um einen kleinen Effekt zu erreichen.
Das Wald-KI aus dem z-Test kann sich nahe 0 oder 1 schlecht verhalten. Das Wilson-Score-Intervall wird für Konversionsraten-Schätzungen pro Gruppe empfohlen.
Dieselbe Hypothese kann mit einer 2×2-Kontingenztafel getestet werden. Bei großem N entspricht die Chi-Quadrat-Statistik z² — ein nützlicher Plausibilitätscheck.
Der frequentistische Ansatz gibt eine binäre Antwort: ablehnen oder nicht ablehnen. Der Bayessche Ansatz beantwortet eine intuitivere Frage: Wie hoch ist die Wahrscheinlichkeit, dass die Behandlung besser ist?Wir verwenden das Beta-Binomial-Konjugat-Modell:
Prior: Beta(1, 1) — uninformativ (gleichverteilt auf [0, 1])
Der Bayessche Ansatz erzählt dieselbe Geschichte: P(Behandlung > Kontrolle) beträgt nur ~9,5%. Die meisten Unternehmen verlangen P(B > A) > 95%, um eine Änderung auszurollen.Frequentistisch vs. Bayessch:
Frequentistisch: „Wir können H₀ nicht ablehnen” — binäre Entscheidung, p-Wert hängt von der Stichprobengröße ab
Bayessch: „Es gibt eine ~9,5%ige Chance, dass die neue Seite besser ist” — direkte Wahrscheinlichkeitsaussage, intuitiver für Stakeholder
Beide stimmen überein: Es gibt keine überzeugenden Belege, die neue Seite einzuführen.
Wir haben einen uniformen Beta(1,1)-Prior verwendet. Mit N=145K Beobachtungen hat der Prior praktisch keinen Einfluss auf den Posterior. Für kleinere Experimente (N < 1000) sollten Sie einen informativen Prior basierend auf historischen Konversionsraten in Betracht ziehen.
Wir wechseln nun zu stetigen Metriken, für die andere statistische Werkzeuge benötigt werden.Datensatz: Marketing Campaign A/B Testing — tägliche Kampagnenmetriken für Kontroll- vs. Testkampagne über 30 Tage.
val dfControlRaw = DataFrame.readCsv("data/campaign-control.csv", delimiter = ';')val dfTestRaw = DataFrame.readCsv("data/campaign-test.csv", delimiter = ';')// Both CSVs share the same 30 dates. Drop nulls independently, then keep only matched dates.val dfControlClean = dfControlRaw.dropNulls()val dfTestClean = dfTestRaw.dropNulls()val ctrlDates = dfControlClean["Date"].toList().map { it.toString() }.toSet()val testDates = dfTestClean["Date"].toList().map { it.toString() }.toSet()val pairedDates = ctrlDates.intersect(testDates)val dfControlPaired = dfControlClean .filter { "Date"<Any>().toString() in pairedDates } .sortBy("Date")val dfTestPaired = dfTestClean .filter { "Date"<Any>().toString() in pairedDates } .sortBy("Date")
Matched days: 29 (dropped 1 day with missing data)Control: 29 rows × 10 columnsTest: 29 rows × 10 columns
Warum Raten statt Rohwerte? Die beiden Gruppen haben sehr unterschiedliche Exposition: die Behandlungsgruppe erhielt ~30% weniger Impressionen, aber ~12% mehr Budget. Der Vergleich roher Tageswerte würde den Kampagneneffekt mit dem Expositionsunterschied vermischen. Stattdessen normalisieren wir nach täglichen Impressionen: CTR (Klicks / Impressionen) und Kaufrate (Käufe / Impressionen).Warum gepaarte Tests? Da Kontroll- und Testbeobachtungen nach Datum gematcht sind, entfernt ein gepaarter t-Test die tagesbedingte Variabilität und erhöht die Power im Vergleich zu einem Test für unabhängige Stichproben.
Vor der Durchführung von Hypothesentests visualisieren wir die Daten, um ihre Struktur zu verstehen und potenzielle Probleme zu erkennen.
CTR
Kaufrate
Streifendiagramm mit Mittelwert ± SE — jeder Punkt ist ein Tag; der Behandlungsmittelwert liegt deutlich höher, aber einzelne Tage variieren stark.Boxplot — der Behandlungsmedian liegt oberhalb des Kontrollbereichs, mit mehreren Ausreißertagen mit hoher CTR.Zeitreihe — die Behandlung (rot) übertrifft die Kontrolle (blau) durchgehend Tag für Tag, was ein gepaiertes Testdesign unterstützt.
Streifendiagramm mit Mittelwert ± SE — gleiches Muster wie bei CTR: höherer Behandlungsmittelwert mit mehr täglicher Streuung.Boxplot — der Interquartilbereich der Behandlung liegt über der Kontrollgruppe, mit zwei Ausreißertagen mit hoher Kaufrate.Zeitreihe — die Behandlungs-Kaufrate liegt an den meisten Tagen über der Kontrolle, wobei der Abstand kleiner ist als bei der CTR.
Die Behandlungsgruppe zeigt an den meisten Tagen höhere CTR und Kaufrate bei deutlich mehr Varianz. Die Streifen- und Boxplots bestätigen eine klare Aufwärtsverschiebung in der Behandlungsgruppe.
Shapiro-Wilk auf Differenzen besteht (p > 0,05) → gepaarter t-Test als Primärtest
Shapiro-Wilk schlägt fehl → der gepaarte t-Test ist oft robust gegenüber leichter Nicht-Normalität, aber bei N ≈ 29 sollten Ergebnisse vorsichtig interpretiert werden. Der Wilcoxon-Vorzeichen-Rang-Test dient als Sensitivitätsprüfung
Keine Prüfung gleicher Varianzen nötig — gepaarte Tests arbeiten mit Differenzen, nicht separaten Gruppen
Wir berichten sowohl parametrische (gepaarter t-Test) als auch nicht-parametrische (Wilcoxon-Vorzeichen-Rang) Tests.
Sowohl parametrische als auch nicht-parametrische Tests stimmen bei beiden Metriken überein: Die Behandlungskampagne hat signifikant höhere CTR und Kaufrate. Diese Übereinstimmung stärkt unser Vertrauen trotz der nicht-normalen Differenzen.
Für gepaarte Designs ist Cohens dz = Mittelwert(Differenzen) / SD(Differenzen) die geeignete Effektgröße. Sie erfasst, wie groß die Innerhalb-Paar-Differenzen relativ zu ihrer Variabilität sind.
Im Gegensatz zu ungepaarten Cohens d (das die gepoolte Zwischen-Gruppen-SD verwendet) spiegelt dz direkt wider, wie konsistent eine Bedingung die andere über gematchte Paare hinweg übertrifft.
Wir haben zwei Metriken getestet: CTR und Kaufrate. Das Testen multipler Hypothesen erhöht die Chance auf falsch positive Ergebnisse (Fehler 1. Art). Drei Korrekturmethoden:
Verwenden Sie FWER (Bonferroni/Holm) wenn jedes falsch positive Ergebnis kostspielig ist (z.B. regulatorisch). Verwenden Sie FDR (BH) beim Screening vieler Metriken, wenn einige falsch positive Ergebnisse tolerierbar sind.
Sind unsere beiden Testmetriken korreliert? Bonferroni kontrolliert FWER unabhängig von der Abhängigkeitsstruktur, aber bei positiv korrelierten Metriken wird die Korrektur konservativer als nötig.
val allCTR = controlCTR + treatmentCTRval allPurchaseRate = controlPurchaseRate + treatmentPurchaseRateval corrP = pearsonCorrelation(allCTR, allPurchaseRate)val corrS = spearmanCorrelation(allCTR, allPurchaseRate)println("Pearson r = ${corrP.coefficient.fmt(3)}, p = ${"%.2e".format(corrP.pValue)}")println("Spearman ρ = ${corrS.coefficient.fmt(3)}, p = ${"%.2e".format(corrS.pValue)}")
Pearson r = 0.748, p = 1.55e-11Spearman ρ = 0.517, p = 3.22e-05
Streudiagramm — jeder Punkt ist ein Kampagnentag; der positive Trend bestätigt, dass Tage mit höherer CTR auch mehr Käufe pro Impression verzeichnen.Korrelations-Heatmap — Pearson r über alle Raten- und Expositionsmetriken. CTR und Kaufrate (r = 0,75) sind das stärkste Paar.CTR und Kaufrate sind stark korreliert (r = 0,75). Das bedeutet, die Bonferroni/Holm-Korrekturen sind konservativer als nötig — die wahre FWER liegt unter dem nominalen Alpha.
Methodenhinweis zu Akt 2: Die Marketingkampagnen-Gruppen hatten sehr unterschiedliche Expositionsniveaus (Behandlung erhielt ~30% weniger Impressionen, aber ~12% mehr Budget). Alle Akt-2-Analysen verwenden Ratenmetriken (pro Impression) und gepaarte Tests, nach Datum gematcht.
Vorbehalt zur Power in Akt 2: Mit nur ~29 gematchten Paaren hat die Studie moderate Power (~55% für einen mittleren Effekt dz ≈ 0,4). Die signifikanten Ergebnisse entsprechen mittleren bis großen Effekten (dz ≈ 0,59-0,75). Dennoch spiegeln die breiten Konfidenzintervalle die kleine Stichprobe wider.Geschäftsschlussfolgerung: Die neue Landingpage verbessert die Konversionsrate nicht — das Experiment war gut gepowert (>99% für einen 1-pp-Anstieg) und sowohl frequentistische als auch Bayessche Analysen stimmen überein. Für die Marketingkampagne sind sowohl CTR als auch Kaufrate in der Behandlungsgruppe nach Holm-Korrektur signifikant höher. Bei nur 29 gematchten Tagen sind die Effektgrößenschätzungen jedoch ungenau; ein längeres Experiment würde die Konfidenzintervalle einengen.