Weiter zum Inhalt

R-Kreuztabellen-Tutorial

In diesem Tutorial lernst du, wie du Kreuztabellen erstellst und wie du Beziehungen darin testest und quantifizierst.
Aktualisiert 18. Sept. 2026  · 10 Min. lesen

Mit KI erkunden

ChatGPTClaudePerplexity

So erstellst du eine Tabelle

Starten wir mit einigen Daten. Das Paket MASS enthält Informationen zu 93 Autos, die 1993 in den USA verkauft wurden. Sie sind im Objekt Cars93 gespeichert und umfassen 27 Merkmale pro Auto, von denen einige kategorial sind. Laden wir also das Paket MASS und schauen uns an, welche Fahrzeugtypen in cars93 enthalten sind:

library(MASS)
Cars93$Type

##  [1] Small   Midsize Compact Midsize Midsize Midsize Large   Large  
##  [9] Midsize Large   Midsize Compact Compact Sporty  Midsize Van    
## [17] Van     Large   Sporty  Large   Compact Large   Small   Small  
## [25] Compact Van     Midsize Sporty  Small   Large   Small   Small  
## [33] Compact Sporty  Sporty  Van     Midsize Large   Small   Sporty
## [41] Sporty  Small   Compact Small   Small   Sporty  Midsize Midsize
## [49] Midsize Midsize Midsize Large   Small   Small   Compact Van    
## [57] Sporty  Compact Midsize Sporty  Midsize Small   Midsize Small  
## [65] Compact Van     Midsize Compact Midsize Van     Large   Sporty
## [73] Small   Compact Sporty  Midsize Large   Compact Small   Small  
## [81] Small   Compact Small   Small   Sporty  Midsize Van     Small  
## [89] Van     Compact Sporty  Compact Midsize
## Levels: Compact Large Midsize Small Sporty Van

Es gibt dort 6 Fahrzeugtypen. Die Funktion table zeigt, wie oft jeder Typ vorkommt:

table(Cars93$Type)

##
## Compact   Large Midsize   Small  Sporty     Van
##      16      11      22      21      14       9

prop.table wandelt das in Anteile um:

prop.table(table(Cars93$Type))

##
##    Compact      Large    Midsize      Small     Sporty        Van
## 0.17204301 0.11827957 0.23655914 0.22580645 0.15053763 0.09677419

Dasselbe für die Herkunft der Autos:

table(Cars93$Origin)

##
##     USA non-USA
##      48      45

prop.table(table(Cars93$Origin))

##
##      USA  non-USA
## 0.516129 0.483871

So erstellst du eine Kreuztabelle

Gut, wir haben gesehen, dass unser Datensatz ähnlich viele US- wie Nicht-US-Autos enthält und dass Midsize und Small die häufigsten Typen sind. Aber unterscheiden sich US- und Nicht-US-Autos vielleicht in ihrer Verteilung der Typen?

Schauen wir uns die Fahrzeugtypen in Abhängigkeit von ihrer Herkunft an. Wir können wieder table verwenden, diesmal aber mit zwei Argumenten. Das erste wird zur Zeilenvariable, das zweite zur Spaltenvariable:

table(Cars93$Type, Cars93$Origin)

##          
##           USA non-USA
##   Compact   7       9
##   Large    11       0
##   Midsize  10      12
##   Small     7      14
##   Sporty    8       6
##   Van       5       4

Jetzt sehen wir, was alle wissen: Amerikaner lieben große Fahrzeuge! Die obige Tabelle zeigt die gemeinsame Verteilung zweier kategorialer Variablen (Type und Origin). Solche Tabellen heißen Kreuztabellen.

So erhältst du Randverteilungen aus der Kreuztabelle

Die Funktionen rowSums und colSums erklären sich von selbst:

(tab1<-table(Cars93$Type, Cars93$Origin))

##          
##           USA non-USA
##   Compact   7       9
##   Large    11       0
##   Midsize  10      12
##   Small     7      14
##   Sporty    8       6
##   Van       5       4

rowSums(tab1)

## Compact   Large Midsize   Small  Sporty     Van
##      16      11      22      21      14       9

colSums(tab1)

##     USA non-USA
##      48      45

So berechnest du Prozente aus der Kreuztabelle

prop.table verschachtelt mit table liefert Häufigkeiten:

prop.table(table(Cars93$Type, Cars93$Origin))

##          
##                  USA    non-USA
##   Compact 0.07526882 0.09677419
##   Large   0.11827957 0.00000000
##   Midsize 0.10752688 0.12903226
##   Small   0.07526882 0.15053763
##   Sporty  0.08602151 0.06451613
##   Van     0.05376344 0.04301075

