Index Ai Academy
Phase 2 · Math, when you need it · Probability & statistics

Conditional probability and Bayes

Update beliefs when new evidence arrives.

About 30 minutes · A free account opens the whole lesson

Create a free account Try part of it here

Already have an account? Sign in to open this lesson

Why it matters

Many useful questions come with a "given". What's the chance this visitor buys, given they viewed the pricing page? That is a conditional probability. So is everything a language model outputs: the chance of each next token, given all the tokens before it. Bayes' rule lets you flip a "given" around. It's behind spam filters, lead scoring and how you should read any "95% accurate" claim. It's also the place where smart people's gut feeling most often goes wrong. This lesson gives you the rule and a simple habit that catches the mistake: count it out.

What you'll be able to do

  • Compute a conditional probability by restricting to the cases where the condition holds
  • Read conditional probabilities from a table of counts
  • Apply Bayes' rule, and avoid the base-rate mistake
  • Update a belief step by step as evidence arrives

What it covers

  1. Probability given something
  2. Reading a table of counts
  3. Language models are conditional probabilities
  4. Bayes' rule: flipping the condition
  5. Updating step by step
  6. Worked example: one word in a spam filter

Probability given something

Picture 10 customers. 4 of them opened your email, and 2 of those 4 went on to buy. Pick one of the openers at random. What's the chance they bought?

You ignore the 6 who didn't open. You look only at the 4 openers, and 2 of them bought. So the chance is 2 out of 4, or 50%.

That's a conditional probability: a chance worked out inside a smaller group. The condition is what picks the group. Here the condition is "opened the email".

As in the last lesson, P(…)P(\ldots) means "the chance of". A and B stand for any two events, like "bought" and "opened".

In math you write a conditional probability with a vertical bar: P(A∣B)P(A \mid B). Read the bar as "given". So P(buy∣opened)P(\text{buy} \mid \text{opened}) reads "the chance of buy, given opened". The event after the bar picks the group. The event before the bar is what you count inside that group.

In plain words: P(A∣B)P(A \mid B) is the chance of A when you only look at the cases where B happened.

Here is the same idea as a formula:

P(A∣B)=P(A and B)P(B)P(A \mid B) = \frac{P(A \text{ and } B)}{P(B)}

In plain words: out of all the cases where B happened, what share also had A? The top of the fraction is the overlap. The bottom is the group you restrict to.

Check it with the 10 customers. 2 of the 10 opened and bought, so P(buy and opened)P(\text{buy and opened}) is 2/10. 4 of the 10 opened, so P(opened)P(\text{opened}) is 4/10. Divide: 2/10 ÷ 4/10 = 2/4 = 0.5. That's the same 50% you got by counting.

Quick check
Of 10 customers, 4 opened the email and 2 of those bought. What is the chance of buying, given they opened?
print(2 / 4)
  1. 2.0
  2. 0.5
  3. 0.4
  4. 0.2
Show the answer

0.5

Given means you only look at the 4 who opened. Then 2 of those 4 bought, and 2 / 4 is 0.5.

Reading a table of counts

Real data usually comes as a table of counts. Say 10,000 people received a campaign email. A person "converted" if they went on to buy.

blankconverteddidn't converttotal
opened2402,7603,000
didn't open706,9307,000
total3109,69010,000

Each question picks a row or a column, then divides:

  • P(convert)=310/10,000=3.1%P(\text{convert}) = 310 / 10{,}000 = 3.1\%: no condition, so use everyone.
  • P(convert∣opened)=240/3,000=8%P(\text{convert} \mid \text{opened}) = 240 / 3{,}000 = 8\%: restrict to the opened row.
  • P(opened∣convert)=240/310≈77%P(\text{opened} \mid \text{convert}) = 240 / 310 \approx 77\%: restrict to the converted column.

Those last two are different questions with very different answers. The first asks: of the people who opened, how many converted? The second asks: of the people who converted, how many had opened? Mixing them up is the most common probability error there is.

Here are the same counts in NumPy. counts[0] is the opened row. counts[:, 0] is the converted column.

import numpy as np

counts = np.array([[240, 2760],     # opened: converted, didn't
                   [70, 6930]])     # didn't open
total = counts.sum()
print("P(convert)          =", round(counts[:, 0].sum() / total, 4))
print("P(convert | opened) =", round(counts[0, 0] / counts[0].sum(), 4))
print("P(opened | convert) =", round(counts[0, 0] / counts[:, 0].sum(), 4))
It runs right here in your browser, with nothing to install.

Opening makes conversion more likely: 8%, against 3.1% overall. So the two events are not independent. Knowing that someone opened changes the chance that they converted.

Quick check
What does this print?
opened_and_clicked = 30
opened = 120
print(opened_and_clicked / opened)
  1. 0.12
  2. 4.0
  3. 0.3
  4. 0.25
Show the answer

0.25

Given that they opened it, only those 120 people count. Then 30 of them clicked, and 30 / 120 is 0.25.

