Friday, May 26, 2023

Monty Hall Problem

One of the most famous probability puzzles, which initially tricked PhDs all over the world in the past few decades (originally posed and solved in 1975), is the "Monty Hall Problem", named after the original host (Monty Hall) of Let's Make a Deal.

As posed in the "viral" (for its time) Parade magazine column in 1990, by reader Craig Whitaker writing to Marilyn vos Savant:

Suppose you're on a game show, and you're given the choice of three doors: Behind one door is a car; behind the others, goats. You pick a door, say No. 1, and the host, who knows what's behind the doors, opens another door, say No. 3, which has a goat. He then says to you, "Do you want to pick door No. 2?" Is it to your advantage to switch your choice?

The optimal strategy, definitively, is to switch away from your initial choice - giving you 2/3 chance of winning the car, as opposed to only 1/3 if you stay with your original selection.

This has been proven time and again, including with straight forward illustrations on the linked Wikipedia page (these illustrations are included in the end of this post).

But doing the work yourself is the best way to learn, right? So I wrote a simple simulator in Python, included below, that you can run yourself to see this in action.

Over 100,000 runs (set this number to whatever you like; line 6, sims variable), the "switch" strategy clearly wins 2/3 of the time:


Opening the CSV of these sims in to Excel clearly shows the same result:


Even pigeons figure this out faster than humans! And most people intuitively get it wrong. Reading the responses to the 1990 Parade article are downright vicious, including "There is enough mathematical illiteracy in this country, and we don’t need the world’s highest IQ propagating more. Shame!" from a University of Florida Ph.D.

As promised, see below for the Python code. I tried to write this in under 100 lines, but I'm not exactly known for writing the cleanest code:
import random as rand
import time
import csv

filename = "C:\\temp\\Monty_Hall_" + "Sims_ao_" + time.strftime("%m_%d_%Y") + ".csv"
sims = 100000
trials = []
global switchWin, stayWin


def callRand():
r = rand.random()
return r


def assignChoice():
r = callRand()
if r < (1 / 3):
choice = 1
elif r < (2 / 3):
choice = 2
else:
choice = 3
return choice


def assignCar():
r = callRand()
if r < (1 / 3):
car = 1
elif r < (2 / 3):
car = 2
else:
car = 3
return car


def montyStandard(n, r, choice, car, strategy):
global stayWin, switchWin

if choice == 1:
if car == 2:
openDoor = 3
elif car == 3:
openDoor = 2
else:
if r < 0.5:
openDoor = 2
else:
openDoor = 3
elif choice == 2:
if car == 1:
openDoor = 3
elif car == 3:
openDoor = 1
else:
if r < 0.5:
openDoor = 1
else:
openDoor = 3
elif choice == 3:
if car == 1:
openDoor = 2
elif car == 2:
openDoor = 1
else:
if r < 0.5:
openDoor = 1
else:
openDoor = 2

if strategy == 'Switch':
if (openDoor == 1 and choice == 2) or (openDoor == 2 and choice == 1):
newChoice = 3
elif (openDoor == 2 and choice == 3) or (openDoor == 3 and choice == 2):
newChoice = 1
elif (openDoor == 3 and choice == 1) or (openDoor == 1 and choice == 3):
newChoice = 2
elif strategy == 'Stay':
newChoice = choice

if newChoice == car:
win = 1
else:
win = 0

if strategy == 'Switch' and win == 1:
switchWin += 1
elif strategy == 'Stay' and win == 1:
stayWin += 1

resultDict = {'Sim #': n, 'Strategy': strategy, 'Winner': win, 'Initial Choice': choice, 'Final Choice': newChoice,
'Car Door': car, 'Open Door': openDoor}
return resultDict


def printData():
columnNames = ["Sim #", "Strategy", "Winner", "Initial Choice", "Final Choice", "Car Door", "Open Door"]
with open(filename, 'w', newline='') as f:
writer = csv.DictWriter(f, fieldnames=columnNames)
writer.writeheader()
writer.writerows(trials)


def run():
global switchWin, stayWin
switchWin = stayWin = 0
for i in range(sims):
r = callRand()
choice = assignChoice()
car = assignCar()
trials.append(montyStandard(i, r, choice, car, 'Switch'))
trials.append(montyStandard(i, r, choice, car, 'Stay'))
printData()
print('Sims: ' + str(sims) + '; Switch Wins: ' + str(float(switchWin) / float(sims)) + ', Stay Wins: ' + str(
float(stayWin) / float(sims)))


run()
And the illustrations from the Wikipedia page: