Chi-Quadrat-Tests

Hypothesentests
Interpretation
R
Python
Unabhängigkeit und Anpassung bei kategorialen Daten prüfen.

Kurzsteckbrief

Fragestellung Passen beobachtete Häufigkeiten zu einer erwarteten Verteilung, oder hängen zwei kategoriale Merkmale zusammen?
Zielvariable kategorial (nominal oder ordinal, die Ordnung wird nicht genutzt)
Gruppen eine Verteilung gegen eine Vorgabe, oder eine Kreuztabelle aus zwei Merkmalen
Was die Methode liefert Chi-Quadrat-Statistik, Freiheitsgrade, p-Wert, erwartete Häufigkeiten und standardisierte Residuen
R-Funktion chisq.test(x, p = anteile) für die Anpassung, chisq.test(tabelle) für die Unabhängigkeit
Python-Funktion stats.chisquare() und stats.chi2_contingency()
Verwandte Methoden t-Test für zwei Stichproben, Korrelationstests, logistische Regression

Wann diese Methode, wann nicht

Typische Fragestellungen: Verteilen sich Ausschussgründe wie behauptet, hängt die Fehlerart von der Schicht ab, unterscheidet sich die Zustimmung zwischen Standorten. Gemeinsam ist ihnen, dass nur Häufigkeiten vorliegen und es nichts zu mitteln gibt.

Zwei Varianten mit derselben Rechenidee: Beim Anpassungstest kommt die erwartete Verteilung von aussen, beim Unabhängigkeitstest aus den Randsummen der Tabelle selbst.

Situation Stattdessen
Metrische Zielgrösse t-Test oder Varianzanalyse
Erwartete Häufigkeiten unter fünf exakter Test nach Fisher
Gepaarte binäre Daten, etwa vorher und nachher am selben Objekt McNemar-Test
Beide Merkmale ordinal, Richtung interessiert Rangkorrelation
Mehrere Einflussgrössen auf ein binäres Merkmal logistische Regression
Stärke des Zusammenhangs statt Nachweis Cramérs V als Effektstärke

Grundidee und Modell

Aus der Nullhypothese werden erwartete Häufigkeiten abgeleitet, und dann wird gemessen, wie weit die beobachteten davon abweichen.

Die Teststatistik

\[\chi^2 = \sum_i \frac{(B_i - E_i)^2}{E_i}\]

Drei Bestandteile, jeder mit einem Grund. Die Differenz misst die Abweichung. Das Quadrat sorgt dafür, dass sich Abweichungen nach oben und unten nicht aufheben. Die Division durch die erwartete Häufigkeit setzt die Abweichung ins Verhältnis zur Grösse der Zelle.

Der letzte Punkt ist der entscheidende und lässt sich zeigen:

# zehn Faelle Abweichung, einmal bei kleiner, einmal bei grosser Erwartung
data.frame(
  erwartet   = c(20, 1000),
  beobachtet = c(30, 1010),
  beitrag    = round((c(30, 1010) - c(20, 1000))^2 / c(20, 1000), 3)
)
  erwartet beobachtet beitrag
1       20         30     5.0
2     1000       1010     0.1

Dieselben zehn Fälle Abweichung wiegen bei zwanzig erwarteten Fällen fünfzig Mal so schwer wie bei tausend. Das entspricht der Anschauung: Zehn statt zwanzig Fällen ist auffällig, 1010 statt 1000 nicht.

Für den Unabhängigkeitstest ergeben sich die erwarteten Häufigkeiten je Zelle aus den Randsummen:

\[E_{ij} = \frac{\text{Zeilensumme}_i \cdot \text{Spaltensumme}_j}{n}\]

Das ist genau die Regel aus der bedingten Wahrscheinlichkeit, nach der sich bei Unabhängigkeit die Wahrscheinlichkeiten multiplizieren.

Die Freiheitsgrade zählen, wie viel an der Tabelle bei festen Randsummen noch variieren kann: beim Anpassungstest Kategorien minus eins, beim Unabhängigkeitstest das Produkt aus um eins verminderten Zeilen- und Spaltenzahlen. Jeder aus den Daten geschätzte Parameter kostet einen weiteren Freiheitsgrad.

Voraussetzungen und ihre Prüfung

