> ## Documentation Index
> Fetch the complete documentation index at: https://kstats.oremif.org/llms.txt
> Use this file to discover all available pages before exploring further.

# A/B Testing — Varianten mit statistischen Tests vergleichen

> Führen Sie einen vollständigen A/B-Test-Workflow in Kotlin durch: Gruppen zusammenfassen, Annahmen prüfen, Effektgröße messen und für multiples Testen korrigieren mit kstats.

{/*---IMPORT org.oremif.kstats.correlation.samples.DocsSamples--*/}

<Card title="Kotlin Notebook" icon="notebook" href="https://github.com/Oremif/kstats/blob/master/samples/ab-testing.ipynb">
  Probieren Sie diesen Leitfaden als Kotlin Notebook mit Kandy-Visualisierungen aus — führen Sie die Zellen aus, um Diagramme zu sehen und die Daten interaktiv zu erkunden.
</Card>

Dieser Leitfaden führt Sie durch einen A/B-Test, der zwei Varianten eines Checkout-Flows in einer mobilen App vergleicht. Die primäre Metrik ist die Sitzungsdauer (Sekunden); die sekundäre Metrik ist die Anzahl der abgeschlossenen Schritte.

## Experimentdaten

```kotlin theme={"system"}
// Variant A (control): original checkout flow
val controlDurationSec = doubleArrayOf(
    34.2, 41.5, 38.7, 45.1, 36.9, 42.3, 39.8, 44.6, 37.4, 40.1,
    43.2, 35.8, 41.9, 38.3, 46.0, 39.5, 42.7, 37.1, 40.8, 44.3
)

// Variant B (treatment): simplified checkout flow
val treatmentDurationSec = doubleArrayOf(
    29.1, 33.8, 31.5, 35.2, 28.7, 32.4, 30.9, 34.6, 29.8, 33.1,
    31.2, 27.5, 34.0, 30.3, 36.1, 31.8, 33.5, 28.9, 32.7, 35.8
)
```

## Schritt 1: Beide Gruppen zusammenfassen

```kotlin theme={"system"}
val controlSummary = controlDurationSec.describe()
val treatmentSummary = treatmentDurationSec.describe()

controlSummary.mean                // control average
treatmentSummary.mean              // treatment average
controlSummary.standardDeviation   // control spread
treatmentSummary.standardDeviation // treatment spread
```

## Schritt 2: Annahmen prüfen

### Normalität

```kotlin theme={"system"}
val controlNormality = shapiroWilkTest(controlDurationSec)
val treatmentNormality = shapiroWilkTest(treatmentDurationSec)

controlNormality.pValue
treatmentNormality.pValue
```

### Varianzhomogenität

```kotlin theme={"system"}
val variances = leveneTest(controlDurationSec, treatmentDurationSec)
variances.pValue
```

## Schritt 3: Test auswählen und durchführen

<Tabs>
  <Tab title="Parametrisch (normalverteilte Daten)">
    ```kotlin theme={"system"}
    // Welch's t-test (default: equalVariances = false)
    val result = tTest(controlDurationSec, treatmentDurationSec)

    result.statistic
    result.pValue
    result.confidenceInterval // 95% CI for the difference in means
    result.isSignificant()    // true if p < 0.05
    ```

    Wenn der Levene-Test gleiche Varianzen bestätigt hat:

    ```kotlin theme={"system"}
    val equalVar = tTest(
        controlDurationSec,
        treatmentDurationSec,
        equalVariances = true
    )
    equalVar.pValue
    ```
  </Tab>

  <Tab title="Nicht-parametrisch (nicht-normalverteilte Daten)">
    ```kotlin theme={"system"}
    val result = mannWhitneyUTest(controlDurationSec, treatmentDurationSec)

    result.statistic
    result.pValue
    result.isSignificant()
    ```
  </Tab>
</Tabs>

### Effektgröße — Cohens d

Ein <Tooltip tip="Standardisiertes Maß für die Größe eines Unterschieds, unabhängig von der Stichprobengröße">p-Wert</Tooltip> sagt Ihnen, *ob* ein Unterschied existiert; die Effektgröße sagt Ihnen, *wie groß* er ist. Cohens d drückt den Unterschied in Standardabweichungseinheiten aus: |d| \< 0,2 vernachlässigbar, 0,2 klein, 0,5 mittel, 0,8+ groß.

