用R模擬Monty Hall Problem

在課前預習的講義中,老師向大家介紹了著名的Monty Hall Problem,並以條件機率以及貝氏定理的角度詮釋了「資訊流入」前後的差別。

如果還是感到很confused,不妨到以下網站來模擬一下,自己玩玩看這個Monty Hall Problem!

當然,到了電腦實習課,我們應該要有能力自己寫出模擬的程式!畢竟上面的網站,其背後的程式是怎麼寫的,我們怎麼會知道呢,當然不能不求甚解啦!

首先我們來定義一個function

#Monty Hall (Original)
#把問題寫成換與不換為input,有沒有得到car為output的fcn
monty_hall = function(switch = logical()){
  #randomly arrange the door
  doors = c(1,2,3)
  names(doors) = sample(c("goat", "car", "goat"))
  prize_door = which(names(doors)=='car') #find the index of car
  #Now Guess
  guess = sample(doors, 1)
  if(guess == prize_door){
    revealed_door = sample(doors[doors != prize_door], 1) #randomly open one of the goat door
  }else{
    revealed_door = doors[doors != prize_door & doors != guess] #open the other goat door
  }
  #Show the return
  if(switch){
    switched_door = doors[doors != guess & doors != revealed_door]
    return(prize_door == switched_door)
  } else {
    return(prize_door == guess)
  }
}

程式碼解說

函式所需的引數是「換」或「不換」,因為我們好奇的是「在一扇背後沒有車的門被主持人打開後,我們選擇『換』後贏得車的機率是否會變高」,所以把「換/不換」這個選項提到最前面是沒有關係的。實際上,我們就是在比較:(1.) 堅持不換 及 (2.)無論如何堅持要換,兩種策略讓我們贏得車的機率有何差別。

程式的第一個部分是隨機分配對應獎項的門,接著隨機地猜一道門。到這裡都相當直觀,每一道門被指派為獎品是車的機率是均等的;我們選到車的機率也是均等的。

door是一個有名字的向量(named vector),我們可以很簡單地用中括號([])來取向量的子集合。這是R的原生結構,跟python裡的pandas很像。

接著是一些邏輯判斷,因為主持人所開啟的門會依據我們選的門背後是不是車而有變化。如果我們選到的門背後是車,那麼主持人在剩下的兩道門中隨機選擇打開一道:

if(guess == prize_door){
    revealed_door = sample(doors[doors != prize_door], 1)
    }

如果我們選到的門背後是山羊,那麼主持人只有剩下另一道山羊門可以打開:

else{
    revealed_door = doors[doors != prize_door & doors != guess]
    }

選擇完要不要換(也就是我們的input,或者說引數argument所決定的)以後,答案就呼之欲出了!

假設我們選擇換:

那麼這時選擇的門會是哪一道呢?不會是我們一開始選的那道,也不會是被主持人打開的那道,如此一來用R最基本的邏輯判斷寫法,就可以知道如何「篩選」出正確的門了:

if(switch){
    switched_door = doors[doors != guess & doors != revealed_door]
    return(prize_door == switched_door)
    }

switched_door 表示決定換之後要選擇的門,這扇門必須從三扇門透過排除法得到。

guess 是我們最一開始隨機猜的那道門,所以最終選擇的門 switched_door 不會是它。revealed_door 則是主持人打開的門,我們也不能選它。我們利用取子集合的方式來「篩」doors 這個向量,它既然不是上述兩道門,所以取交集,以得到switched_door。最後把這道最終選擇之門跟藏有汽車的門比對即可輸出結果。

假設我們選擇不換:

那答案就很簡單了,只要把最一開始猜的 guess 跟藏有汽車的門比對即可輸出結果:

return(prize_door == guess)

看看模擬的結果如何

直接執行 monty_hall(T)monty_hall(F) 就可以看到玩「一次」Monty Hall Problem的結果囉!輸出如果是1表示你得到車了,若是0則是沒有。

那麼現在只要執行非常多變以上兩種策略的結果,然後比較看看得到車的機率有沒有差別就可以了!

# 選擇換
mean(replicate(monty_hall(T), n = 10000))

# 選擇不換
mean(replicate(monty_hall(F), n = 10000))
視覺化
result = replicate(monty_hall(T), n = 10000)
barplot(c(sum(result), length(result)-sum(result)), 
        main = 'The frequence of winning the car in 
        Monty Hall Problem in 10000 rounds',
        names.arg = c('Change', 'Do not change'), 
        ylim = c(0,10000))

結果是不是顯而易見呢?

拓展到n扇門的版本

事實上,要將這個程式碼改寫成n扇門的版本也相當容易!

monty_hall_n = function(switch = logical(), n){
  #randomly arrange the door
  doors = 1:n
  names(doors) = sample(rep(c("goat", "car"), c(n-1,1)))
  prize_door = which(names(doors)=='car') #find the index of car
  #Now Guess
  guess = sample(doors, 1)
  if(guess == prize_door){
    revealed_door = sample(doors[doors != prize_door], n-2) #randomly open one of the goat door
  }else{
    #revealed_door = sample(doors[doors != prize_door & doors != guess], n-2)
    revealed_door = doors[!doors %in% c(prize_door, guess)] #open the other goat door
  }
  #Show the return
  if(switch){
    switched_door = doors[!doors %in% c(guess, revealed_door)]#'%in%' is value matching
    return(prize_door == switched_door)
  } else {
    return(prize_door == guess)
  }
}

請參考

更詳盡的程式碼文檔以及解說請參考:

  1. 我在2019 Fall所寫的Monty Hall Problem講義
  2. 我放在YouTube的程式碼解說影片
    其一:https://www.youtube.com/watch?v=90ksaxiNX8U
    其二:https://www.youtube.com/watch?v=rvfIMoKS-K4

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *