While exploring the relationship between an exposure and an outcome it may be useful to statistically test the strength of association. Hypothesis testing is a statistical inference technique by which one uses observed sample data to interrogate an assumption about a population parameter. Given an assumed probability distribution for the population parameter, a hypothesis test can yield a measure of how likely it would be to encounter the data observed. Two of the hypothesis tests applied to count data in two-by-two tables include Pearson’s chi-squared test and Fisher’s exact test. Both of these techniques are implemented in the twoxtwo
package. The narrative that follows demonstrates how to perform these tests using example datasets created below as well as the titanic
data that ships with twoxtwo
1.
With data organized in a contingency table it is possible to test independence of counts in each cell. At a given cell, one can compare the observed count (\(n_{ij}\)) and expected count (\(\mu_{ij}\)) and arrive at the following test statistic:
\[\chi^2 = \sum \frac{(n_{ij}-\mu_{ij})^2}{\mu_{ij}}\] The Pearson’s \(\chi^2\) (chi-squared) statistic above is parameterized by degrees of freedom. A contingency table has degrees of freedom computed as (number or rows - 1 ) * (number of columns - 1). For a two-by-two table (2 rows, 2 columns), \(\chi^2\) will have 1 degree of freedom.
The \(\chi^2\) test statistic and its degrees of freedom can be used in a hypothesis test. If the probability of observing a statistic as large is less than the stated significance level (\(\alpha\)) then one can reject the null hypothesis of independence among cell counts.
Pearson’s \(\chi^2\) test is implemented in R via the chisq.test()
function in the stats
package. The twoxtwo
package wraps this functionality in its chisq()
function. One of the advantages of chisq()
as opposed to chisq.test()
is that the twoxtwo
implementation returns a tidy hypothesis test output.
Below is an example of using the chisq()
function to explore the association between crew status and survival in the titanic
dataset:
library(twoxtwo)
%>%
titanic chisq(., Crew, Survived)
# # A tibble: 1 x 9
# test estimate ci_lower ci_upper statistic df pvalue exposure outcome
# <chr> <lgl> <lgl> <lgl> <dbl> <int> <dbl> <chr> <chr>
# 1 Pearson'… NA NA NA 46.5 1 8.97e-12 Crew::T… Surviv…
Another method for testing independence between two-by-two cell counts is Fisher’s exact test. After fixing marginal row and column totals, one can cycle through possible combinations of cell counts that would sum to those margins. Each combination yields a unique two-by-two table. For each of these tables the binomial probability of observing the given count in cell A2 can be expressed as:
\[p=\frac{(A+B)!\times{(C+D)!}\times{(A+C)!}\times{(B+D)!}}{A!\times{B!}\times{C!}\times{D!}}\]
To calculate a p-value, one can sum the probabilities for cell counts that are at least as extreme as the observed count. Using this method there is no need for a test statistic since the p-value can be calculated exactly as the sum of binomial probabilities.
The stated origin of Fisher’s exact test is the “tea taster” experiment. The design is detailed in the example below:
library(dplyr)
library(tidyr)
<-
tea tribble(
~poured, ~guessed, ~ n,
"Milk", "Milk", 3,
"Milk", "Tea", 1,
"Tea", "Milk", 1,
"Tea", "Tea", 3
%>%
) uncount(n)
%>%
tea twoxtwo(., poured, guessed,
levels = list(exposure = c("Milk","Tea"), outcome = c("Milk","Tea")))
# | | |OUTCOME |OUTCOME |
# |:--------|:-----------|:------------|:-----------|
# | | |guessed=Milk |guessed=Tea |
# |EXPOSURE |poured=Milk |3 |1 |
# |EXPOSURE |poured=Tea |1 |3 |
To conduct the hypothesis test with the fisher()
function from the twoxtwo
package:
%>%
tea fisher(., poured, guessed,
levels = list(exposure = c("Milk","Tea"), outcome = c("Milk","Tea")))
# # A tibble: 1 x 9
# test estimate ci_lower ci_upper statistic df pvalue exposure outcome
# <chr> <dbl> <dbl> <dbl> <lgl> <lgl> <dbl> <chr> <chr>
# 1 Fisher's… 6.41 0.212 622. NA NA 0.486 poured::… guessed…
As with chisq()
the fisher()
function wraps a function from the stats
package. fisher()
passes arguments for the proposed odds ratio for the hypothesis testing, confidence level for odds ratio estimate, and alternative hypothesis into the interal fisher.test()
function. Examples of several of these parameters in practice are provided below to explore the association between crew status and survival in the titanic
dataset:
%>%
titanic fisher(., exposure = Crew, outcome = Survived, alternative = "greater")
# # A tibble: 1 x 9
# test estimate ci_lower ci_upper statistic df pvalue exposure outcome
# <chr> <dbl> <dbl> <dbl> <lgl> <lgl> <dbl> <chr> <chr>
# 1 Fisher's… 0.516 0.437 Inf NA NA 1.00 Crew::TR… Survive…
%>%
titanic fisher(., exposure = Crew, outcome = Survived, alternative = "less")
# # A tibble: 1 x 9
# test estimate ci_lower ci_upper statistic df pvalue exposure outcome
# <chr> <dbl> <dbl> <dbl> <lgl> <lgl> <dbl> <chr> <chr>
# 1 Fisher'… 0.516 0 0.608 NA NA 2.71e-12 Crew::TR… Surviv…
%>%
titanic fisher(., exposure = Crew, outcome = Survived, alternative = "two.sided")
# # A tibble: 1 x 9
# test estimate ci_lower ci_upper statistic df pvalue exposure outcome
# <chr> <dbl> <dbl> <dbl> <lgl> <lgl> <dbl> <chr> <chr>
# 1 Fisher'… 0.516 0.424 0.626 NA NA 5.19e-12 Crew::TR… Surviv…
%>%
titanic fisher(., exposure = Crew, outcome = Survived, or = 2)
# # A tibble: 1 x 9
# test estimate ci_lower ci_upper statistic df pvalue exposure outcome
# <chr> <dbl> <dbl> <dbl> <lgl> <lgl> <dbl> <chr> <chr>
# 1 Fisher'… 0.516 0.424 0.626 NA NA 9.98e-47 Crew::TR… Surviv…
Agresti, A. (2019). An Introduction to Categorical Data Analysis. Hoboken, New Jersey: Wiley and Sons.