Die Umrechnung in Prozente ist einfach eine Multiplikation mit 100:

prop.table(table(Cars93$Type, Cars93$Origin))*100

##          
##                 USA   non-USA
##   Compact  7.526882  9.677419
##   Large   11.827957  0.000000
##   Midsize 10.752688 12.903226
##   Small    7.526882 15.053763
##   Sporty   8.602151  6.451613
##   Van      5.376344  4.301075

Beachte: Das ist eine gemeinsame Wahrscheinlichkeitsverteilung, aus der wir z. B. sehen, dass etwa 7,5% der Autos klein und amerikanischen Ursprungs sind.

Häufiger interessiert uns die Verteilung einer Variablen innerhalb von Gruppen, die durch eine andere gebildet werden. Hier wirkt die Verteilung der Fahrzeugtypen unter US- bzw. getrennt davon Nicht-US-Autos spannend. Dafür nutzen wir das Argument margin der Funktion prop.table. Es gibt an, ob die Gruppierungsvariable in den Zeilen (margin=1) oder in den Spalten (margin=2) steht:

prop.table(table(Cars93$Type, Cars93$Origin), margin=2)*100

##          
##                 USA   non-USA
##   Compact 14.583333 20.000000
##   Large   22.916667  0.000000
##   Midsize 20.833333 26.666667
##   Small   14.583333 31.111111
##   Sporty  16.666667 13.333333
##   Van     10.416667  8.888889

Jetzt sehen wir klar, dass kleine Autos im Nicht-US-Teil unseres Datensatzes doppelt so häufig sind wie im US-Teil.

Außerdem addieren sich die Prozente in den Spalten zu 100, während in der gemeinsamen Verteilung (ohne margin-Argument) die Summe der ganzen Tabelle 100 ergab.

(tab2<-prop.table(table(Cars93$Type, Cars93$Origin), margin=2)*100)

##          
##                 USA   non-USA
##   Compact 14.583333 20.000000
##   Large   22.916667  0.000000
##   Midsize 20.833333 26.666667
##   Small   14.583333 31.111111
##   Sporty  16.666667 13.333333
##   Van     10.416667  8.888889

colSums(tab2)

##     USA non-USA
##     100     100

Chi-Quadrat-Test

Die häufigste Frage bei Kreuztabellen ist, ob Zeilen- und Spaltenvariable unabhängig sind. Der grundlegendste Ansatz ist der Chi-Quadrat-Test. Er wird ausführlich in diesem Tutorial behandelt. Prüfen wir, ob Type und Origin unabhängig sind:

chisq.test(Cars93$Type, Cars93$Origin)

## Warning in chisq.test(Cars93$Type, Cars93$Origin): Chi-squared
## approximation may be incorrect

##
##  Pearson's Chi-squared test
##
## data:  Cars93$Type and Cars93$Origin
## X-squared = 14.08, df = 5, p-value = 0.01511

Offenbar sind sie es nicht, allerdings erhalten wir auch die Warnung Chi-squared approximation may be incorrect. Das liegt daran, dass die Chi-Quadrat-Statistik der Chi-Quadrat-Verteilung nur näherungsweise folgt. Je mehr Beobachtungen wir haben, desto besser ist die Approximation. Die Funktion chisq.test gibt diese Warnung aus, sobald einer der erwarteten Häufigkeiten unter 5 liegt (was eine „erwartete Häufigkeit“ ist, steht im oben verlinkten Tutorial).

Fishers Exakter Test

Fishers exakter Test ist eine Alternative zum Chi-Quadrat-Test, vor allem wenn die Chi-Quadrat-Approximation unzureichend ist. Führen wir ihn aus:

fisher.test(Cars93$Type, Cars93$Origin)

##
##  Fisher's Exact Test for Count Data
##
## data:  Cars93$Type and Cars93$Origin
## p-value = 0.007248
## alternative hypothesis: two.sided

Die Ergebnisse ähneln denen des Chi-Quadrat-Tests, was aber nicht immer so sein muss. Ein wesentlicher Nachteil von Fishers Test ist, dass er für große Tabellen (oder Stichproben) rechnerisch aufwendig wird.

G-Test

Eine weitere Alternative ist der sogenannte G-Test. Seine Statistik ist ebenfalls näherungsweise Chi-Quadrat-verteilt, aber bei kleinen Stichproben ist diese Approximation oft besser als die des Chi-Quadrat-Tests. Für den G-Test können wir die Funktion GTest aus dem Paket DescTools verwenden. Die Ergebnisse ähneln erneut den beiden vorherigen Tests: Type und Origin sind nicht unabhängig.

