Skip to main content

The random Library and Monte Carlo

Module 4: IDEs & Tools

The random library

random is part of the standard library: it comes with Python, so you only need to import it. It generates (pseudo-)random numbers and makes random choices.

import random

random.seed(42)  # fix the seed to get the same numbers every time

print(random.random())            # a float in [0, 1)
print(random.randint(1, 6))       # an integer from 1 to 6, both included
print(random.uniform(10, 20))     # a float between 10 and 20
print(random.choice(["red", "green", "blue"]))   # one element of a list
0.6394267984578837
1
17.41550499759833
red

More functions:

cards = ["A", "K", "Q", "J", "10"]

random.shuffle(cards)             # shuffles the list in place
print(cards)

print(random.sample(cards, 2))    # 2 different elements, without repetition
print(random.choices(cards, k=3)) # 3 elements, with repetition
['J', 'A', 'Q', '10', 'K']
['K', 'J']
['Q', 'J', 'J']
NoteSeeds

Computers do not generate truly random numbers: they follow a formula that starts from a seed. If you set the same seed, you get the same sequence. This is very useful to make experiments reproducible and to debug.

random and numpy.random are different generators. You have used np.random before, and random.seed does not affect it (and the other way around).

NoteRelated material

In Module 3 you generated random numbers and ran simulations with NumPy (Random Numbers and Fancy Indexing and Statistics and Simulation). Here we use the standard library instead: it needs no installation and works well for simple simulations with a few values at a time. NumPy is faster when you need millions of numbers at once.

Simulating random events

We can simulate an event by repeating it many times. For example, rolling two dice and adding the results:

random.seed(1)

def roll_two_dice():
    return random.randint(1, 6) + random.randint(1, 6)

rolls = [roll_two_dice() for _ in range(10)]
print(rolls)
[7, 4, 5, 8, 10, 3, 5, 8, 6, 10]

What is the probability of getting a 7? We can estimate it by counting how often it appears in many rolls:

random.seed(1)

n = 100_000
sevens = sum(1 for _ in range(n) if roll_two_dice() == 7)
print(sevens / n)
0.16621

The exact value is \(6/36 \approx 0.1667\). The more repetitions, the closer the estimate gets (the law of large numbers).

Monte Carlo experiments

The ornate facade of the Casino de Monte-Carlo in Monaco, with three tall arched windows and colourful tile decoration.

The method is named after the Monte Carlo casino in Monaco, a place famous for games of chance. Photo: Diego Delso, CC BY-SA 4.0, via Wikimedia Commons.

A Monte Carlo experiment uses random numbers to estimate a quantity that is difficult to compute exactly. The recipe is always the same:

  1. Define the random experiment.
  2. Repeat it a large number of times.
  3. Compute the result from the frequencies (or the average) of the outcomes.

Example: estimating \(\pi\)

Imagine a square with side 2 centred at the origin, and the circle of radius 1 inside it.

  • The area of the square is \(4\).
  • The area of the circle is \(\pi\).

If we throw random points into the square, the fraction that falls inside the circle is \(\pi / 4\). So:

\[\pi \approx 4 \cdot \frac{\text{points inside the circle}}{\text{total points}}\]

A point \((x, y)\) is inside the circle when \(x^2 + y^2 \le 1\).

A square from minus 1 to 1 on both axes with a circle of radius 1 drawn inside. About 1,500 random points are scattered: blue dots inside the circle and orange triangles in the corners outside it. The fraction inside the circle gives an estimate of pi of about 3.15.

1,500 random points in a square: those inside the circle are blue circles, those outside are orange triangles.
random.seed(1)

def estimate_pi(n):
    inside = 0
    for _ in range(n):
        x = random.uniform(-1, 1)
        y = random.uniform(-1, 1)
        if x**2 + y**2 <= 1:
            inside += 1
    return 4 * inside / n

for n in [100, 10_000, 1_000_000]:
    print(n, estimate_pi(n))
100 3.12
10000 3.1152
1000000 3.140816

The estimate improves as n grows.

Practice

NoteExercise: Guessing Game

If you have not done it yet, write the Guessing Game using random.randint.

import random

number = random.randint(1, 9)   # both ends are included
guesses = 0

while True:
    guess = input("Guess a number between 1 and 9, or type 'exit': ")
    if guess.lower() == "exit":
        break
    guess = int(guess)
    guesses += 1
    if guess < number:
        print("Too low!")
    elif guess > number:
        print("Too high!")
    else:
        print("Exactly right!")
        break

print(f"Game over. You took {guesses} guesses.")

Unlike np.random.randint, the function random.randint includes the upper limit. The same game written as a script with a main function is in Writing and Running Scripts.

NoteExercise: The birthday problem

In a group of 23 people, what is the probability that at least two of them have the same birthday? Estimate it with a Monte Carlo experiment. Assume 365 equally likely days.

Hint: in each experiment, generate 23 random birthdays with random.randint(1, 365) and check whether there are repeated values. A set helps.

random.seed(1)

def has_shared_birthday(people=23):
    birthdays = [random.randint(1, 365) for _ in range(people)]
    return len(set(birthdays)) < people

n = 100_000
shared = sum(1 for _ in range(n) if has_shared_birthday())
print(shared / n)
0.50708

The result is close to \(0.51\): it is more likely than not that two people share a birthday.

NoteExercise: Dice

Estimate with a simulation the probability that the sum of three dice is at least 15. Try with 1,000 and with 1,000,000 repetitions. What do you notice?

random.seed(3)

def three_dice_at_least_15():
    return sum(random.randint(1, 6) for _ in range(3)) >= 15

for n in [1_000, 1_000_000]:
    count = sum(1 for _ in range(n) if three_dice_at_least_15())
    print(n, count / n)
1000 0.092
1000000 0.092623

The exact probability is \(20/216 \approx 0.0926\): of the \(6^3 = 216\) equally likely outcomes, 20 have a sum of 15 or more (10 with sum 15, 6 with 16, 3 with 17 and 1 with 18).

With 1,000 repetitions the estimate can easily be off by one or two hundredths, and it changes a lot from one seed to another. With 1,000,000 repetitions it is very close to \(0.0926\). The more repetitions, the smaller the error: this is the law of large numbers.


Need Help?

If you have any questions, feel free to reach out via email at dprecioso@faculty.ie.edu

Next: Environments