Voraussetzung Was sie bedeutet Prüfung Folge bei Verletzung Vorgehen
Unabhängige Beobachtungen Jede Beobachtung zählt in genau eine Zelle, niemand wird doppelt gezählt Erhebungsdesign, Summe der Tabelle gegen n prüfen p-Wert wertlos Design klären, bei gepaarten Daten McNemar
Absolute Häufigkeiten Gezählt wird, nicht gemittelt oder prozentuiert Tabelleninhalt prüfen Der Test rechnet mit falschem n und liefert beliebige Ergebnisse Rohzahlen einsetzen
Erwartete Häufigkeiten mindestens fünf Die Chi-Quadrat-Verteilung ist eine Näherung chisq.test(...)$expected ansehen Näherung unzuverlässig, in beide Richtungen Kategorien zusammenfassen oder Fisher-Test
Kategorien schliessen sich aus und sind vollständig Jede Beobachtung passt in genau eine Zelle Kategoriendefinition prüfen Zellen sind nicht vergleichbar Kategorien neu bilden

Die erwarteten Häufigkeiten sind die wichtigste Nebenausgabe des Tests. Sie entscheiden erstens, ob die Näherung überhaupt tragfähig ist, und sind zweitens die Grundlage jeder inhaltlichen Deutung: Erst der Vergleich von beobachtet und erwartet sagt, in welche Richtung eine Zelle abweicht. Sie werden deshalb immer mitausgegeben und gehören in den Bericht.

Output lesen

R, chisq.test():

Grösse Was sie sagt Faustregel oder Falle
Testname Chi-squared test for given probabilities oder Pearson's Chi-squared test zeigt an, welche Variante gerechnet wurde
X-squared Summe der normierten quadrierten Abweichungen hat ohne Freiheitsgrade keine Bedeutung
df Freiheitsgrade Kategorien minus eins, oder Produkt der verminderten Dimensionen
p-value Wahrscheinlichkeit dieser oder grösserer Abweichung bei gültiger Nullhypothese sagt nicht, wo die Abweichung sitzt
$expected erwartete Häufigkeiten die Prüfgrösse für die Faustregel und die Grundlage der Deutung
$stdres standardisierte Residuen je Zelle Beträge über etwa 2 markieren auffällige Zellen
Warnmeldung Chi-squared approximation may be incorrect ernst nehmen: mindestens eine erwartete Häufigkeit ist zu klein
correct = TRUE Stetigkeitskorrektur bei Vierfeldertafeln in R Voreinstellung, in Python abschaltbar; erklärt kleine Abweichungen zwischen den Sprachen

Python:

Aufruf Rückgabe Unterschied zu R
stats.chisquare(f_obs, f_exp) Statistik und p-Wert erwartete Häufigkeiten müssen selbst berechnet werden, als absolute Zahlen
stats.chi2_contingency(tab) Statistik, p-Wert, Freiheitsgrade, erwartete Häufigkeiten liefert die Freiheitsgrade ausdrücklich, R nennt sie in der Ausgabe
standardisierte Residuen nicht enthalten in R $stdres, in Python von Hand oder über pingouin
correction=True Stetigkeitskorrektur bei 2x2 in R heisst der Schalter correct

Der häufigste stille Fehler in beiden Sprachen: f_exp beziehungsweise p mit Anteilen statt mit absoluten Häufigkeiten zu füllen. R normiert Anteile selbst, Python erwartet absolute Zahlen; wer das verwechselt, bekommt eine Zahl ohne Fehlermeldung.

Interpretationsfallen

  • Ein signifikanter Test sagt nicht, wo die Abweichung sitzt. Das beantworten die standardisierten Residuen, und ohne sie bleibt das Ergebnis inhaltlich leer.
  • Signifikanz ist keine Stärke. Chi-Quadrat wächst mit n. Für die Stärke des Zusammenhangs gibt es Cramérs V, das zwischen null und eins liegt und nicht von n abhängt.
  • Prozentwerte gehören nicht in den Test. Gerechnet wird mit absoluten Häufigkeiten; Prozente setzen n implizit auf hundert.
  • Ein Zusammenhang ist keine Ursache. Dass Fehlerart und Schicht zusammenhängen, heisst nicht, dass die Schicht die Fehler verursacht; eine dritte Grösse wie das bearbeitete Material kann beides erklären.
  • Die Ordnung ordinaler Kategorien wird verschenkt. Der Test behandelt “gering, mittel, hoch” wie unsortierte Namen; für gerichtete Fragen ist ein Trendtest oder eine Rangkorrelation passender.
  • Gepaarte Daten brauchen McNemar. Zwei Messungen am selben Objekt in eine Kreuztabelle zu schreiben verletzt die Unabhängigkeit.