Quick check
Using the same email table, what is P(convert | didn't open)?
  1. 70 / 10,000 = 0.7%
  2. 70 / 7,000 = 1%
  3. 310 / 7,000, about 4.4%
  4. 70 / 310, about 23%
Show the answer

70 / 7,000 = 1%

The condition is "didn't open", so restrict to that row: 7,000 people, and 70 of them converted.

Language models are conditional probabilities

A language model reads some text and gives a chance for every possible next token. (A token is a word or a piece of a word.) Each of those chances depends on the text so far. After "Happy", the token "birthday" is likely. After "The", it isn't.

So the model estimates P(next token∣all previous tokens)P(\text{next token} \mid \text{all previous tokens}).

To get the chance of a whole sentence, you chain these together one step at a time. Call the first word w1w_1 (w for word). The second word is w2w_2, and the third is w3w_3.

P(w1,w2,w3)=P(w1) P(w2∣w1) P(w3∣w1,w2)P(w_1, w_2, w_3) = P(w_1)\, P(w_2 \mid w_1)\, P(w_3 \mid w_1, w_2)

In plain words: the chance of the whole sentence is the chance of the first word, times the chance of the second word given the first, times the chance of the third word given the first two. The small gaps between the pieces mean "times".

A worked example, with made-up numbers to keep the sums easy. Say a model gives "the" a chance of 0.05 to start a sentence. Given "the", it gives "cat" a chance of 0.01. Given "the cat", it gives "sat" a chance of 0.2. Then "the cat sat" has a chance of 0.05 × 0.01 × 0.2 = 0.0001.

p_first = 0.05    # P("the")
p_second = 0.01   # P("cat" | "the")
p_third = 0.2     # P("sat" | "the cat")
print(round(p_first * p_second * p_third, 6))

It prints 0.0001. A long text multiplies hundreds of small numbers like these, and the result gets tiny fast. That's why the log rules matter: logs turn multiplying into adding. In practice these products become sums of log-probabilities.

Go deeper Why the chain works

It's the formula from the top of the lesson, rearranged. Multiply both sides of P(A∣B)=P(A and B)/P(B)P(A \mid B) = P(A \text{ and } B) / P(B) by P(B)P(B):

P(A and B)=P(B) P(A∣B)P(A \text{ and } B) = P(B)\, P(A \mid B)

Read it as: the chance of both is the chance of B, times the chance of A once B has happened. Let B be "the first word is w1w_1". Let A be "the second word is w2w_2". That gives the first two pieces of the chain. Use the same step again to add the third word.

When A and B are independent, P(A∣B)P(A \mid B) is the same as P(A)P(A). Then this becomes the plain "and" rule from the last lesson.

Quick check
A model gives 'good' a chance of 0.1 to start a message, and P('morning' | 'good') = 0.5. What's the chance a message starts 'good morning'?
  1. 0.5
  2. 0.1
  3. 0.6
  4. 0.05
Show the answer

0.05

Chain the steps: 0.1 × 0.5 = 0.05. Half of the messages that start with 'good' go on with 'morning'.

Bayes' rule: flipping the condition

Often you know a chance the other way around from the one you want. The maker of a fraud filter tells you how often it flags fraud: P(flag∣fraud)P(\text{flag} \mid \text{fraud}). But you have a flagged order in front of you. You want P(fraud∣flag)P(\text{fraud} \mid \text{flag}). Bayes' rule flips one into the other:

P(A∣B)=P(B∣A) P(A)P(B)P(B)=P(B∣A)P(A)+P(B∣not A)P(not A)P(A \mid B) = \frac{P(B \mid A)\, P(A)}{P(B)} \qquad P(B) = P(B \mid A)P(A) + P(B \mid \text{not } A)P(\text{not } A)

That's two formulas side by side. The left one is Bayes' rule. The right one works out P(B)P(B), the bottom of the fraction.

In plain words: to get the chance of A given B, take the chance of B when A is true. Multiply by how common A was to start with. Then divide by how common B is overall. B can happen in two ways: with A, or without A. The right-hand formula adds those two ways together.

Three words name the parts:

  • P(A)P(A) is the prior: what you believed before the evidence.
  • P(B∣A)P(B \mid A) is the likelihood: how likely the evidence is if A is true.
  • P(A∣B)P(A \mid B) is the posterior: what you believe after the evidence.

"Prior" means before, and "posterior" means after.

Example. A fraud filter flags 95% of fraudulent orders. It also wrongly flags 5% of legitimate ones. 1% of all orders are fraudulent. An order is flagged. What's the chance it's fraud?

Many people guess about 95%. Count it out with 10,000 orders instead:

  • 1% are fraud, so 100 orders. The filter flags 95 of them.
  • The other 9,900 are legit. 5% of those get flagged too: 495 orders.
  • So 95 + 495 = 590 orders get flagged, and only 95 of them are fraud.

95 out of 590 is about 16%. In Bayes' terms, A is "fraud" and B is "flagged". The prior is 1%, and the likelihood is 95%. Bayes' rule gets the same answer without the counting:

prior = 0.01            # P(fraud)
hit = 0.95              # P(flag | fraud)
false_alarm = 0.05      # P(flag | legit)

p_flag = hit * prior + false_alarm * (1 - prior)
posterior = hit * prior / p_flag
print(f"P(flag) = {p_flag:.4f}")
print(f"P(fraud | flag) = {posterior:.3f}")

p_flag is the right-hand formula: flags from fraud plus flags from legit orders. It prints 0.0590, which is 590 flags out of 10,000 orders. The posterior is 0.161, only about 16%.

Most flags are false alarms because fraud is rare. Ignoring the prior like this is called the base-rate fallacy. The base rate is how common something is to start with. It's why a "95% accurate" detector can still be wrong most of the time it fires.

For marketers: the same trap applies to lead scoring. Say 2% of leads buy and your model is "90% accurate". Ask what share of the leads it flags actually buy. That's the posterior, also called precision, and it's the number sales cares about.

Go deeper Where Bayes' rule comes from

You can build the chance of "A and B" in two ways. Start from B, or start from A:

P(A and B)=P(B) P(A∣B)=P(A) P(B∣A)P(A \text{ and } B) = P(B)\, P(A \mid B) = P(A)\, P(B \mid A)

Read it as: "B, then A given B" covers the same overlap as "A, then B given A". Divide both sides by P(B)P(B) and you get Bayes' rule.

The bottom of the fraction, P(B)P(B), splits the B cases into two piles: those where A is true and those where it isn't. In the fraud example, that's 95 flags from fraud plus 495 from legit orders.

Try it now

Rare things mostly produce false alarms, even with a good test. Raise how common it is and watch the answer jump.

Quick check
Among people who bought, 70% visited the pricing page. Does that mean 70% of pricing-page visitors buy?
  1. Yes, if the page is popular
  2. No: that's P(visited | bought); P(bought | visited) can be far lower
  3. No, it's always exactly 30%
  4. Yes, they're the same
Show the answer

No: that's P(visited | bought); P(bought | visited) can be far lower

Most visitors may not buy at all. You need the base rates to flip the condition.

Updating step by step

Evidence often arrives in pieces. Think of a detective who updates a hunch each time a new clue turns up. Bayes' rule works the same way. The posterior after one piece of evidence becomes the prior for the next piece.

Say the same order trips two separate checks: the address doesn't match, and the order comes from a new device. Each check catches 95% of fraud and wrongly fires on 5% of legit orders. Assume the two checks work independently of each other. (That's a simplification. Real signals often overlap.)