```kotlin theme={"system"}
// Cohen's d: how large is the difference in standard-deviation units?
val d = cohensD(controlDurationSec, treatmentDurationSec)
d // ~2.9 → large effect (|d| ≥ 0.8)
```

### Einseitige Tests

Wenn Sie erwarten, dass die Behandlung die Sitzungsdauer verkürzt:

```kotlin theme={"system"}
val oneSided = tTest(
    controlDurationSec,
    treatmentDurationSec,
    alternative = Alternative.GREATER // control > treatment
)
oneSided.pValue
```

## Schritt 4: Zweite Metrik testen

Wenden Sie denselben Workflow auf die sekundäre Metrik an.

```kotlin theme={"system"}
// Number of completed checkout steps per session
val controlSteps = doubleArrayOf(
    3.0, 4.0, 3.0, 5.0, 3.0, 4.0, 4.0, 5.0, 3.0, 4.0,
    4.0, 3.0, 4.0, 3.0, 5.0, 4.0, 4.0, 3.0, 4.0, 5.0
)
val treatmentSteps = doubleArrayOf(
    5.0, 5.0, 4.0, 5.0, 5.0, 5.0, 4.0, 5.0, 5.0, 5.0,
    4.0, 5.0, 5.0, 4.0, 5.0, 5.0, 5.0, 4.0, 5.0, 5.0
)

// Discrete step counts are typically non-normal
shapiroWilkTest(controlSteps).pValue

val stepsResult = mannWhitneyUTest(controlSteps, treatmentSteps)
stepsResult.pValue
stepsResult.isSignificant()
```

### Korrektur für multiples Testen

Das Testen von zwei Metriken (Dauer und Schritte) erhöht die Falsch-Positiv-Rate. Die <Tooltip tip="Sequentiell ablehnende Methode, die die familienweise Fehlerrate kontrolliert und dabei leistungsfähiger als Bonferroni ist">Holm-Bonferroni</Tooltip>-Korrektur passt die p-Werte entsprechend an.

```kotlin theme={"system"}
// Correct for testing two metrics (duration + steps)
val rawPValues = doubleArrayOf(result.pValue, stepsResult.pValue)
val corrected = holmBonferroniCorrection(rawPValues)

corrected[0] // adjusted p-value for duration
corrected[1] // adjusted p-value for steps
```

## Schritt 5: Korrelation zwischen Metriken

Prüfen Sie, ob die beiden Metriken innerhalb jeder Gruppe zusammenhängen.

```kotlin theme={"system"}
// Within the treatment group: do faster sessions correlate with more completed steps?
val correlation = spearmanCorrelation(treatmentDurationSec, treatmentSteps)

correlation.coefficient // negative means shorter sessions correlate with more steps
correlation.pValue
```

<Note>
  Spearman-Korrelation wird hier bevorzugt, da eine der Metriken (Schritte) ordinal ist.
</Note>

## Gepaarter Vorher/Nachher-Vergleich

Wenn dieselben Nutzer vor und nach einer Änderung gemessen werden, verwenden Sie gepaarte Tests.

```kotlin theme={"system"}
val beforeMs = doubleArrayOf(
    340.2, 415.0, 387.1, 451.3, 369.5, 423.8, 398.0, 446.2, 374.1, 401.5
)
val afterMs = doubleArrayOf(
    310.5, 380.2, 355.8, 410.7, 335.1, 392.4, 365.3, 405.9, 340.8, 371.6
)

val paired = pairedTTest(beforeMs, afterMs)
paired.pValue
paired.confidenceInterval

// Non-parametric alternative
val wilcoxon = wilcoxonSignedRankTest(beforeMs, afterMs)
wilcoxon.pValue

// Paired effect size: Cohen's dz = mean(diff) / sd(diff)
val differences = DoubleArray(beforeMs.size) { beforeMs[it] - afterMs[it] }
val dz = differences.mean() / differences.standardDeviation()
dz // ~6.1 → large effect
```