Ergebnis berichten

Vorlage für den Ergebnissatz:

[Merkmal 1] und [Merkmal 2] hängen zusammen, Chi-Quadrat([df], n = [n]) = [Statistik], p = [p], Cramérs V = [V]. Auffällig sind [Zelle] (standardisiertes Residuum [Wert]) und [Zelle] ([Wert]).

Genannt werden Statistik mit Freiheitsgraden und n, p-Wert, eine Effektstärke, die Kreuztabelle mit beobachteten und erwarteten Häufigkeiten sowie die auffälligen Zellen. Wurde wegen kleiner Erwartungswerte ein exaktes Verfahren verwendet oder wurden Kategorien zusammengefasst, gehört das ebenfalls in den Bericht.

Beispiel für eine Ergebnistabelle:

Kategorie 1 Kategorie 2 Kategorie 3
Gruppe A beobachtet (erwartet)
Gruppe B beobachtet (erwartet)

Abgrenzung zu verwandten Methoden

Methode Wann diese statt des Chi-Quadrat-Tests
Exakter Test nach Fisher erwartete Häufigkeiten unter fünf, kleine Tabellen
McNemar-Test gepaarte binäre Daten, etwa vorher und nachher am selben Objekt
Korrelationstests beide Merkmale ordinal und die Richtung des Zusammenhangs interessiert
t-Test für zwei Stichproben die Zielgrösse ist metrisch
Logistische Regression mehrere Einflussgrössen auf ein binäres Merkmal, mit Effektschätzung

Beispiele

Alle Beispiele rechnen mit fest eingetragenen Häufigkeiten, damit R und Python dieselben Tabellen sehen und jeder Wert im Text nachprüfbar ist.

Frage und Datenlage

An einer Linie werden 140 Ausfälle erfasst, jeder mit seiner Ursache (Werkzeugbruch oder Materialfehler) und der Schicht, in der er auftrat. Die Frage lautet: Hängen Ausfallursache und Schicht zusammen?

Früh Spät Nacht Summe
Werkzeug 38 22 14 74
Material 12 28 26 66
Summe 50 50 40 140

Beide Merkmale sind kategorial, jeder Ausfall zählt in genau eine Zelle, und die Summe der Tabelle ist die Anzahl der Ausfälle. Damit ist der Unabhängigkeitstest das passende Verfahren.

Rechnung

ausfaelle <- matrix(c(38, 22, 14,
                      12, 28, 26),
                    nrow = 2, byrow = TRUE,
                    dimnames = list(ursache = c("Werkzeug", "Material"),
                                    schicht = c("Früh", "Spät", "Nacht")))

ergebnis <- chisq.test(ausfaelle)
ergebnis

    Pearson's Chi-squared test

data:  ausfaelle
X-squared = 17.44, df = 2, p-value = 0.0001633
round(ergebnis$expected, 2)
          schicht
ursache     Früh  Spät Nacht
  Werkzeug 26.43 26.43 21.14
  Material 23.57 23.57 18.86
round(ergebnis$stdres, 3)
          schicht
ursache      Früh   Spät  Nacht
  Werkzeug  4.089 -1.565 -2.677
  Material -4.089  1.565  2.677
ausfaelle = np.array([[38, 22, 14],
                      [12, 28, 26]])
zeilen = ["Werkzeug", "Material"]
spalten = ["Früh", "Spät", "Nacht"]

statistik, p_wert, fg, erwartet = stats.chi2_contingency(ausfaelle)
print("Chi-Quadrat =", round(statistik, 4), " df =", fg,
      " p =", round(p_wert, 6))
Chi-Quadrat = 17.4398  df = 2  p = 0.000163
print(pd.DataFrame(erwartet.round(2), index=zeilen, columns=spalten))
           Früh   Spät  Nacht
Werkzeug  26.43  26.43  21.14
Material  23.57  23.57  18.86
# Standardisierte Residuen: scipy liefert sie nicht, R schon.
n = ausfaelle.sum()
zeilenanteil = ausfaelle.sum(axis=1) / n
spaltenanteil = ausfaelle.sum(axis=0) / n
nenner = np.sqrt(erwartet * np.outer(1 - zeilenanteil, 1 - spaltenanteil))
print(pd.DataFrame(((ausfaelle - erwartet) / nenner).round(3),
                   index=zeilen, columns=spalten))
           Früh   Spät  Nacht