library(DescTools)
GTest(Cars93$Type, Cars93$Origin)

##
##  Log likelihood ratio (G-test) test of independence without
##  correction
##
## data:  Cars93$Type and Cars93$Origin
## G = 18.362, X-squared df = 5, p-value = 0.002526

Yates-Korrektur

Bei 2x2-Kreuztabellen kann der Chi-Quadrat-Test mit Yates’ Stetigkeitskorrektur verbessert werden. Sie zieht schlicht 0,5 von jedem | Observed - Expected | Term in der Chi-Quadrat-Statistik ab. Wenn dir das gerade nichts sagt, schau wieder in dieses Tutorial. Außerdem wendet R die Yates-Korrektur automatisch an, wenn sie nötig ist. Betrachten wir die Verfügbarkeit von Schaltgetriebe-Versionen bei US- und Nicht-US-Autos:

(tab3<-table(Cars93$Man.trans.avail, Cars93$Origin))

##      
##       USA non-USA
##   No   26       6
##   Yes  22      39

chisq.test(tab3)

##
##  Pearson's Chi-squared test with Yates' continuity correction
##
## data:  tab3
## X-squared = 15.397, df = 1, p-value = 8.712e-05

Die Yates-Korrektur wurde automatisch angewendet, und R weist uns darauf hin.

Dreidimensionale (oder höhere) Tabelle

Betrachten wir Man.trans.avail, Origin und Type gleichzeitig. table teilt den Datensatz nach der als drittes übergebenen Variable auf:

table(Cars93$Man.trans.avail, Cars93$Origin, Cars93$Type)

## , ,  = Compact
##
##      
##       USA non-USA
##   No    2       0
##   Yes   5       9
##
## , ,  = Large
##
##      
##       USA non-USA
##   No   11       0
##   Yes   0       0
##
## , ,  = Midsize
##
##      
##       USA non-USA
##   No    9       4
##   Yes   1       8
##
## , ,  = Small
##
##      
##       USA non-USA
##   No    0       0
##   Yes   7      14
##
## , ,  = Sporty
##
##      
##       USA non-USA
##   No    0       0
##   Yes   8       6
##
## , ,  = Van
##
##      
##       USA non-USA
##   No    4       2
##   Yes   1       2

ftable liefert eine kompaktere Ansicht:

ftable(Cars93$Man.trans.avail, Cars93$Origin, Cars93$Type)

##              Compact Large Midsize Small Sporty Van
##                                                    
## No  USA            2    11       9     0      0   4
##     non-USA        0     0       4     0      0   2
## Yes USA            5     0       1     7      8   1
##     non-USA        9     0       8    14      6   2

Cochran-Mantel-Haenszel-Test

An den table-Ergebnissen oben sollte klar werden, dass die Beziehung zwischen Origin und Man.trans.avail je nach Type variiert. Bei kleinen und sportlichen Autos gibt es etwa keinen Zusammenhang: Sowohl jedes US- als auch jedes Nicht-US-Auto dieser Typen gibt es mit Schaltgetriebe. Andererseits haben die meisten mittelgroßen US-Autos keine Schaltversion, während die meisten Nicht-US-Autos dieses Typs eine haben. Um für unterschiedlich starke Zusammenhänge in Schichten zu berücksichtigen, kann der Cochran-Mantel-Haenszel-Test verwendet werden:

mantelhaen.test(Cars93$Man.trans.avail, Cars93$Origin, Cars93$Type)

##
##  Mantel-Haenszel chi-squared test with continuity correction
##
## data:  Cars93$Man.trans.avail and Cars93$Origin and Cars93$Type
## Mantel-Haenszel X-squared = 8.0153, df = 1, p-value = 0.004638
## alternative hypothesis: true common odds ratio is not equal to 1
## 95 percent confidence interval:
##   2.226531 76.891307
## sample estimates:
## common odds ratio
##          13.08438

Das dritte Argument von mantelhaen.test identifiziert die Schichten. Vergleiche die obigen Ergebnisse mit denen ohne Schichtung (Yates-Korrektur-Beispiel oben). Der Zusammenhang bleibt bestehen, die Evidenz dafür ist jedoch deutlich schwächer.

Zusammenhangsmaße

Haben wir Zusammenhänge zwischen Variablen entdeckt, wollen wir ihre Stärke messen. Es gibt eine Fülle möglicher Maße. Viele davon sind hier beschrieben. Im Folgenden konzentriere ich mich auf zwei der am häufigsten verwendeten.

Cramers V

V basiert auf der Chi-Quadrat-Statistik:

