⚙️ Setup¶
import numpy as np
rng = np.random.default_rng(331)
print("ready")
ready
py, py_and_stat, stat, total = 120, 90, 130, 200
p_stat_given_py = py_and_stat / py # restrict to the 120 Python students
print(f"P(statistics | Python) = 90/120 = {p_stat_given_py:.3f}")
print(f"compare P(statistics) = 130/200 = {stat/total:.3f} -> Python students are more likely to take stats (dependent)")
P(statistics | Python) = 90/120 = 0.750 compare P(statistics) = 130/200 = 0.650 -> Python students are more likely to take stats (dependent)
Answer: condition on Python, so the denominator is the 120 Python students, not all 200: P(statistics | Python) = 90/120 = 0.75. That is higher than the overall P(statistics) = 0.65, so the two subjects are not independent, studying Python is associated with studying statistics.
p = (5/8) * (4/7)
print(f"P(both red) = (5/8) x (4/7) = {p:.4f}")
bag = np.array([1]*5 + [0]*3)
N=300_000; hits=sum(rng.choice(bag,2,replace=False).sum()==2 for _ in range(N))
print(f"empirical (300k) = {hits/N:.4f}")
P(both red) = (5/8) x (4/7) = 0.3571
empirical (300k) = 0.3566
Answer: the draws are dependent, so use the multiplication rule with a conditional second term: P(both red) = P(1st red) x P(2nd red | 1st red) = (5/8) x (4/7) = 20/56 ≈ 0.357. Removing the first red marble changes the bag from 5/8 red to 4/7 red.
pA = 4/52 # four kings
pA_given_B = 1/13 # of the 13 hearts, one is a king
print(f"P(King) = 4/52 = {pA:.4f}")
print(f"P(King | Heart) = 1/13 = {pA_given_B:.4f}")
print("equal -> A and B are INDEPENDENT" if np.isclose(pA,pA_given_B) else "not independent")
P(King) = 4/52 = 0.0769 P(King | Heart) = 1/13 = 0.0769 equal -> A and B are INDEPENDENT
Answer: P(King) = 4/52 = 1/13, and P(King | Heart) = 1/13 (one king among the 13 hearts). They are equal, so being a King is independent of being a Heart: knowing the suit tells you nothing about whether it is a king. (Suit and rank are independent in a standard deck.)
N=10_000; prev=0.02; sens=0.95; spec=0.90
sick=N*prev; healthy=N*(1-prev)
TP=sick*sens; FP=healthy*(1-spec)
ppv=TP/(TP+FP)
print(f"sick={sick:.0f}, healthy={healthy:.0f}")
print(f"true positives = {TP:.0f}, false positives = {FP:.0f}")
print(f"P(disease | positive) = {TP:.0f}/({TP:.0f}+{FP:.0f}) = {ppv:.3f} (about {ppv*100:.0f}%)")
sick=200, healthy=9800 true positives = 190, false positives = 980 P(disease | positive) = 190/(190+980) = 0.162 (about 16%)
Answer: 200 are sick and 9,800 healthy. True positives = 200 x 0.95 = 190; false positives = 9,800 x 0.10 = 980. So P(disease | positive) = 190 / (190 + 980) ≈ 0.16, only about 16%, because the 10% false-positive rate applied to the large healthy group swamps the true positives. Specificity matters enormously when the disease is rare.
neg_refund=120; pos_refund=30
p_neg_given_refund = neg_refund / (neg_refund + pos_refund)
print(f"reviews containing \"refund\": {neg_refund+pos_refund}")
print(f"P(negative | refund) = 120/150 = {p_neg_given_refund:.2f}")
print(f"compare P(negative) = 200/500 = {200/500:.2f} -> the word \"refund\" is a strong negative signal")
reviews containing "refund": 150 P(negative | refund) = 120/150 = 0.80 compare P(negative) = 200/500 = 0.40 -> the word "refund" is a strong negative signal
Answer: of the 150 reviews containing "refund", 120 are negative, so P(negative | "refund") = 120/150 = 0.80, far above the base rate P(negative) = 0.40. A sentiment classifier is built from exactly these conditional probabilities: each feature shifts the estimated P(class | features) up or down.