Werkzeug  4.089 -1.565 -2.677
Material -4.089  1.565  2.677

Output Zeile für Zeile

Ausgabe Wert hier Bedeutung Wie er zu lesen ist
Testname Pearson's Chi-squared test Unabhängigkeitstest Bei einer 2x2-Tabelle stünde hier zusätzlich with Yates' continuity correction, siehe Beispiel 3.
X-squared 17.440 Summe der normierten quadrierten Abweichungen über alle sechs Zellen Für sich genommen ohne Bedeutung; sie hängt an Tabellengrösse und n.
df 2 Freiheitsgrade (Zeilen minus 1) mal (Spalten minus 1), also 1 mal 2. Bei festen Randsummen sind nur zwei Zellen frei wählbar, der Rest ergibt sich.
p-value 0.00016 Wahrscheinlichkeit einer mindestens so grossen Abweichung bei Unabhängigkeit Die beiden Merkmale hängen zusammen. Wo, sagt diese Zeile nicht.
$expected kleinster Wert 18.86 kleinste erwartete Häufigkeit Die Voraussetzung “mindestens fünf” ist deutlich erfüllt, die Näherung also tragfähig. Diese Zahl gehört geprüft, bevor der p-Wert gelesen wird.
$stdres Werkzeug/Früh +4.089 standardisiertes Residuum Der auffälligste Wert der Tabelle. In der Frühschicht treten deutlich mehr Werkzeugbrüche auf als bei Unabhängigkeit zu erwarten wäre. Beträge über etwa 2 gelten als auffällig.
$stdres Werkzeug/Nacht -2.677 In der Nachtschicht deutlich weniger Werkzeugbrüche als erwartet.
$stdres Werkzeug/Spät -1.565 Unauffällig. Die Spätschicht trägt wenig zum Befund bei.

Bei einer Tabelle mit zwei Zeilen sind die Residuen der zweiten Zeile exakt die negierten der ersten: +4.089 und -4.089. Das ist keine Redundanz, sondern folgt aus den festen Randsummen. Mehr Werkzeugbrüche in der Frühschicht heisst zwangsläufig weniger Materialfehler dort.

Wie stark ist der Zusammenhang?

Der p-Wert sagt nur, dass ein Zusammenhang besteht. Für seine Stärke gibt es Cramérs V, das zwischen 0 und 1 liegt und im Gegensatz zu Chi-Quadrat nicht mit n wächst.

cramers_v <- function(tabelle) {
  test <- suppressWarnings(chisq.test(tabelle))
  n <- sum(tabelle)
  sqrt(as.numeric(test$statistic) / (n * (min(dim(tabelle)) - 1)))
}

round(cramers_v(ausfaelle), 4)
[1] 0.3529
def cramers_v(tabelle):
    statistik = stats.chi2_contingency(tabelle)[0]
    n = tabelle.sum()
    return np.sqrt(statistik / (n * (min(tabelle.shape) - 1)))


print(round(cramers_v(ausfaelle), 4))
0.3529

Cramérs V beträgt 0.353. Als Anhaltspunkte bei einem Freiheitsgrad in der kleineren Dimension gelten 0.1 klein, 0.3 mittel, 0.5 gross; der Zusammenhang ist also mittelstark. Die Formel ist \(V = \sqrt{\chi^2 / (n \cdot (k-1))}\), wobei \(k\) die kleinere der beiden Dimensionen ist.

Interpretation und Ergebnissatz

Der Befund ist erst mit den Residuen eine Aussage. “Ursache und Schicht hängen zusammen” ist inhaltlich leer; “in der Frühschicht überwiegen Werkzeugbrüche, in der Nachtschicht Materialfehler” ist eine Aussage, mit der sich arbeiten lässt.

Was der Test nicht sagt: warum. Denkbar ist, dass in der Frühschicht andere Aufträge laufen, dass Werkzeuge morgens gewechselt werden oder dass die Erfassung je Schicht unterschiedlich genau ist. Ein Zusammenhang zweier Merkmale lässt offen, ob eines das andere verursacht oder ob ein drittes beide erklärt.

Ausfallursache und Schicht hängen zusammen, Chi-Quadrat(2, n = 140) = 17.44, p < 0.001, Cramérs V = 0.35. Auffällig sind Werkzeugbrüche in der Frühschicht (standardisiertes Residuum +4.09) und Materialfehler in der Nachtschicht (+2.68); die Spätschicht ist unauffällig. Alle erwarteten Häufigkeiten liegen über 18.

