---
jupyter:
  jupytext:
    notebook_metadata_filter: language_info
    text_representation:
      extension: .Rmd
      format_name: rmarkdown
      format_version: '1.2'
      jupytext_version: 1.14.1
  kernelspec:
    display_name: Python 3 (ipykernel)
    language: python
    name: python3
  language_info:
    codemirror_mode:
      name: ipython
      version: 3
    file_extension: .py
    mimetype: text/x-python
    name: python
    nbconvert_exporter: python
    pygments_lexer: ipython3
    version: 3.10.9
---

# Random choice

```{python}
# Import the array library
import numpy as np
# Make random number generator.
rng = np.random.default_rng()
```

We are building up to an answer to the [three girls
problem](../code-basics/three_girls).  At the end of the [lists
page](../data-types/lists), we found a way to simulate one family, and get a
count.

Up until now, we have simulated each child in the family by randomly choosing
an integer from 0 up to, but not including 2.  Therefore, we are randomly
choosing between 0 and 1, like this.

```{python}
one_child = rng.integers(0, 2)
one_child
```

Now we know about function arguments, lists and arrays, we can use a nicer
alternative to do the same thing — `rng.choice`.

The `choice` method of the Numpy random generator object asks Numpy to choose
from the choices you sent it, at random.

For example, say we want the `rng` to choose randomly between 0 and 1, we could
do this:

```{python}
# Choose randomly between 0 and 1.
alternatives = [0, 1]
rng.choice(alternatives)
```

## `p` for choices with probabilities


By default, the `choice` method chooses at random from the choice you give,
return each choice with the same probability.   In our case, it is returning 0
with probability 0.5 and 1 with probability 0.5.

However, you can use the `p` keyword argument to tell the function the probabilities to use for each choice.

For example, let us return to the [supreme court jury
problem](../code-basics/sampling_problem).  Remember, we wanted to simulate
a jury of 12 people, where each juror has a 26% chance of being Black.

We use 1 to mean Black, and 0 to mean White.

```{python}
# 1 means Black, 0 means White.
colors = [1, 0]
```

Now we want to select 1 (Black) with probability 0.26 (26%) and 0 (White) with
probability 1 - 0.26 = 0.74.  The `p` keyword argument to `choice` allows us to
do that:


```{python}
# Select Black with probability 0.26, White with probability 0.74.
rng.choice(colors, p=[0.26, 0.74])
```

Run the cell above a few times to persuade yourself that you see 1 about
quarter of the time, and 0 otherwise.


## Multiple choices with `size`

The `choice` method has another useful keyword argument, `size`.  We can use
this to ask `choice` to make multiple choices at the same time.  `choice`
returns the choices as an array.  For example, say we wanted to choose 12
jurors, each with a probability of 0.26 of being 1 (Black).  We could do this:

```{python}
# Select 12 jurors in one go.
rng.choice(colors, p=[0.26, 0.74], size=12)
```

Now consider the problem of choosing 4 children, each with a probability of 0.5
of being a Girl.  Let's do the same as we did before, and use 0 for a boy, and
1 for girl.

```{python}
sexes = [0, 1]
children = rng.choice(sexes, p=[0.5, 0.5], size=4)
children
```

Notice that the default is for `choice` to give equal probability the given
choices, so, in this case, we can simply miss out the `p=[0.5, 0.5]` argument
and get the same outcome:

```{python}
# choice selects each option with the same probability unless told otherwise.
children = rng.choice(sexes, size=4)
children
```


## `replace` to choose with and without replacement

When you take choices from options, you do this in one of two ways:

* *With* replacement.
* *Without* replacement.

So far you have seen the *with* replacement option.  This is the default for `rng.choice`.

For example, let's say we want to build a lottery to play with your fellow
students.  We want the kind of lottery where you have 10 balls, numbered 0
through 9.  You put the balls into a bucket, close your eyes, shake the bucket,
then draw one ball, and call out the number.

*Then you put that ball back* and repeat the procedure two more times.

Each student writes down three numbers from 0 through 9, such as '382' or '919'.

The winner is the student who got the exact three numbers that you drew from
the bucket.

We can simulate the lottery process like this:

```{python}
balls = np.arange(10)
balls
```

```{python}
drawn = rng.choice(balls, size=3)
drawn
```

**For reflection** - if there are 100 students playing, what is the chance that
at least one of the 25 will get the exact number?.

That is one way to do the lottery process, where we *put the ball back* before
drawing the next ball.  In that case, for each digit, you have the same chance
of getting each of the numbers 0 through 9.  The is *sampling with
replacement*, when we draw the ball and put it back, before doing the next
draw.

Another option is *sampling without replacement*.  This is where we don't put
the drawn ball back, but put it aside.

If we do that, that changes the probabilities of the following numbers.
Specifically, let us say we draw a 9 first, and put it aside, that means we
*cannot* get a 9 for the second and third draws.  For that case, it doesn't
make sense to guess '929' as the three numbers, because this cannot happen. And
in general, the digits cannot repeat in this case.

We can simulate that situation using the `replace=False` option to the
`rng.choice` function, like this:

```{python}
drawn_without_replacement = rng.choice(balls, size=3, replace=False)
drawn_without_replacement
```

If you run this cell a few times, you will see that you never get repeated
digits.

Notice that `replace=True` is the default, so these two cells both do sampling
*with replacement*, selecting 10 balls at random from the 10 available.

```{python}
# With replacement is the default.
rng.choice(balls, size=10)
```

```{python}
# You can also specify replace=True explicitly, for clarity.
rng.choice(balls, size=10, replace=True)
```

**For reflection** - if there are 100 students playing, what is the chance that
at least one of the 25 will get the exact number, if we are sampling without
replacement?.

One obvious place where we use sampling without replacement, is dealing a
deck of cards.  Once we have dealt a particular card, such as the ace of
spades, that card cannot be dealt again — it is no longer in the pack from
which we are dealing.

Sampling without replacement means that we can exhaust our choices.  For
example, if we try to draw 11 balls from the 10 available, we will get an
error:

```{python tags=c("raises-exception")}
# Exhausting our options.
rng.choice(balls, size=11, replace=False)
```

If we are drawing *with replacement* we can draw as many balls as we like:

```{python tags=c("raises-exception")}
# We can't exhaust our options.
rng.choice(balls, size=100, replace=True)
```