def update(prior, hit, false_alarm):
    return hit * prior / (hit * prior + false_alarm * (1 - prior))

belief = 0.01
for check in ["address mismatch", "new device"]:
    belief = update(belief, 0.95, 0.05)
    print(f"after {check}: {belief:.3f}")

The first check moves the belief from 1% to 0.161. The second check starts from 0.161 and moves it to 0.785. Two independent warnings push the chance of fraud from 1% to about 78%.

This is the idea behind naive Bayes spam filters. Each word in an email updates the chance that it's spam. "Naive" refers to the assumption that the words are independent.

Quick check
A check fires on 40% of fraud orders and on 40% of legit orders. You think there's a 30% chance an order is fraud. The check fires on it. What should your belief be now?
  1. 70%
  2. Still 30%
  3. 12%
  4. 40%
Show the answer

Still 30%

The signal is equally likely either way, so it can't tell fraud from legit. 0.4 × 0.3 / (0.4 × 0.3 + 0.4 × 0.7) = 0.3.

Worked example

Worked example: one word in a spam filter

10% of the emails you get are spam. The word "free" shows up in 40% of spam emails, but in only 2% of normal ones. An email contains "free". What's the chance it's spam? Count it out with 1,000 emails.

emails = 1000
spam = emails * 0.10           # 100 spam emails
normal = emails - spam         # 900 normal emails

free_in_spam = spam * 0.40     # 40 spam emails contain "free"
free_in_normal = normal * 0.02 # 18 normal emails contain "free"

share = free_in_spam / (free_in_spam + free_in_normal)
print("emails with 'free':", round(free_in_spam + free_in_normal))
print("P(spam | 'free') =", round(share, 2))
In the lesson you can change this and run it.

Out of 1,000 emails, 100 are spam and 40 of those say "free". The other 900 are normal, and 18 of those say "free". So 58 emails say "free", and 40 of them are spam: 40 / 58 is about 0.69. Bayes' rule gives the same: 0.4 × 0.1 / (0.4 × 0.1 + 0.02 × 0.9) = 0.04 / 0.058, about 0.69. One word moves the belief from 10% to 69%. A real filter keeps updating, one word at a time.

Practice what you just read

  • 5 code examples you can edit and run
  • a practice exercise that checks your code
  • a 5-question quiz
  • 5 review cards for spaced repetition

Create a free account

Try a quiz question

A model flags 'likely to churn' and is right 90% of the time on churners, with a 10% false-alarm rate. Only 5% of customers churn. Roughly what share of flagged customers actually churn?

  1. About 32%
  2. About 90%
  3. About 5%
  4. About 50%

Answer it at the end of the lesson to see if you're right, and why.

Before this lesson