Frage und Datenlage

190 Ausfälle verteilen sich auf die fünf Arbeitstage einer Woche. Die Frage lautet: Treten sie gleichmässig über die Woche auf, oder häufen sie sich an bestimmten Tagen?

Mo Di Mi Do Fr
beobachtet 41 33 29 35 52
erwartet 38 38 38 38 38

Anders als in Beispiel 1 gibt es nur ein Merkmal. Verglichen wird nicht mit einem zweiten Merkmal, sondern mit einer von aussen vorgegebenen Verteilung, hier der Gleichverteilung über fünf Tage.

Rechnung

wochentage <- c(Mo = 41, Di = 33, Mi = 29, Do = 35, Fr = 52)

anpassung <- chisq.test(wochentage)          # Gleichverteilung ist Voreinstellung
anpassung

    Chi-squared test for given probabilities

data:  wochentage
X-squared = 8.4211, df = 4, p-value = 0.07732
round(anpassung$expected, 2)
Mo Di Mi Do Fr 
38 38 38 38 38 
round(anpassung$residuals, 3)                # Pearson-Residuen
    Mo     Di     Mi     Do     Fr 
 0.487 -0.811 -1.460 -0.487  2.271 
wochentage = np.array([41, 33, 29, 35, 52])
tage = ["Mo", "Di", "Mi", "Do", "Fr"]
erwartet_gleich = np.full(5, wochentage.sum() / 5)

statistik, p_wert = stats.chisquare(wochentage, erwartet_gleich)
print("Chi-Quadrat =", round(statistik, 4), " df =", len(wochentage) - 1,
      " p =", round(p_wert, 6))
Chi-Quadrat = 8.4211  df = 4  p = 0.077317
print(pd.DataFrame({
    "beobachtet": wochentage,
    "erwartet": erwartet_gleich,
    "Beitrag": ((wochentage - erwartet_gleich) ** 2 / erwartet_gleich).round(3),
    "Residuum": ((wochentage - erwartet_gleich)
                 / np.sqrt(erwartet_gleich)).round(3),
}, index=tage))
    beobachtet  erwartet  Beitrag  Residuum
Mo          41      38.0    0.237     0.487
Di          33      38.0    0.658    -0.811
Mi          29      38.0    2.132    -1.460
Do          35      38.0    0.237    -0.487
Fr          52      38.0    5.158     2.271

Output Zeile für Zeile

Ausgabe Wert hier Wie er zu lesen ist
Testname Chi-squared test for given probabilities Anpassungstest, nicht Unabhängigkeitstest. Der Name ist die Kontrolle, dass die richtige Variante lief.
X-squared 8.421 Summe der fünf Beiträge.
df 4 Kategorien minus eins. Bei fester Gesamtzahl sind nur vier der fünf Häufigkeiten frei.
p-value 0.077 Knapp über 0.05, also kein Nachweis einer ungleichen Verteilung.
Beitrag Freitag 5.158 Die aufschlussreichste Zahl. Der Freitag allein steuert 61 Prozent der gesamten Teststatistik bei; die übrigen vier Tage zusammen nur 3.26.
Residuum Freitag +2.27 Am Freitag treten mehr Ausfälle auf als erwartet.
Residuum Mittwoch -1.46 Am Mittwoch weniger, aber im unauffälligen Bereich.

Interpretation und Ergebnissatz

Hier liegt die Lehre nicht im p-Wert, sondern im Umgang mit einem knappen Ergebnis. Drei Aussagen sind zu unterscheiden:

Aussage Zulässig?
“Die Ausfälle verteilen sich gleichmässig über die Woche.” Nein. Nicht nachgewiesen ist nicht dasselbe wie gleichverteilt.
“Es gibt keinen Wochentagseffekt.” Nein, aus demselben Grund.
“Eine ungleiche Verteilung liess sich nicht nachweisen; auffällig ist der Freitag, der 61 Prozent der Teststatistik trägt.” Ja.

Der Freitag darf jetzt nicht nachträglich einzeln getestet werden. Wer nach einem nicht signifikanten Gesamttest die auffälligste Kategorie herausgreift und gegen den Rest prüft, hat sie anhand der Daten ausgewählt und das Testniveau verloren. Der saubere Weg ist eine neue Erhebung mit der nun vorab formulierten Hypothese “am Freitag treten mehr Ausfälle auf”.

