# Conditional probability in Python

Comparisons, predicates, the Monty Hall problem, and updating on evidence, in the CS61A style.

### What would Python display? Comparing a list

*Single choice · en*

What would Python display?

```python
>>> child1 = [1, 2, 1]
>>> child1 == 1
```

- [ ] elementwise: `[True, False, True]`
- [ ] false: `False`
- [ ] error: A `TypeError`

Your answer: false

#### Hints

1. Python's `==` compares the whole left value with the whole right value.

2. Is a list ever equal to a number?

#### Reference solution

`False`. Python compares the list `[1, 2, 1]` as a whole with the number `1`, and they are not equal. Nothing is compared entry by entry, unlike R, where `child1 == 1` gives `TRUE FALSE TRUE`. Write `[c == 1 for c in child1]` to get `[True, False, True]`.

Source: [Adapted from Blitzstein & Hwang, Introduction to Probability, Chapter 2 and §2.10 (R), rewritten in Python](http://stat110.net) · Original exercise; the book's R examples are paraphrased, not copied

### What would Python display? Counting with and

*Fill in the blank · en*

What would Python display?

```python
>>> pairs = [(1, 1), (1, 2), (2, 1), (1, 1)]
>>> sum([c1 == 1 and c2 == 1 for c1, c2 in pairs])
```

Your answer: 2 (Accepted absolute error: 0)

#### Hints

1. The comprehension is `[True, False, False, True]`.

2. `sum` counts each `True` as 1.

#### Reference solution

`2`. Only the first and last pairs have both children equal to 1, so the list is `[True, False, False, True]`, and its sum is 2. This is $n_{AB}$ for $A$ = both girls.

Source: [Adapted from Blitzstein & Hwang, Introduction to Probability, Chapter 2 and §2.10 (R), rewritten in Python](http://stat110.net) · Original exercise; the book's R examples are paraphrased, not copied

### Estimate a conditional probability

*Python · en*

Complete `conditional_frequency(outcomes, a, b)`. `a` and `b` are predicates, functions that take one outcome and return `True` or `False`. Return $n_{AB}/n_B$: the number of outcomes where both hold, divided by the number where `b` holds. Return `None` if `b` never holds.

```python
def conditional_frequency(outcomes, a, b):
    """Estimate P(A | B) as n_AB / n_B over the list OUTCOMES.

    A and B are predicates: functions from an outcome to True or False.
    Return None when B never occurs.

    >>> families = [(1, 1), (1, 2), (2, 1), (2, 2)]
    >>> conditional_frequency(families, lambda f: f == (1, 1), lambda f: f[0] == 1)
    0.5
    """
    pass
```

#### Public tests

Elder child is a girl

```pycon
>>> families = [(1, 1), (1, 2), (2, 1), (2, 2)]
>>> both_girls = lambda f: f == (1, 1)
>>> conditional_frequency(families, both_girls, lambda f: f[0] == 1)
0.5
```

At least one girl

```pycon
>>> families = [(1, 1), (1, 2), (2, 1), (2, 2)]
>>> both_girls = lambda f: f == (1, 1)
>>> round(conditional_frequency(families, both_girls, lambda f: 1 in f), 3)
0.333
```

B never happens

```pycon
>>> families = [(1, 1), (1, 2), (2, 1), (2, 2)]
>>> both_girls = lambda f: f == (1, 1)
>>> conditional_frequency(families, both_girls, lambda f: False) is None
True
```

Other outcomes

```pycon
>>> rolls = [1, 2, 3, 4, 5, 6]
>>> conditional_frequency(rolls, lambda r: r == 6, lambda r: r % 2 == 0)
0.3333333333333333
```

#### Hints

1. Count $n_B$ with `sum(1 for o in outcomes if b(o))`.

2. Count $n_{AB}$ the same way, requiring `a(o) and b(o)`.

3. Check for $n_B = 0$ before dividing.

#### Reference solution

```python
def conditional_frequency(outcomes, a, b):
    n_b = sum(1 for o in outcomes if b(o))
    n_ab = sum(1 for o in outcomes if a(o) and b(o))
    return None if n_b == 0 else n_ab / n_b
```

The function knows nothing about children or dice. It only calls the predicates it is given, which is what lets one definition answer every question in the note.

Source: [Adapted from Blitzstein & Hwang, Introduction to Probability, Chapter 2 and §2.10 (R), rewritten in Python](http://stat110.net) · Original exercise; the book's R examples are paraphrased, not copied

### Which door does Monty open?

*Python · en*

Complete `monty_opens(chosen, car)` for doors 1, 2, and 3. Monty never opens the player's door or the car's door. When two doors are allowed (the player picked the car), return the lower-numbered one, so the result is always predictable.

```python
def monty_opens(chosen, car):
    """Return the door Monty opens among doors 1, 2, and 3.

    It is neither CHOSEN nor CAR. When two doors are allowed,
    return the lower-numbered one.

    >>> monty_opens(1, 2)
    3
    """
    pass
```

#### Public tests

Only one door allowed

```pycon
>>> monty_opens(1, 2)
3
>>> monty_opens(2, 1)
3
>>> monty_opens(3, 1)
2
```

Player picked the car

```pycon
>>> monty_opens(1, 1)
2
>>> monty_opens(2, 2)
1
>>> monty_opens(3, 3)
1
```

#### Hints

1. Consider each door in `(1, 2, 3)` and keep the ones that are neither `chosen` nor `car`.

2. `min` of the allowed doors gives the lower-numbered one.

#### Reference solution

```python
def monty_opens(chosen, car):
    return min(door for door in (1, 2, 3) if door != chosen and door != car)
```

When `chosen != car`, exactly one door is allowed; when they are equal, two are, and `min` picks the lower. In the real game Monty chooses at random in that case, which the simulation in the note adds back.

Source: [Adapted from Blitzstein & Hwang, Introduction to Probability, Chapter 2 and §2.10 (R), rewritten in Python](http://stat110.net) · Original exercise; the book's R examples are paraphrased, not copied

### The chance of winning by switching

*Fill in the blank · en*

You pick door 1, Monty opens a door with a goat behind it, and you always switch. What is the probability that you win the car? Answer as a fraction.

Your answer: 2/3 (Accepted absolute error: 0)

#### Hints

1. Switching wins exactly when your first pick was wrong.

2. Your first pick is wrong with probability $2/3$.

#### Reference solution

$2/3$. If your first pick is the car (probability $1/3$), switching loses. If your first pick is a goat (probability $2/3$), Monty must open the other goat door, so the door you switch to has the car. The simulation in the note gives $0.6690$.

Source: [Adapted from Blitzstein & Hwang, Introduction to Probability, Chapter 2 and §2.10 (R), rewritten in Python](http://stat110.net) · Original exercise; the book's R examples are paraphrased, not copied

### Update a probability on evidence

*Python · en*

Complete `bayes_update(prior, p_evidence_if_a, p_evidence_if_not_a)`. Given $P(A)$, $P(E\mid A)$, and $P(E\mid A^c)$, return $P(A\mid E)$ using Bayes' rule with the law of total probability. The tests pass `Fraction` values so the answers are exact. Assume $P(E)>0$.

```python
from fractions import Fraction


def bayes_update(prior, p_evidence_if_a, p_evidence_if_not_a):
    """Return P(A | E) from the prior P(A), P(E | A), and P(E | not A).

    >>> bayes_update(Fraction(1, 4), 1, Fraction(2, 3))
    Fraction(1, 3)
    """
    pass
```

#### Public tests

Monday: at least one girl

```pycon
>>> bayes_update(Fraction(1, 4), 1, Fraction(2, 3))
Fraction(1, 3)
```

Tuesday: the elder is a girl

```pycon
>>> bayes_update(Fraction(1, 3), 1, Fraction(1, 2))
Fraction(1, 2)
```

Evidence that rules out not-A

```pycon
>>> bayes_update(Fraction(1, 100), 1, 0)
Fraction(1, 1)
```

Evidence that says nothing

```pycon
>>> bayes_update(Fraction(1, 5), Fraction(1, 2), Fraction(1, 2))
Fraction(1, 5)
```

#### Hints

1. The numerator is $P(E\mid A)\,P(A)$.

2. The denominator adds $P(E\mid A^c)\,(1-P(A))$ to the numerator.

#### Reference solution

```python
def bayes_update(prior, p_evidence_if_a, p_evidence_if_not_a):
    numerator = p_evidence_if_a * prior
    return numerator / (numerator + p_evidence_if_not_a * (1 - prior))
```

Because the result is again a probability for $A$, it can be passed straight back in as the next prior: that is sequential updating. Evidence equally likely under $A$ and not $A$ leaves the prior unchanged.

Source: [Adapted from Blitzstein & Hwang, Introduction to Probability, Chapter 2 and §2.10 (R), rewritten in Python](http://stat110.net) · Original exercise; the book's R examples are paraphrased, not copied
