==============================================================================================
RAHUL'S ML BLOG -- notes on machine learning, worked out by hand est. 2026
==============================================================================================
home | about | archive | glossary | contact
----------------------------------------------------------------------------------------------
SPECIAL . FOR HACKER NEWS
Bayes by Head-Count: The Shopkeeper's Faulty Machine and the Sick Who Walked Away
============================================================================================
Every Bayes tutorial gives you the formula: P(A|B) = P(B|A) P(A) / P(B). Then it asks
you to swap the letters around until the answer pops out. That is not an explanation -- it
is a recipe card with no kitchen. This page builds the kitchen first: four characters, one
hospital corridor, and ten concrete cases, every answer arrived at by counting patients on
paper. The formula appears at the end, after you have already used it.
TOY NUMBERS, REAL RECIPE. The head-count trick, the two-crowd distinction, the gluing
move, Bayes' rule itself -- all exact. The NUMBERS are a toy: small groups (100
or 1,000 patients) and simple machine rates, chosen so arithmetic fits on paper. Where a
toy choice could mislead, the text says so. Trust the recipe; treat the group sizes as a
sketchpad.
The one move, named here so you are not surprised later:
STOP reading fractions as decimals. Make them PATIENTS IN THIS GROUP. Then every Bayes
question becomes the same plain count: "of the patients who came back with a BEEP
paper, how many are truly sick?"
That single translation turns ten formulas into one arithmetic count.
THE STORY (READ THIS ONCE; EVERY PROBLEM BELOW LIVES INSIDE IT)
Here is the whole room, drawn before any words:
PATIENT THE SHOPKEEPER'S STALL
(feels ------ corridor ---> [FAULTY MACHINE] --> BEEP paper
unwell) beep = cash for |
shopkeeper v
PATIENT returns to DOCTOR
"Given your BEEP paper,
how likely are you sick?"
<- this is Bayes' question
Patients who got silence go home: healthy, OR sick but MISSED (THE WILD).
The machine lies two ways:
truly sick + silence = MISS (false negative: machine stayed quiet)
truly well + BEEP = GLITCH (false positive: machine fired by mistake)
THE PATIENT arrives at the hospital feeling unwell. The doctor forms a suspicion --
this patient might have a disease. To confirm, the doctor writes a referral slip and sends
the patient down the corridor.
THE GREEDY SHOPKEEPER runs the test stall at the end of that corridor. He owns one
faulty machine (sometimes two). Feed him a patient and his machine prints a paper: BEEP
(the test fired) or silence (the test stayed quiet). The shopkeeper is greedy -- each BEEP
paper is a sale. Because every beep is income, he has no incentive to tell you how many
beep papers he printed in total today. That hidden total is the number we need and must
rebuild ourselves.
Note: the shopkeeper sometimes scrawls an extra note on the paper -- a coat colour, a shoe
size, something the machine also recorded. These extra notes are not part of the test result.
The doctor must learn to spot and ignore them. Not every fact on a beep paper belongs in the
probability calculation.
THE FAULTY MACHINE lies in two directions:
- It stays quiet on a truly sick patient. That patient gets a clean paper (no beep), walks
back to the doctor, and the doctor must say "the test found nothing." MISS. The textbook
calls this a false negative.
- It fires on a healthy patient. That patient gets a beep paper, returns alarmed, and the
doctor must deal with a false alarm. GLITCH. The textbook calls this a false positive.
THE DOCTOR sits at the end of the story. The patient returns clutching a beep paper.
The doctor's question: "Given that your paper beeped, how likely are you actually sick?"
Answering that question is Bayes' rule. It is the doctor's question, not the shopkeeper's.
THE WILD. Every patient who got a clean paper and went home is now undiagnosed. They
may be truly healthy (good) or truly sick but missed (bad). The missed-sick patients are
out in the wild -- untreated, possibly worsening, possibly spreading the illness. Note
carefully: Bayes answers the doctor's question (given a beep, how likely sick?). It does
not answer the public-health question (how many sick people did the machine miss?). Those
are two different questions. Bayes is not wrong -- it is simply answering a different one.
The danger of the missed cases is real and separate from the formula.
TWO QUESTIONS THE DOCTOR AND THE SHOPKEEPER ANSWER DIFFERENTLY
Two probability symbols look almost identical but describe completely different groups of
patients.
The comma: P(Sick, Beep) = JOINT = "both, out of the whole group."
P(Sick, Beep) is the share of ALL patients sent for testing who are sick AND came back
with a beep paper. You stand at the hospital entrance and count every patient -- healthy,
sick, beeping, silent. The denominator is the whole group.
100 patients sent for testing. 15 are sick AND have a beep paper.
P(Sick, Beep) = 15/100 = 0.15. Denominator: all 100.
The bar: P(Beep | Sick) = CONDITIONAL = "out of one shrunk back-room."
P(Beep | Sick) is the machine's catch rate: of the patients who ARE truly sick, what share
came back with a beep? First, throw out every healthy patient. Lock only the confirmed-sick
in a back room. Now count: of those sick-only patients, what share got a beep?
Same 100 patients. 50 are truly sick. Of those 50, 15 got a beep.
P(Beep | Sick) = 15/50 = 0.30. Denominator: sick-only back-room.
The link between them (a definition, not a formula to memorise):
P(Sick, Beep) = P(Beep | Sick) x P(Sick)
(whole-group both) = (back-room catch rate) x (whole-group sick share)
Rearranged: P(Beep | Sick) = P(Sick, Beep) / P(Sick). To go from joint to conditional:
divide by the size of the back-room.
Confusing these two is the most common Bayes mistake. The doctor asks about beepers.
The machine answers about the sick. Those are opposite directions of the same relationship.
SHOPKEEPER HIDES THE TOTAL -- SO WE BUILD IT BY GLUING THE TWO PILES
The doctor's question would be trivial if the shopkeeper told us the total share of beep
papers. He refuses. So we build that total ourselves from the only two ways a beep paper
is ever born:
PILE 1: a SICK patient the machine caught.
Count = P(Beep | Sick) x P(Sick)
PILE 2: a HEALTHY patient the machine glitched on.
Count = P(Beep | Healthy) x P(Healthy)
Every beep paper came from exactly one of these two piles. They never overlap (a patient
is either sick or healthy, not both). So we simply add them:
P(Beep) = P(Beep | Sick) x P(Sick) + P(Beep | Healthy) x P(Healthy)
Mathematicians call this the Law of Total Probability. In the story it is just adding two
non-overlapping piles of papers to get the shopkeeper's hidden total.
Three sub-groups instead of two (disease A, disease B, or healthy)? Glue three piles:
P(Beep) = P(Beep|A)xP(A) + P(Beep|B)xP(B) + P(Beep|Healthy)xP(Healthy)
Once we have the total, the doctor's question almost writes itself:
P(Sick | Beep) = pile of sick beepers
----------------------
total beep papers (all piles glued)
= P(Beep | Sick) x P(Sick)
-----------------------------------------
P(Beep|Sick)xP(Sick) + P(Beep|Healthy)xP(Healthy)
That is Bayes' rule. You never need to memorise it. Just build the two piles, glue them,
divide the pile you want by the total.
TEN PATIENTS, TEN VISITS TO THE SHOPKEEPER
The same move in every case: imagine a concrete group of patients, run them through the
machine, count the beep papers by pile, divide.
─────────────────────────────────────────────────────────────────────────────────────
VISIT 1 -- conditional from joint (which back-room?)
Given: P(Sick, Beep) = 0.15 and P(Sick) = 0.50. Find: P(Beep | Sick).
100 patients sent to the shopkeeper. 50 are truly sick (P(Sick) = 0.50). Of the whole 100,
15 are sick AND returned with a beep paper (P(Sick, Beep) = 0.15). The doctor locks the 50
sick patients in the back room. 15 of them have a beep paper.
P(Beep | Sick) = 15 / 50 = 0.30.
─────────────────────────────────────────────────────────────────────────────────────
VISIT 2 -- joint from conditional (scale the back-room back out)
Given: P(Sick) = 0.30 and P(Beep | Sick) = 0.20. Find: P(Sick, Beep).
100 patients. 30 are truly sick. The machine catches only 20% of them:
30 x 0.20 = 6 sick patients came back with a beep. Out of the whole 100:
P(Sick, Beep) = 6 / 100 = 0.06.
─────────────────────────────────────────────────────────────────────────────────────
VISIT 3 -- classic Bayes: the shopkeeper hands over his total (a rare day)
Given: P(Sick) = 0.30, P(Beep | Sick) = 0.80, P(Beep) = 0.50. Find: P(Sick | Beep).
100 patients. 30 truly sick; machine catches 80% of them = 24 sick beepers. Today the
shopkeeper, unusually, volunteers that he printed 50 beep papers total. A patient returns
clutching one of those 50. The doctor asks: truly sick?
P(Sick | Beep) = 24 / 50 = 0.48.
─────────────────────────────────────────────────────────────────────────────────────
VISIT 4 -- rare disease (1%): the shopkeeper hides his total; we glue it
Given: P(D) = 0.01, P(Beep | D) = 0.90, P(Beep | Healthy) = 0.10.
Find: P(D | Beep).
1,000 patients sent down the corridor this week.
Sick (1% = 10): machine catches 90% -> 9 true beeps. 1 missed -- goes home to wild.
Healthy (990): machine glitches 10% -> 99 false beeps.
Glue the two beep piles: 9 + 99 = 108 beep papers.
A patient returns clutching one. Truly sick?
P(D | Beep) = 9 / 108 = 0.0833. About 8%.
The rarity illusion: the disease is so rare (only 10 sick among 1,000) that the machine's 10%
glitch rate on the huge healthy crowd (99 false beeps) drowns the true sick pile (9 beeps).
A beep means disease only 8% of the time -- even though the machine catches 90% of the sick.
In the wild: 1 sick patient per 1,000 walked home with a clean paper, undiagnosed.
─────────────────────────────────────────────────────────────────────────────────────
VISIT 5 -- common disease (30%): when prevalence fights the glitch
Given: P(Sick) = 0.30, P(Beep | Sick) = 0.60, P(Beep | Healthy) = 0.20.
Find: P(Sick | Beep).
100 patients.
Sick (30): machine catches 60% -> 18 true beeps. 12 missed -- go home to wild.
Healthy (70): machine glitches 20% -> 14 false beeps.
Glue: 18 + 14 = 32 beep papers.
P(Sick | Beep) = 18 / 32 = 0.5625. About 56%.
Prevalence fights back: 30% sick is common enough that the 18 true beeps outnumber the
14 glitches and the doctor can say "more likely sick than not." But 12 sick patients walked
home with clean papers -- more absolute misses than in Visit 4.
─────────────────────────────────────────────────────────────────────────────────────
VISIT 6 -- rarity illusion again, slightly better machine (2%)
Given: P(D) = 0.02, P(Beep | D) = 0.80, P(Beep | Healthy) = 0.05.
Find: P(D | Beep).
1,000 patients.
Sick (2% = 20): machine catches 80% -> 16 true beeps. 4 missed -- go home to wild.
Healthy (980): machine glitches 5% -> 49 false beeps.
Glue: 16 + 49 = 65 beep papers.
P(D | Beep) = 16 / 65 = 0.2462. About 25%.
The machine is better than in Visit 4 (80% catch, only 5% glitch) -- yet a beep is still
only right one time in four. Why? The sick crowd is tiny (20 patients). Even a small 5%
glitch rate on the 980 healthy patients produces 49 false beeps -- three times the 16 true
beeps. Rarity is the dominant force. The machine's improvement barely moves the needle.
─────────────────────────────────────────────────────────────────────────────────────
VISIT 7 -- two causes: Disease AND a harmless Sniffle both trigger the machine
The shopkeeper's machine cannot distinguish the real Disease (D, rare at 1%) from a
harmless seasonal Sniffle (S, common at 5%). It fires at a different rate depending on
which a patient carries. Disease and Sniffle are independent -- a patient can have both,
one, or neither.
Given: P(D)=0.01, P(S)=0.05.
Machine rates: P(Beep | D, S)=0.99 P(Beep | D, no S)=0.95
P(Beep | no D, S)=0.90 P(Beep | no D, no S)=0.02 (pure glitch).
Find: P(D | Beep).
10,000 patients sent to the shopkeeper. Four corners of the waiting room:
D and S: 0.01 x 0.05 x 10,000 = 5 patients -> beep 99% -> 4.95 beep papers
D only: 0.01 x 0.95 x 10,000 = 95 patients -> beep 95% -> 90.25 beep papers
S only: 0.99 x 0.05 x 10,000 = 495 patients -> beep 90% -> 445.50 beep papers
Neither: 0.99 x 0.95 x 10,000 = 9405 patients -> beep 2% -> 188.10 beep papers
Glue all four piles: 4.95 + 90.25 + 445.50 + 188.10 = 728.80 beep papers.
Of those, truly-Disease beeps = 4.95 + 90.25 = 95.20.
P(D | Beep) = 95.20 / 728.80 = 0.1306. About 13%.
The harmless Sniffle (common, 5%) triggers the machine at 90%, flooding the pile with
445 sniffle-beeps. Pure glitches add 188 more. Real Disease beeps (95) are swamped.
A beep means the actual Disease only 13% of the time.
─────────────────────────────────────────────────────────────────────────────────────
VISIT 8 -- two machines: the shopkeeper runs every patient through BOTH (and charges twice)
The shopkeeper acquired a second faulty machine. He now runs every patient through Machine
1 and then Machine 2. The doctor only acts when BOTH papers beep.
Given: P(H)=0.10.
Machine 1: P(M1|H)=0.80, P(M1|Healthy)=0.20.
Machine 2: P(M2|H)=0.70, P(M2|Healthy)=0.10.
Find: P(H | M1 and M2).
1,000 patients through both machines.
Sick (10% = 100):
Machine 1 beeps 80% -> 80 patients.
Of those 80, Machine 2 beeps 70% -> 56 get both beep papers.
44 sick patients got fewer than two beeps -- sent home -- into the wild.
Healthy (900):
Machine 1 glitches 20% -> 180 patients.
Of those 180, Machine 2 glitches 10% -> 18 get both beep papers.
Glue the double-beepers: 56 + 18 = 74 double-beep papers.
A patient returns with both papers beeping. Truly sick?
P(H | M1, M2) = 56 / 74 = 0.7568. About 76%.
Two machines in series raised the doctor's precision dramatically -- from a single-machine
precision of roughly 44% (try Visit 5's numbers with H=10%) to 76%. The price paid: 44 sick
patients failed both machines and walked home undiagnosed. Running patients through more
machines raises precision at the cost of more misses. There is no free lunch.
─────────────────────────────────────────────────────────────────────────────────────
VISIT 9 -- the shopkeeper's extra scribble (the distraction to ignore)
The shopkeeper, to look thorough, also scrawls on every paper: "patient wore a red coat"
(true half the time at random, unrelated to disease). The doctor is handed a beep paper
with this scribble. The red-coat note has no connection to the disease.
Given: P(D)=0.30, P(Beep|D)=0.80, P(Beep|Healthy)=0.20.
Find: P(D | Beep).
100 patients.
Sick (30): machine beeps 80% -> 24 true beeps. 6 missed -- go home to wild.
Healthy (70): machine glitches 20% -> 14 false beeps.
Glue: 24 + 14 = 38 beep papers.
P(D | Beep) = 24 / 38 = 0.6316. About 63%.
The red-coat scribble never appears in the calculation. The count uses only the beep and
the disease rate. The mistake is spending mental effort on the red-coat variable -- including
it in the denominator, trying to condition on it -- when it has no power to change the
answer. The head-count makes that mistake visible automatically: you counted beepers, not coat
colours.
─────────────────────────────────────────────────────────────────────────────────────
VISIT 10 -- the rarity illusion in one table (what actually moves the needle)
Visits 4, 6, and 9 in one row each:
Visit Disease rate Catch rate Glitch rate P(D|Beep) Missed per 1,000
----- ------------ ---------- ----------- --------- ----------------
4 1% 90% 10% 8.3% 1
6 2% 80% 5% 24.6% 4
9 30% 80% 20% 63.2% 60
Reading down the precision column: going from 1% to 2% prevalence (doubling the sick
share) with a better machine triples precision from 8% to 25%. Going to 30% prevalence
pushes precision to 63%. Prevalence is the dominant lever; the machine's improvement is
secondary. A beep from a rare-disease machine is almost always a glitch.
Reading the missed column: precision and recall pull opposite directions. At 30% sick,
60 per 1,000 patients walk home with clean papers and enter the wild. Raising the catch
rate helps this; nothing eliminates it entirely.
PYTHON: THREE VISITS HARD-CODED, THEN A FULL SIMULATION
Visits 4, 5, and 6 shown as explicit variables -- no loops, every patient count on its own
line -- so the head-count move stays visible. Then a full Monte Carlo simulation (100,000
patients per scenario) to verify all six non-trivial visits.
# -----------------------------------------------------------------------
# PART 1: VISITS 4, 5, 6 -- explicit head-counts, no loops
# Every quantity is a named variable matching the story above.
# -----------------------------------------------------------------------
# ---- VISIT 4: rare disease 1%, catch 90%, glitch 10%, group of 1000 ----
group_v4 = 1000
sick_v4 = group_v4 * 0.01 # 10 (truly sick, sent to shopkeeper)
healthy_v4 = group_v4 - sick_v4 # 990
true_beeps_v4 = sick_v4 * 0.90 # 9 (sick patients who came back with beep)
missed_v4 = sick_v4 * 0.10 # 1 (sick patients who got clean paper -> wild)
false_beeps_v4 = healthy_v4 * 0.10 # 99 (healthy patients the machine glitched on)
total_beeps_v4 = true_beeps_v4 + false_beeps_v4 # 108 (all beep papers)
precision_v4 = true_beeps_v4 / total_beeps_v4 # P(D | Beep)
print(f"Visit 4: true_beeps={true_beeps_v4:.0f}, glitch_beeps={false_beeps_v4:.0f}, "
f"total={total_beeps_v4:.0f}, P(D|Beep)={precision_v4:.4f}")
print(f"Visit 4: missed into wild = {missed_v4:.0f} per {group_v4} patients")
# Visit 4: true_beeps=9, glitch_beeps=99, total=108, P(D|Beep)=0.0833
# Visit 4: missed into wild = 1 per 1000 patients
# ---- VISIT 5: 30% disease, catch 60%, glitch 20%, group of 100 ----
group_v5 = 100
sick_v5 = group_v5 * 0.30 # 30
healthy_v5 = group_v5 - sick_v5 # 70
true_beeps_v5 = sick_v5 * 0.60 # 18
missed_v5 = sick_v5 * 0.40 # 12 (into wild)
false_beeps_v5 = healthy_v5 * 0.20 # 14
total_beeps_v5 = true_beeps_v5 + false_beeps_v5 # 32
precision_v5 = true_beeps_v5 / total_beeps_v5
print(f"Visit 5: true_beeps={true_beeps_v5:.0f}, glitch_beeps={false_beeps_v5:.0f}, "
f"total={total_beeps_v5:.0f}, P(Sick|Beep)={precision_v5:.4f}")
print(f"Visit 5: missed into wild = {missed_v5:.0f} per {group_v5} patients")
# Visit 5: true_beeps=18, glitch_beeps=14, total=32, P(Sick|Beep)=0.5625
# Visit 5: missed into wild = 12 per 100 patients
# ---- VISIT 6: 2% disease, catch 80%, glitch 5%, group of 1000 ----
group_v6 = 1000
sick_v6 = group_v6 * 0.02 # 20
healthy_v6 = group_v6 - sick_v6 # 980
true_beeps_v6 = sick_v6 * 0.80 # 16
missed_v6 = sick_v6 * 0.20 # 4 (into wild)
false_beeps_v6 = healthy_v6 * 0.05 # 49
total_beeps_v6 = true_beeps_v6 + false_beeps_v6 # 65
precision_v6 = true_beeps_v6 / total_beeps_v6
print(f"Visit 6: true_beeps={true_beeps_v6:.0f}, glitch_beeps={false_beeps_v6:.0f}, "
f"total={total_beeps_v6:.0f}, P(D|Beep)={precision_v6:.4f}")
print(f"Visit 6: missed into wild = {missed_v6:.0f} per {group_v6} patients")
# Visit 6: true_beeps=16, glitch_beeps=49, total=65, P(D|Beep)=0.2462
# Visit 6: missed into wild = 4 per 1000 patients
# The rarity illusion visible in one comparison:
print(f"\nRarity illusion: Visit 4 precision={precision_v4:.1%}, "
f"Visit 6 precision={precision_v6:.1%}")
# Rarity illusion: Visit 4 precision=8.3%, Visit 6 precision=24.6%
# Doubling prevalence (1%->2%) with a better machine triples precision.
# Prevalence is the dominant lever, not the machine's catch rate.
Output of Part 1:
Visit 4: true_beeps=9, glitch_beeps=99, total=108, P(D|Beep)=0.0833
Visit 4: missed into wild = 1 per 1000 patients
Visit 5: true_beeps=18, glitch_beeps=14, total=32, P(Sick|Beep)=0.5625
Visit 5: missed into wild = 12 per 100 patients
Visit 6: true_beeps=16, glitch_beeps=49, total=65, P(D|Beep)=0.2462
Visit 6: missed into wild = 4 per 1000 patients
Rarity illusion: Visit 4 precision=8.3%, Visit 6 precision=24.6%
# -----------------------------------------------------------------------
# PART 2: FULL SIMULATION -- 100,000 patients, every visit verified
# Each patient walks the same path: doctor -> shopkeeper -> machine(s) -> return.
# One loop per visit. Loops here hide the per-patient coin flip, not the Bayes
# logic -- 100,000 explicit if-statements would say the same thing more slowly.
# -----------------------------------------------------------------------
Nothing above needs a computer -- a head-count of a round 1000 patients gives the
exact answer. Visit 4, the rare disease (1% sick, machine catches 90%, glitches 10%):
1000 patients
|
+-- 10 sick (1%) --beep 90%--> 9 true beeps
| --miss 10%--> 1 walks out clean (into the wild)
|
+-- 990 well (99%) --glitch 10%--> 99 false beeps
--quiet 90%--> 891 sent home
beeps total = 9 + 99 = 108
P(sick | beep) = 9 / 108 = 0.0833
The 99 false beeps from the huge well crowd swamp the 9 true ones -- the whole rarity
illusion, in nine and ninety-nine. Every visit is the same head-count, different dials:
visit prevalence catch glitch per 1000: true false P(sick|beep)
----- ---------- ----- ------ ---- ----- ------------
4 1% 90% 10% 9 99 0.0833
5 30% 60% 20% 180 140 0.5625
6 2% 80% 5% 16 49 0.2462
9 30% 80% 20% 240 140 0.6316
(visit 7 adds a sniffle, visit 8 runs two machines in series -- same head-count
idea, worked in those visits above; theory 0.1306 and 0.7568)
And for the day you meet a computer, the same answers from a random simulation that
converges on those head-counts:
import random
def run_simulation(n=100_000):
print(f"Simulating {n:,} patients through the shopkeeper's corridor...")
print("=" * 62)
# ---- VISIT 4: rare disease 1%, catch 90%, glitch 10% ----
v4_true=0; v4_glitch=0; v4_missed=0
for _ in range(n):
sick = random.random() < 0.01
if sick:
if random.random() < 0.90: v4_true += 1
else: v4_missed += 1 # clean paper -> wild
else:
if random.random() < 0.10: v4_glitch += 1
total = v4_true + v4_glitch
print(f"Visit 4 (1% disease): beeps={total}, "
f"true={v4_true}, glitch={v4_glitch}, missed->wild={v4_missed}")
print(f" P(D|Beep)={v4_true/total:.4f} Theory: 0.0833")
print("-" * 62)
# ---- VISIT 5: 30% disease, catch 60%, glitch 20% ----
v5_true=0; v5_glitch=0; v5_missed=0
for _ in range(n):
sick = random.random() < 0.30
if sick:
if random.random() < 0.60: v5_true += 1
else: v5_missed += 1
else:
if random.random() < 0.20: v5_glitch += 1
total = v5_true + v5_glitch
print(f"Visit 5 (30% disease): beeps={total}, "
f"true={v5_true}, glitch={v5_glitch}, missed->wild={v5_missed}")
print(f" P(Sick|Beep)={v5_true/total:.4f} Theory: 0.5625")
print("-" * 62)
# ---- VISIT 6: 2% disease, catch 80%, glitch 5% ----
v6_true=0; v6_glitch=0; v6_missed=0
for _ in range(n):
sick = random.random() < 0.02
if sick:
if random.random() < 0.80: v6_true += 1
else: v6_missed += 1
else:
if random.random() < 0.05: v6_glitch += 1
total = v6_true + v6_glitch
print(f"Visit 6 (2% disease): beeps={total}, "
f"true={v6_true}, glitch={v6_glitch}, missed->wild={v6_missed}")
print(f" P(D|Beep)={v6_true/total:.4f} Theory: 0.2462")
print("-" * 62)
# ---- VISIT 7: Disease(1%) + Sniffle(5%), four-corner machine ----
v7_beep_disease=0; v7_beep_other=0; v7_missed=0
for _ in range(n):
has_disease = random.random() < 0.01
has_sniffle = random.random() < 0.05
if has_disease and has_sniffle: rate = 0.99
elif has_disease and not has_sniffle: rate = 0.95
elif not has_disease and has_sniffle: rate = 0.90
else: rate = 0.02 # pure glitch
beeped = random.random() < rate
if beeped:
if has_disease: v7_beep_disease += 1
else: v7_beep_other += 1
elif has_disease: v7_missed += 1 # sick with clean paper -> wild
total = v7_beep_disease + v7_beep_other
print(f"Visit 7 (D+Sniffle): beeps={total}, "
f"disease_beeps={v7_beep_disease}, other={v7_beep_other}, "
f"missed->wild={v7_missed}")
print(f" P(D|Beep)={v7_beep_disease/total:.4f} Theory: 0.1306")
print("-" * 62)
# ---- VISIT 8: two machines in series, both must beep ----
v8_both_sick=0; v8_both_healthy=0; v8_missed=0
for _ in range(n):
sick = random.random() < 0.10
if sick:
m1 = random.random() < 0.80
m2 = random.random() < 0.70
if m1 and m2: v8_both_sick += 1
else: v8_missed += 1 # fewer than 2 beeps -> wild
else:
m1 = random.random() < 0.20
m2 = random.random() < 0.10
if m1 and m2: v8_both_healthy += 1
total = v8_both_sick + v8_both_healthy
print(f"Visit 8 (two machines): double_beeps={total}, "
f"sick={v8_both_sick}, healthy={v8_both_healthy}, "
f"sick missed->wild={v8_missed}")
print(f" P(H|M1,M2)={v8_both_sick/total:.4f} Theory: 0.7568")
print("-" * 62)
# ---- VISIT 9: red-coat scribble -- generated but ignored ----
v9_true=0; v9_glitch=0; v9_missed=0
for _ in range(n):
sick = random.random() < 0.30
_red_coat = random.random() < 0.50 # shopkeeper's scribble -- discarded
if sick:
if random.random() < 0.80: v9_true += 1
else: v9_missed += 1
else:
if random.random() < 0.20: v9_glitch += 1
total = v9_true + v9_glitch
print(f"Visit 9 (red-coat): beeps={total}, "
f"true={v9_true}, glitch={v9_glitch}, missed->wild={v9_missed}")
print(f" P(D|Beep)={v9_true/total:.4f} Theory: 0.6316")
print(f" (_red_coat was generated but never used -- irrelevant to the answer)")
print("=" * 62)
run_simulation()
Sample output -- 100,000 patients, the simulation landing within a hair of each
hand head-count:
Visit 4 (1% disease): P(D|Beep) = 0.0829 Theory: 0.0833
Visit 5 (30% disease): P(Sick|Beep) = 0.5621 Theory: 0.5625
Visit 6 (2% disease): P(D|Beep) = 0.2345 Theory: 0.2462
... (visits 7, 8, 9 likewise within ~0.01 of theory: 0.1306, 0.7568, 0.6316)
FORMULA (IT WAS HERE ALL ALONG)
You have now used Bayes' rule ten times without once reading it as a formula. Here it is,
for the exam or the quiz:
P(Sick | Beep) = P(Beep | Sick) x P(Sick)
-----------------------------------------
P(Beep|Sick)xP(Sick) + P(Beep|Healthy)xP(Healthy)
Every symbol is a pile of patients you already counted:
- P(Beep | Sick): of the sick-only back-room, what share came back with a beep?
- P(Sick): of all patients sent to the shopkeeper, what share was truly sick?
- The denominator: the shopkeeper's hidden total, glued from the two piles.
- P(Sick | Beep): of all the beep papers, what share went to sick patients?
The formula is not magic. It is the patient head-count, compressed into one line.
DECODER (JARGON -> PLAIN)
PEACOCK WORD PLAIN WORD (hospital story)
-------------------------------- -----------------------------------------------
prior probability P(H) the sick share of patients sent to the shopkeeper,
before the machine sees them
likelihood P(Beep | H) of the sick-only back-room, what share the
machine caught with a beep
marginal probability P(Beep) total beep papers -- the shopkeeper's hidden count;
we build it by gluing the two piles
posterior probability P(H|Beep) of the beep-paper patients, what share is truly
sick -- the doctor's question answered
false positive / false alarm healthy patient who got a beep paper (a glitch)
false negative / miss sick patient who got a clean paper and walked home
into the wild undiagnosed
sensitivity / recall / TPR of all sick patients sent to the shopkeeper, what
share came back with a beep (catch rate)
specificity / TNR of all healthy patients, what share the machine
correctly stayed silent on
precision / PPV of all beep papers, what share went to sick patients;
P(Sick | Beep); the doctor's number
Law of Total Probability the gluing move: add beep-pile counts from every
non-overlapping sub-group to find P(Beep)
Bayes' rule / Bayes' theorem divide the sick-beep pile by the total beep pile
---
One Breath: a patient walks into a hospital, the doctor suspects disease and sends them to
the greedy shopkeeper down the corridor, the shopkeeper's faulty machine prints BEEP or
stays silent, the patient returns to the doctor -- but the shopkeeper hides how many beep
papers he printed in total, so the doctor must glue together the only two ways a beep is
born (sick patient caught, healthy patient glitched) to get the denominator, then divide
the sick-beep pile by that total; the cruel result is that when a disease is rare the
glitch pile on the huge healthy crowd always drowns the tiny sick pile, and every patient
the machine stayed silent on has already walked home into the wild.
----------------------------------------------------------------------------------------------
home . archive . source on GitHub
==============================================================================================