# Load necessary packages
library(knitr)
library(kableExtra)
# Example data
data <- data.frame(
Serotype = c("Serotype 10", "Serotype 31", "Total"),
Alive = c(37, 24, 61),
Dead = c(7, 10, 17),
Total = c(44, 34, 78)
)Lecture 13: Proportions and Categorical data
BIOS 600 - Spring 2026
Announcements
- HW 5 due today
- HW 6 will be posted today
- Lab 6 due tomorrow
Overview
Chi-square test and example
Fisher exact test and example
Very optional reading
P&G Chapters 14, 15
OI: Sections 5.2, 5.3, 6.1, 6.2, 6.4
Pneumococcal disease
The World Health Organization estimates that in developing countries 814,000 children under the age of five die annually from invasive pneumococcal disease (IPD), with an estimated 1.6 million deaths affecting all ages globally.
Several recent studies have identified associations between pneumococcal serotypes (species variations) and patient outcomes from IPD. We consider data from a study of pneumococcal serotypes and mortality (Inverarity et al. (2011)).
Pneumococcal disease
| Serotype | Alive | Dead | Total |
|---|---|---|---|
| Serotype 10 | 37 | 7 | 44 |
| Serotype 31 | 24 | 10 | 34 |
| Total | 61 | 17 | 78 |
Is there an association between serotype and mortality?
Chi-square tests
\(H_0\): There is no association between serotype and mortality
\(H_1\): There is an association between serotype and mortality
Under \(H_0\), we have independence between serotype and mortality, implying that joint probabilities should be equal to products of marginal distributions:
# Example data
data <- data.frame(
Serotype = c("Serotype 10", "Serotype 31", "Total"),
Alive = c("?", "?", 61),
Dead = c("?", "?", 17),
Total = c(44, 34, 78)
)| Serotype | Alive | Dead | Total |
|---|---|---|---|
| Serotype 10 | ? | ? | 44 |
| Serotype 31 | ? | ? | 34 |
| Total | 61 | 17 | 78 |
What counts might we expect under \(H_0\)?
Intuition: Joint and Marginal Probabilities
Suppose we look at two variables: Serotype and Mortality (Alive or Dead).
The joint probability describes the probability of being in both categories (e.g., “Serotype 10 and Alive”).
The marginal probabilities describe totals across one variable:
- P(Serotype 10) = proportion of all patients with Serotype 10
- P(Alive) = proportion of all patients who survived
- P(Serotype 10) = proportion of all patients with Serotype 10
Marginal totals
| Serotype | Total |
|---|---|
| 10 | 44 |
| 31 | 34 |
| Total | 78 |
Marginal totals
| Outcome | Total |
|---|---|
| Alive | 61 |
| Dead | 17 |
| Total | 78 |
What would happen if these two variables were independent?
- Under the null hypothesis of independence, the chance of being both “Serotype 10” and “Alive” should just be:
\[ P(\text{Serotype 10 and Alive}) = P(\text{Serotype 10}) \times P(\text{Alive}) \]
- We can multiply these probabilities by the total number of observations to find the expected counts.
For example:
\[ E_{10,Alive} = P(\text{Serotype 10}) \times P(\text{Alive}) \times N \]
- Using our totals:
\[ P(\text{Serotype 10}) = \frac{44}{78}, \quad P(\text{Alive}) = \frac{61}{78}, \quad N = 78 \]
Compute expected count under independence
# Compute expected count under independence
expected_10_alive <- (44/78) * (61/78) * 78
expected_10_alive[1] 34.41026
We can repeat this for each cell to find what we’d expect if serotype and mortality were truly unrelated.
The chi-square test then measures how far our observed counts deviate from these expected counts.
\[\chi^2 = \sum\frac{(O-E)^2}{E}\]
Intuition
If observed counts are close to what we’d expect under independence, \(\chi^2\) will be small, then we’d fail to reject \(H_0\).
If they’re far from expected, \(\chi^2\) will be large, which might lead to evidence of an association.
Visualizing Observed vs Expected Counts
Let’s visualize how the observed counts compare to the expected counts under independence.
library(dplyr)
Attaching package: 'dplyr'
The following object is masked from 'package:kableExtra':
group_rows
The following objects are masked from 'package:stats':
filter, lag
The following objects are masked from 'package:base':
intersect, setdiff, setequal, union
library(tidyr)
library(ggplot2)
# Observed data
obs_data <- data.frame(
Serotype = c("Serotype 10", "Serotype 31"),
Alive = c(37, 24),
Dead = c(7, 10)
)
# Reshape to long format
obs_long <- obs_data %>%
pivot_longer(cols = Alive:Dead, names_to = "Outcome", values_to = "Observed")
# Compute expected counts under independence
total_alive <- sum(obs_data$Alive)
total_dead <- sum(obs_data$Dead)
total_serotype <- rowSums(obs_data[, 2:3])
N <- sum(total_serotype)
expected_alive <- (total_serotype / N) * total_alive
expected_dead <- (total_serotype / N) * total_dead
exp_data <- data.frame(
Serotype = obs_data$Serotype,
Alive = expected_alive,
Dead = expected_dead
) %>%
pivot_longer(cols = Alive:Dead, names_to = "Outcome", values_to = "Expected")
# Combine observed and expected
plot_data <- obs_long %>%
left_join(exp_data, by = c("Serotype", "Outcome")) %>%
pivot_longer(cols = c(Observed, Expected), names_to = "Type", values_to = "Count")
# Plot observed vs expected
ggplot(plot_data, aes(x = Outcome, y = Count, fill = Type)) +
geom_col(position = "dodge") +
facet_wrap(~Serotype) +
scale_fill_manual(values = c("Observed" = "skyblue", "Expected" = "lightcoral")) +
theme_minimal(base_size = 14) +
labs(
title = "Observed vs Expected Counts by Serotype",
y = "Count",
x = "Outcome",
fill = ""
)
Chi-square tests
The chi-square test compares the observed frequencies in each cell to the expected count under \(H_0\); if the total differences are large enough, we reject \(H_0\):
\[\chi^2 = \sum_{i=1}^{r \times c}\frac{(O_i - E_i)^2}{E_i}\]
which has a \(\chi^2_{(r - 1) \times (c-1)}\) distribution, under \(H_0\).
Chi-square tests
data <- matrix(c(37, 7, 24, 10),
byrow = T, nrow = 2)
data [,1] [,2]
[1,] 37 7
[2,] 24 10
chisq.test(data)
Pearson's Chi-squared test with Yates' continuity correction
data: data
X-squared = 1.3359, df = 1, p-value = 0.2478
What might we conclude at \(\alpha = .05\)?
Conclusion
- Conclusion: Since the p-value is greater than 0.05, we fail to reject the null hypothesis. There is not enough evidence to conclude that survival differs between Serotype 10 and Serotype 31.
Fisher’s exact test
The chi-square test relies on asymptotic results (i.e. large sample size), which might not be appropriate if we have small cell counts (common rules of thumb are >5 or >10 observed in each cell).
Fisher’s exact test calculates an exact p-value under a specific distributional assumption.
| Serotype | Alive | Dead | Total |
|---|---|---|---|
| Serotype 10 | 37 | 7 | 44 |
| Serotype 31 | 24 | 10 | 34 |
| Total | 61 | 17 | 78 |
Fisher’s exact test
Fisher’s exact test relies on the hypergeometric distribution (don’t worry about this, seriously).
| Serotype | Alive | Dead | Total |
|---|---|---|---|
| Serotype 10 | a | c | a + c |
| Serotype 31 | b | d | b + d |
| Total | a + b | c + d | a + b + c + d |
Conditionally on the margins (assumed fixed), then each of the individual cells is distributed according to the hypergeometric distribution. We can thus calculate the exact probabilities of obtaining specific tables.
Fisher’s exact test
Observed data:
| Serotype | Alive | Dead | Total |
|---|---|---|---|
| Serotype 10 | 37 | 7 | 44 |
| Serotype 31 | 24 | 10 | 34 |
| Total | 61 | 17 | 78 |
We could use the hypergeometric distribution to calculate the probability of seeing this table, assuming 61 total alive patients, 17 dead, and 44 and 34 infections of serotypes 10 and 31, respectively (lots of factorials, so you could imagine it wasn’t fun before modern computers).
Fisher’s exact test
Another “possible” table:
| Serotype | Alive | Dead | Total |
|---|---|---|---|
| Serotype 10 | 36 | 8 | 44 |
| Serotype 31 | 25 | 9 | 34 |
| Total | 61 | 17 | 78 |
Here we’ve displayed another table with the same margins, but with some slightly different potential cell counts.
Again we could use the hypergeometric distribution to calculate the probability of observing the cell counts conditionally on fixed margins.
Fisher’s exact test
Fisher’s exact test relies on constructing all possible contingency tables with the same margins, and then summing up probabilities of all tables as extreme or more extreme than the observed data.
What might “more extreme” mean in the context of contingency tables?
Fisher’s exact test
\(H_0\): The proportion of deaths is the same for Serotype 10 and Serotype 31 (no association between serotype and outcome).
\(H_A\): The proportion of deaths differs between serotypes (there is an association)
data <- matrix(c(37, 7, 24, 10), byrow = T, nrow = 2)
data [,1] [,2]
[1,] 37 7
[2,] 24 10
Conducting the Fisher’s Exact test
fisher.test(data)
Fisher's Exact Test for Count Data
data: data
p-value = 0.1758
alternative hypothesis: true odds ratio is not equal to 1
95 percent confidence interval:
0.6465347 7.7636744
sample estimates:
odds ratio
2.179552
- What might we conclude at \(\alpha = 0.05\)?
Conclusion
The Fisher’s Exact Test yielded a p-value of 0.17, which is greater than the conventional significance level of 0.05. Therefore, we fail to reject the null hypothesis.
There is no statistically significant evidence of an association between serotype and survival status in this sample.
Odds ratios
| Serotype | Alive | Dead | Total |
|---|---|---|---|
| Serotype 10 | 37 | 7 | 44 |
| Serotype 31 | 24 | 10 | 34 |
| Total | 61 | 17 | 78 |
Odds of death among serotype 10 patients = \(\frac{7/44}{37/44} = 7/37\)
Odds of death among serotype 31 patients = \(\frac{10/34}{24/34} = 10/24\)
Odds ratio of death comparing 31 to 10 is thus \(\frac{10/24}{7/37} \approx 2.2\)
r by c tables
| Serotype | Alive | Dead | Total |
|---|---|---|---|
| Serotype 10 | 37 | 7 | 44 |
| Serotype 15 | 60 | 12 | 72 |
| Serotype 20 | 97 | 9 | 106 |
| Serotype 31 | 24 | 10 | 34 |
| Total | 218 | 38 | 256 |
We can also extend the chi-square test and Fisher’s exact test to more general contingency tables (although the notion of an odds ratio would need to be adjusted).
Winter Olympics training
Suppose we were interested in whether a particular training regimen was associated with “success” as measured by whether an athlete qualified for being on the US Winter Olympic Team for giant slalom.
Winter Olympics training
| Qualified | Did Not Qualify | |
|---|---|---|
| New Regimen | 129 | 97 |
| Old Regimen | 112 | 114 |
Pearson's Chi-squared test with Yates' continuity correction
data: data
X-squared = 2.2755, df = 1, p-value = 0.1314
Write out the code to 1) construct the data matrix and 2) to conduct a chi square test in R. What might we conclude using alpha = .05?
Overview
Chi-square test and example
Fisher exact test and example (use if sample sizes aren’t large enough)
Next Class
- Tuesday’s class: Non-Parametric Methods