$$ V = \sqrt{\frac{\chi^2/N}{\min(C-1, R-1)}}, $$ wobei:

  • N die Gesamtsumme der Kreuztabelle ist (Summe aller Zellen),
  • C die Anzahl der Spalten ist,
  • R die Anzahl der Zeilen ist.

V ∈ [0; 1]. Je größer V, desto stärker der Zusammenhang zwischen den Variablen. V = 0 kann als Unabhängigkeit interpretiert werden (denn V = 0 genau dann, wenn χ2 = 0). Der Hauptnachteil von V ist die fehlende präzise Interpretation. Ist V = 0,6 ein starker, mittlerer oder schwacher Zusammenhang?

Die Funktion CramerV aus DescTools berechnet das für uns:

CramerV(Cars93$Type, Cars93$Origin)

## [1] 0.3890967

Noch einmal die Frage: Ist das ein starker, mittlerer oder schwacher Zusammenhang?

Goodman- und Kruskal-Lambda

Goodman- und Kruskal-Lambda ist ein Beispiel für ein Maß, das auf der proportionalen Reduktion der Variation beruht. Diese Maße zielen darauf ab, das R2 – den Bestimmtheitskoeffizienten aus der linearen Regression – nachzuahmen:

  • Sie nehmen Werte aus [0, 1] an,
  • sie geben den Anteil der durch die unabhängige Variable erklärten Variation an.

Verwenden wir die Spaltenvariable als unabhängige. Die Formel für Goodman- und Kruskal-Lambda lautet dann: $$\lambda = \frac{L - \sum_j L_j}{L},$$ wobei:

  • Lj die Summe der nicht-modalen Häufigkeiten in der j-ten Spalte ist,
  • L die Summe der nicht-modalen Häufigkeiten in der „Gesamt“-Spalte ist.

Am besten versteht man das an einem Beispiel. Schauen wir auf Type und Origin, wobei Letzteres die unabhängige Variable ist:

table(Cars93$Type, Cars93$Origin)

##          
##           USA non-USA
##   Compact   7       9
##   Large    11       0
##   Midsize  10      12
##   Small     7      14
##   Sporty    8       6
##   Van       5       4

Das Modalwert der US-Spalte ist 11, also ist L1 = 7 + 10 + 7 + 8 + 5 = 37. Das Modalwert der Nicht-US-Spalte ist 14, also ist L2 = 9 + 0 + 12 + 6 + 4 = 31. Die „Gesamt“-Spalte ist

rowSums(table(Cars93$Type, Cars93$Origin))

## Compact   Large Midsize   Small  Sporty     Van
##      16      11      22      21      14       9

Der Modalwert ist 22, also ist L = 16 + 11 + 21 + 14 + 9 = 71 und $$\lambda = \frac{71 - (37+31)}{71}=0.042$$

Stattdessen können wir die Funktion Lambda aus dem Paket DescTools verwenden:

Lambda(Cars93$Type, Cars93$Origin, direction='row')

## [1] 0.04225352

Der Parameter direction legt fest, wo sich die abhängige Variable befindet (row oder column).

Origin erklärt nur etwa 4% der Variation in Type. Beachte aus der Formel, dass Lambda Variation als Dichotomie zwischen Zugehörigkeit und Nicht-Zugehörigkeit zur größten Gruppe definiert.

Erwähnenswert ist, dass Lambda null ist, wann immer die modale Kategorie jeder Spalte dieselbe ist. Betrachte diese Tabelle:

(lambdaTab<-cbind(c(0,100), c(49,51)))

##      [,1] [,2]
## [1,]    0   49
## [2,]  100   51

chisq.test(lambdaTab)

##
##  Pearson's Chi-squared test with Yates' continuity correction
##
## data:  lambdaTab
## X-squared = 62.279, df = 1, p-value = 2.981e-15

Die modale Kategorie ist in jeder Spalte dieselbe (es ist die zweite Zeile). Daher muss Lambda null sein, trotz eines signifikanten und offensichtlichen Zusammenhangs:

Lambda(lambdaTab, direction='row')

## [1] 0

Wenn du mehr über Statistik in R lernen möchtest, schau dir den DataCamp-Kurs Statistical Modeling in R (Part 1) an.

Themen
R
Datenwissenschaft

R-Kurse

Kurs

Einführung in R

4 Std.
3.1M
Beherrsche die Grundlagen der Datenanalyse in R, einschließlich Vektoren, Listen und Datenrahmen, und übe R mit echten Datensätzen.
Details anzeigenRight Arrow
Kurs Starten
Mehr anzeigenRight Arrow