Simulating Probabilities – The Monty Hall Problem

Psychology vs. Probability

Anyone old enough to remember the Monty Hall problem from the old TV Show Let’s Make a Deal? It’s a classic probability problem – but despite its simplicity, it can be hard to understand what choices to make to maximize your odds of winning.

This is the problem:

You are a contestant on a game show. The host displays three doors. One has the brand new car behind it while behind the other doors have goats behind them. Here’s a beautiful image of all possible options you would have:

monty hall problem

All potential starting positions

The situation starts out with Monty Hall knowing what is behind each door. The following happens:

  1. You choose a door
  2. Of the remaining two doors which you did not pick, Monty opens one with a goat (remember, he knows what is behind each door)
  3. You are then asked whether or not you would like to change your decision and pick another door or keep your initial pick

What should you do to maximize the chance of winning a car?

At first glance, most people think it doesn’t matter and that it’s a 50/50 shot. However, that’s dead wrong. The game has been changed right under your nose!

At first you had a 33.33…% chance of choosing the car and a 66.66…% chance of picking a goat. If you stick with your original choice, you keep that same chance. If you switch doors, you have a 66.66…% chance of winning! That’s well above 50%!

Let’s just look at one potential outcome to make this point clear. Assume that in the previous image, the very first option was how the car and goats were arranged (door 1 = car, door 2 = goat, door 3 = goat).

First, let’s look at the outcomes if you keep your original choice:

  • Scenario 1 = You originally select door 3 (Goat), Monty opens up door 2 (Goat), and you stick with door 3 (Goat). You LOSE
  • Scenario 2 = You originally select door 2 (Goat), Monty opens up door 3 (Goat), and you stick with door 2 (Goat). You LOSE
  • Scenario 3 = You originally select door 1 (Car), Monty opens up door 3 (Goat), and you stick with door 1 (Goat). You WIN
    • Note: Monty could have opened up door number 2 or 3 since both have goats (this doesn’t affect the outcome)
    • This is the ONLY scenario in which you win (1/3 chance of winning)

A beautiful visualization of these scenarios shown in order below:

Outcomes when you stick with your original choice!

What happens? If you stick with your original choice, two out of three times you will lose.

Here are the outcomes if you switch doors:

  • Scenario 1 = You originally select door 3 (Goat), Monty opens up door 2 (Goat), and you switch to door 1 (Car). You WIN
  • Scenario 2 = You originally select door 2 (Goat), Monty opens up door 3 (Goat), and you switch to door 1 (Car). You WIN
  • Scenario 3 = You originally select door 1 (Goat), Monty opens up door 3 (Goat), and you stick with door 2 (Goat). You LOSE
    • Note: Monty could have opened up door number 2 or 3 since both have goats (this doesn’t affect the outcome)
    • This is the ONLY scenario in which you lose (2/3 chance of winning)

Outcomes when you switch!

What happens? If you switch, two out of three times you will win!

While you can explicitly this problem using Bayes Theorem, it’s much easier to see these things through simulation (like the pictures above). You can also write simulations to help you solve these types of problems! Being able to simulate probabilities is an incredibly powerful tool in the data science world. There are many cases where it is either extremely difficult to find an explicit solution or simply impossible. In those instances, you can simulate!

Simulating this by hand would take an incredibly long time, so it’s time to write some Python to simulate playing this game 500 times.

After 500 games:

  • If you switched, the simulated outcome had you winning 68.4% of the time
  • If you kept you original choice, the simulated outcome had you winning 31.6% of the time

While those numbers aren’t exactly 66.66…% and 33.33…% they are close. If you run this simulation 100K times, your result continuously gets closer to those results. Here’s a visualization of the cumulative probability that was run where the x-axis is the simulation, and the y-axis is the running total of wins/games played.

500 simulations!

As you can see, the probabilities are a bit more volatile at first, but after 100 games it becomes much more stable.

If you are still not convinced, let’s play the same game where there are 100 doors with 99 goats and 1 car! (You pick one door, Monty opens up 98 doors with goats, and you are offered a choice to switch or keep your original pick).

500 simulations with 99 goats & 1 car

Wow! It’s pretty clear you should switch in this case:

  • Switching = 99.4% chance of winning
  • Staying = 0.06% chance of winning

You can find the code for the Jupyter Notebook on my GitHub


import random
import pandas as pd
import matplotlib as mpl
import matplotlib.pyplot as plt
%matplotlib inline
mpl.rcParams['figure.figsize'] = (10,6)

random.seed(123)

number_of_wins = []
number_of_simulations = range(500)
door_numbers = set(range(3))
switch_wins = []
stay_wins = []

for i in number_of_simulations:
car_door = random.randint(0,2)
contestant_first_choice = random.randint(0,2)

if car_door == contestant_first_choice:
switch_wins.append(0)
stay_wins.append(1)

if car_door != contestant_first_choice:
switch_wins.append(1)
stay_wins.append(0)

game_results = pd.DataFrame({
“stay_wins”: stay_wins,
“switch_wins”: switch_wins,
})

game_results[‘plays’] = game_results[‘stay_wins’] + game_results[‘switch_wins’]
game_results[‘cumulative_stay_wins’] = game_results[‘stay_wins’].cumsum()
game_results[‘cumulative_switch_wins’] = game_results[‘switch_wins’].cumsum()
game_results[‘cumulative_plays’] = game_results[‘plays’].cumsum()
game_results[‘cumulative_switch_win_pct’] = game_results[‘cumulative_switch_wins’] / game_results[‘cumulative_plays’]
game_results[‘cumulative_stay_win_pct’] = game_results[‘cumulative_stay_wins’] / game_results[‘cumulative_plays’]

fig, ax = plt.subplots(1, 1)
game_results.reset_index().plot(x=’index’, y=’cumulative_switch_win_pct’, ax=ax, )
game_results.reset_index().plot(x=’index’, y=’cumulative_stay_win_pct’, ax=ax)

game_totals = game_results[[‘cumulative_switch_win_pct’,’cumulative_stay_win_pct’]].tail(1)
game_totals