Die Gleichverteilung ist in chisq.test() nur die Voreinstellung. Erwartet man etwas anderes, etwa weil am Freitag kürzer gearbeitet wird, übergibt man das über p = c(...). Wichtig: R erwartet dort Anteile, die sich zu 1 summieren, stats.chisquare() dagegen absolute erwartete Häufigkeiten. Das ist der häufigste stille Fehler dieser Funktion, denn beide Sprachen rechnen ohne Fehlermeldung weiter.

Eine Abweichung von der Gleichverteilung über die Wochentage lässt sich nicht nachweisen, Chi-Quadrat(4, n = 190) = 8.42, p = 0.077. Der Freitag trägt mit einem Beitrag von 5.16 den grössten Anteil an der Teststatistik (52 beobachtete gegenüber 38 erwarteten Ausfällen); eine gezielte Prüfung dieser Hypothese erfordert neue Daten.

Frage und Datenlage

Zwei Werkzeugtypen, je zehn Einsätze, erfasst wird, ob ein Fehler auftrat.

Fehler in Ordnung Summe
neuer Typ 9 1 10
alter Typ 4 6 10
Summe 13 7 20

Zwanzig Beobachtungen in vier Zellen: Hier greift die Faustregel, dass alle erwarteten Häufigkeiten mindestens fünf betragen sollen.

Voraussetzungen prüfen

werkzeugtyp <- matrix(c(9, 1,
                        4, 6),
                      nrow = 2, byrow = TRUE,
                      dimnames = list(typ = c("neu", "alt"),
                                      ergebnis = c("Fehler", "in Ordnung")))

test <- chisq.test(werkzeugtyp)
Warning in chisq.test(werkzeugtyp): Chi-squared approximation may be incorrect
round(test$expected, 2)
     ergebnis
typ   Fehler in Ordnung
  neu    6.5        3.5
  alt    6.5        3.5
werkzeugtyp = np.array([[9, 1],
                        [4, 6]])

erwartet = stats.chi2_contingency(werkzeugtyp)[3]
print(pd.DataFrame(erwartet.round(2), index=["neu", "alt"],
                   columns=["Fehler", "in Ordnung"]))
     Fehler  in Ordnung
neu     6.5         3.5
alt     6.5         3.5
print("kleinste erwartete Häufigkeit:", round(erwartet.min(), 2))
kleinste erwartete Häufigkeit: 3.5

Die kleinste erwartete Häufigkeit beträgt 3.5 und liegt damit unter fünf. R gibt von sich aus die Warnung Chi-squared approximation may be incorrect aus. Python schweigt an dieser Stelle, weshalb die erwarteten Häufigkeiten dort von Hand geprüft werden müssen.

Rechnung

Drei Wege stehen offen. Sie werden hier nebeneinander gezeigt, weil sie zu verschiedenen Ergebnissen führen.

chisq.test(werkzeugtyp, correct = FALSE)   # ohne Stetigkeitskorrektur
Warning in chisq.test(werkzeugtyp, correct = FALSE): Chi-squared approximation
may be incorrect

    Pearson's Chi-squared test

data:  werkzeugtyp
X-squared = 5.4945, df = 1, p-value = 0.01908
chisq.test(werkzeugtyp)                    # mit Yates, in R Voreinstellung
Warning in chisq.test(werkzeugtyp): Chi-squared approximation may be incorrect

    Pearson's Chi-squared test with Yates' continuity correction

data:  werkzeugtyp
X-squared = 3.5165, df = 1, p-value = 0.06076
fisher.test(werkzeugtyp)                   # exakt

    Fisher's Exact Test for Count Data

data:  werkzeugtyp
p-value = 0.05728
alternative hypothesis: true odds ratio is not equal to 1
95 percent confidence interval:
   0.9487882 684.4235629
sample estimates:
odds ratio 
   11.6367 
ohne = stats.chi2_contingency(werkzeugtyp, correction=False)
mit = stats.chi2_contingency(werkzeugtyp, correction=True)
exakt = stats.fisher_exact(werkzeugtyp)

print("ohne Korrektur  Chi-Quadrat =", round(ohne[0], 4), " p =", round(ohne[1], 4))
ohne Korrektur  Chi-Quadrat = 5.4945  p = 0.0191
print("mit Yates       Chi-Quadrat =", round(mit[0], 4), " p =", round(mit[1], 4))
mit Yates       Chi-Quadrat = 3.5165  p = 0.0608
print("Fisher exakt    Odds Ratio =", round(exakt[0], 2), " p =", round(exakt[1], 4))
Fisher exakt    Odds Ratio = 13.5  p = 0.0573

