Conditional probability and Bayes' rule
Derive Bayes' rule and use it to invert conditional probabilities.
Often you can measure but you actually want : the test detects the disease with known reliability, but the patient wants the probability of disease given a positive test. Bayes' rule inverts the conditioning, and it is the engine of all probabilistic inference[1].
From the chain rule to Bayes
The chain rule writes the same joint two ways:
Setting the two products equal and dividing by gives Bayes' rule[2]:
The pieces have standard names when is a hypothesis and is observed data:
- is the prior: belief in before seeing .
- is the likelihood: how well predicts the data.
- is the posterior: updated belief after seeing .
- is the evidence (or marginal likelihood), a normalizing constant.
The law of total probability
The evidence usually is not given directly; it is assembled from the hypotheses. If are a partition of the sample space (disjoint, covering everything), then
Derivation. The event splits into the disjoint pieces , so by additivity, and each term is by the chain rule. Substituting into the denominator gives the working form of Bayes' rule:
The base-rate worked example
A disease affects 1 in 1000 people. A test has 99% sensitivity () and 95% specificity (, so the false-positive rate is 0.05). A patient tests positive. What is ?
Under 2%. The rare prior dominates: most positives come from the large healthy population multiplied by the small false-positive rate. Ignoring the base rate is the classic error Bayes' rule guards against.
Posterior odds form
Dividing the posterior for by the posterior for cancels the shared evidence term and gives a clean update:
Belief updating is multiplying prior odds by the likelihood ratio, which is why log-odds turn sequential Bayesian updates into addition.
Check it on arrays
import numpy as np
rng = np.random.default_rng(5)
N = 5_000_000
# Simulate the disease/test scenario
disease = rng.random(N) < 0.001
test_pos = np.where(
disease,
rng.random(N) < 0.99, # sensitivity
rng.random(N) < 0.05, # false positive rate
)
# P(disease | positive) empirically
p_disease_given_pos = disease[test_pos].mean()
print("P(disease | +) simulated:", round(p_disease_given_pos, 4))
# Bayes' rule closed form
num = 0.99 * 0.001
den = 0.99 * 0.001 + 0.05 * 0.999
print("P(disease | +) Bayes: ", round(num / den, 4))
# Posterior odds = likelihood ratio * prior odds
prior_odds = 0.001 / 0.999
lr = 0.99 / 0.05
post_odds = lr * prior_odds
print("posterior prob from odds:", round(post_odds / (1 + post_odds), 4))The simulation, the direct Bayes computation, and the odds form all agree on the counterintuitive ~2%.
Exercises
Exercise 1. A box has two coins: a fair coin () and a biased coin (). You pick one at random and flip it once, getting heads. Compute the posterior probability that you picked the biased coin.
Solution
Step 1. Set the hypotheses and prior. Let = "biased coin" and = "fair coin", each chosen with prior . The data is with likelihoods and .
Step 2. Assemble the evidence by the law of total probability:
Step 3. Apply Bayes' rule:
One head nudges belief in the biased coin from 0.5 up to about 0.64, not all the way: a single head is only mild evidence.
Exercise 2. Continue Exercise 1: you flip the same coin a second time and get heads again. Using the posterior after one flip as the new prior, compute the posterior after two heads. Verify it matches computing from the two-head likelihoods directly.
Solution
Step 1. Update sequentially. The posterior from Exercise 1 becomes the prior: , . A second head has the same likelihoods, , .
Step 2. Compute from scratch with the two-flip likelihood and , starting from the original prior :
The two agree. Bayesian updating is associative: feeding evidence one piece at a time, reusing each posterior as the next prior, lands on the same belief as conditioning on all the evidence at once.
Exercise 3 (code). A spam filter sees the word "free". Spam has prior ; "free" appears in 30% of spam and 2% of ham. Simulate emails and confirm the empirical matches Bayes' rule.
Solution
import numpy as np
rng = np.random.default_rng(0)
N = 5_000_000
spam = rng.random(N) < 0.4
has_free = np.where(
spam,
rng.random(N) < 0.30, # P(free | spam)
rng.random(N) < 0.02, # P(free | ham)
)
p_sim = spam[has_free].mean()
# Bayes' rule closed form
num = 0.30 * 0.4
den = 0.30 * 0.4 + 0.02 * 0.6
p_bayes = num / den
print("P(spam | free) simulated:", round(p_sim, 4))
print("P(spam | free) Bayes: ", round(p_bayes, 4))
assert np.isclose(p_sim, p_bayes, atol=1e-3)The closed form is , and the simulation lands on the same value: seeing "free" raises the spam probability from the 0.4 prior to about 0.91.
Adding a prior to the likelihood is the bridge to the next lesson's distributions, which supply the likelihoods this rule consumes.
References
- [1]Bishop, C. M. (2006). Pattern Recognition and Machine Learning. Springer. §1.2.3, Bayes' theorem and the prior/likelihood/posterior decomposition.↩
- [2]Wasserman, L. (2004). All of Statistics. Springer. §1.6-1.7, conditional probability, the law of total probability, and Bayes' rule. doi:10.1007/978-0-387-21736-9↩