This walkthrough computes the chapter’s central quantities — prevalence, sensitivity, specificity, and PPV — and shows how the same numbers can be reached two ways: by counting the contingency table, and by Bayes’ theorem.
Setup
source("_common.R")
Warning: package 'tidyverse' was built under R version 4.5.2
Warning: package 'ggplot2' was built under R version 4.5.2
Warning: package 'tibble' was built under R version 4.5.2
Warning: package 'tidyr' was built under R version 4.5.2
Warning: package 'readr' was built under R version 4.5.2
Warning: package 'purrr' was built under R version 4.5.2
Warning: package 'dplyr' was built under R version 4.5.2
Warning: package 'stringr' was built under R version 4.5.2
Warning: package 'forcats' was built under R version 4.5.2
Warning: package 'lubridate' was built under R version 4.5.2
Download _common.R to run this locally — it loads tidyverse and the book’s plot theme. Or replace the source() line with library(tidyverse).
2,000 simulated test results, each with the patient’s true infection status (the unobservable ground truth) and the test result (what the test said). The dataset is calibrated to published rapid-antigen test characteristics: ~91% sensitivity, ~95% specificity, with a 5% community prevalence.
About 5% of patients are truly infected. This is the base rate the chapter keeps coming back to — and the number that AI tools routinely ignore when reasoning about test results.
The chapter’s central insight: even with high sensitivity and high specificity, the PPV — the probability you are actually infected given a positive test — can be far lower than people expect when prevalence is low. The base rate dominates.
Bayes’ theorem from first principles
The same PPV, computed from prevalence + sensitivity + specificity:
The two values match (within rounding). That is the connection the chapter draws: Bayes’ theorem and a contingency table are two ways of seeing the same arithmetic.
Figure 21.1: PPV as a function of prevalence, holding sensitivity and specificity fixed at the test’s values.
At 5% prevalence (the dashed line), PPV is around 50%. At 1% prevalence, PPV drops below 20%. At 0.1%, PPV is in the single digits. The test has not changed; the prior probability has. Mass-screening healthy populations with even a “good” test produces lots of false positives because the base rate is low.
The binomial distribution
The chapter introduces the binomial in the context of “if I test 20 patients and the population positive-rate is p, what is the distribution of the number of positives?”
Figure 21.2: Binomial distribution: number of positive tests in a batch of 20, given the observed positive rate.
dbinom() returns the probability mass for each value of k. pbinom() returns the cumulative probability if you need “probability of at least 5 positives.” rbinom() simulates draws.
Try it yourself
Drop prevalence to 1%. Recompute PPV via Bayes with prevalence = 0.01. What does that imply about mass-screening healthy populations?
Improve specificity. Bump specificity from 95% to 99% (a more accurate test). How much does PPV improve at 5% prevalence? Is the change bigger or smaller than improving sensitivity from 91% to 99%?