Output Zeile für Zeile

Verfahren Teststatistik p-Wert Entscheidung bei 5 Prozent
Chi-Quadrat ohne Korrektur 5.494 0.019 signifikant
Chi-Quadrat mit Yates-Korrektur 3.517 0.061 nicht signifikant
Exakter Test nach Fisher Odds Ratio 13.5 0.057 nicht signifikant

Drei Verfahren, dieselben zwanzig Beobachtungen, und die Entscheidung kippt. Die Yates-Korrektur zieht von jeder Abweichung 0.5 ab, bevor quadriert wird; das gleicht aus, dass die stetige Chi-Quadrat-Verteilung eine diskrete Zählgrösse annähert. Der Fisher-Test rechnet gar nicht mit einer Näherung, sondern zählt alle Tabellen ab, die bei festen Randsummen mindestens so extrem sind.

Beide Sprachen korrigieren bei Vierfeldertafeln von sich aus: chisq.test() hat correct = TRUE, stats.chi2_contingency() hat correction=True. Wer die Korrektur abschaltet, weil eine Anleitung es so zeigt, bekommt den kleineren p-Wert und hier eine andere Entscheidung.

Zwei Unterschiede bleiben trotzdem. Erstens gibt es die Korrektur nur bei 2x2-Tabellen; bei grösseren Tabellen wie in Beispiel 1 ist der Schalter ohne Wirkung. Zweitens warnt R bei zu kleinen erwarteten Häufigkeiten von sich aus, scipy nicht. In Python muss die Prüfung deshalb von Hand erfolgen, so wie oben unter “Voraussetzungen prüfen”.

Interpretation und Ergebnissatz

Bei zwanzig Beobachtungen und einer erwarteten Häufigkeit von 3.5 ist der Fisher-Test die richtige Wahl, und zwar unabhängig davon, welches Ergebnis er liefert. Die Entscheidung fällt an der Tabelle der erwarteten Häufigkeiten, nicht an den p-Werten.

Das Odds Ratio von 13.5 ist dabei die inhaltlich interessante Zahl: Die Chance auf einen Fehler ist beim neuen Typ rund dreizehnmal so hoch. Dass dieser deutliche Unterschied trotzdem nicht signifikant wird, liegt allein am Umfang. Zehn Einsätze je Typ reichen nicht; das Konfidenzintervall des Odds Ratio, das fisher.test() mit ausgibt, macht diese Unsicherheit sichtbar.

Wegen einer erwarteten Häufigkeit von 3.5 wurde der exakte Test nach Fisher verwendet. Ein Zusammenhang zwischen Werkzeugtyp und Fehlerauftreten lässt sich nicht nachweisen (p = 0.057, n = 20), das geschätzte Odds Ratio beträgt jedoch 13.5. Die Datenlage ist für eine Entscheidung zu dünn.

Frage und Datenlage

Ein Test zweier Varianten einer Seite, je 10 000 Aufrufe. Erfasst wird, ob geklickt wurde.

Klick kein Klick Summe Klickrate
Variante A 5200 4800 10 000 52.0 %
Variante B 5050 4950 10 000 50.5 %

Rechnung

varianten <- matrix(c(5200, 4800,
                      5050, 4950),
                    nrow = 2, byrow = TRUE,
                    dimnames = list(variante = c("A", "B"),
                                    ergebnis = c("Klick", "kein Klick")))

chisq.test(varianten, correct = FALSE)

    Pearson's Chi-squared test

data:  varianten
X-squared = 4.5028, df = 1, p-value = 0.03384
round(cramers_v(varianten), 4)
[1] 0.0149
round(prop.table(varianten, margin = 1), 4)
        ergebnis
variante Klick kein Klick
       A 0.520      0.480
       B 0.505      0.495
varianten = np.array([[5200, 4800],
                      [5050, 4950]])

statistik, p_wert, fg, erwartet = stats.chi2_contingency(varianten,
                                                         correction=False)
print("Chi-Quadrat =", round(statistik, 4), " df =", fg,
      " p =", round(p_wert, 6))
Chi-Quadrat = 4.5028  df = 1  p = 0.033839
print("Cramérs V =", round(cramers_v(varianten), 4))
Cramérs V = 0.0149
print("Klickraten:", (varianten[:, 0] / varianten.sum(axis=1)).round(4))
Klickraten: [0.52  0.505]

Output Zeile für Zeile

