==============================================================================================
  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
==============================================================================================