Ausgabe Wert hier Wie er zu lesen ist
X-squared 4.503 Über der kritischen Schwelle von 3.84 bei einem Freiheitsgrad.
df 1 Vierfeldertafel.
p-value 0.034 Signifikant auf dem 5-Prozent-Niveau.
Cramérs V 0.015 Und hier steht die eigentliche Antwort: praktisch null. Der Anhaltspunkt für einen kleinen Zusammenhang liegt bei 0.1, also fast siebenmal höher.
Klickraten 52.0 % gegenüber 50.5 % Der Unterschied beträgt 1.5 Prozentpunkte.
standardisierte Residuen ±2.122 Knapp über der Auffälligkeitsschwelle von 2, aber bei 20 000 Beobachtungen sagt das wenig über die Grösse.

Chi-Quadrat wächst proportional mit n. Dieselben Klickraten bei je 1000 statt 10 000 Aufrufen ergäben Chi-Quadrat 0.45 und p = 0.50, also einen glatten Nullbefund. An den Klickraten und an Cramérs V ändert der Umfang dagegen nichts.

Interpretation und Ergebnissatz

Ob 1.5 Prozentpunkte zählen, ist keine statistische Frage. Bei einer Million Aufrufen im Monat sind es 15 000 zusätzliche Klicks und damit womöglich eine klare Entscheidung; bei tausend Aufrufen sind es fünfzehn und damit belanglos. Genau deshalb wird die Relevanzschwelle vor der Auswertung festgelegt und nicht danach an das Ergebnis angepasst.

Berichtet werden deshalb immer beide Ebenen: der Test für die Frage “vom Zufall unterscheidbar” und die Kennzahl in der Einheit der Sache für die Frage “wie gross”. Der p-Wert allein beantwortet die zweite Frage nicht und wird bei grossem n regelmässig überinterpretiert.

Die Klickrate unterscheidet sich zwischen den Varianten, Chi-Quadrat(1, n = 20 000) = 4.50, p = 0.034. Der Unterschied beträgt 1.5 Prozentpunkte (52.0 % gegenüber 50.5 %), Cramérs V = 0.015 und damit praktisch null. Ob der Unterschied eine Entscheidung trägt, hängt am Volumen und nicht am p-Wert.

Verständnisfragen

Warum hat eine Kreuztabelle mit 4 Zeilen und 3 Spalten sechs Freiheitsgrade?

Weil bei festen Randsummen nur drei mal zwei Zellen frei wählbar sind
Richtig. Sind sechs Zellen gefüllt, ergeben sich die übrigen zwangsläufig. Die Freiheitsgrade zählen, wie viel an der Tabelle variieren kann.
Weil 4 plus 3 minus 1 gleich 6 ist
Das Ergebnis stimmt zufällig, die Regel nicht. Bei 5 Zeilen und 3 Spalten käme man damit auf 7 statt auf 8.
Weil zwölf Zellen minus sechs Randsummen bleiben
Die Randsummen sind sieben, und die Rechnung führt hier nur zufällig in die Nähe.

Ein Unabhängigkeitstest ergibt p = 0.002. Wie berichtet man das inhaltlich sinnvoll? ?@fig-residuen-chi-r zeigt den nächsten Schritt.

Mit dem p-Wert, der spricht für sich
Er sagt nur, dass es irgendwo eine Abweichung gibt, nicht wo.
Mit der Aussage über den Zusammenhang und den auffälligen Zellen
Richtig. Erst die standardisierten Residuen zeigen, welche Kombinationen häufiger oder seltener auftreten als bei Unabhängigkeit zu erwarten. Das beantwortet die Frage, die zum Test geführt hat.
Mit der Chi-Quadrat-Statistik statt des p-Werts
Auch sie ist nur eine Gesamtzahl ohne inhaltliche Richtung.

In einer Kreuztabelle liegen mehrere erwartete Häufigkeiten unter fünf. Was folgt daraus?

Der Test wird konservativer und damit sicherer
Er wird nicht sicherer, sondern unzuverlässig, und zwar in beide Richtungen.
Die Näherung durch die Chi-Quadrat-Verteilung ist nicht mehr verlässlich
Richtig. Auswege sind das inhaltlich sinnvolle Zusammenfassen dünn besetzter Kategorien oder ein exaktes Verfahren wie der Test nach Fisher.
Die Stichprobe muss verworfen werden
Sie bleibt brauchbar, nur das Verfahren muss angepasst werden.

Verlinkte Ressourcen