Imagem gerada por eu mesmo, usando Stable Diffusion.

1 Uma breve introdução ao problema de Monty Hall

O problema de Monty Hall, tambĂ©m conhecido por paradoxo de Monty Hall Ă© um problema matemĂĄtico e paradoxo que surgiu a partir de um concurso televisivo dos Estados Unidos chamado Let’s Make a Deal, exibido na dĂ©cada de 1970.

O jogo consistia no seguinte: Monty Hall, o apresentador, apresentava trĂȘs portas aos concorrentes. AtrĂĄs de uma delas estava um prĂȘmio (um carro) e, atrĂĄs das outras duas, dois bodes.

 

Imagem retirada do google.

Qual Ă© a estratĂ©gia mais lĂłgica? Ficar com a porta escolhida inicialmente ou mudar de porta? Com qual das duas portas ainda fechadas o concorrente tem mais probabilidades de ganhar? Por quĂȘ?

Nota: copiei o trecho de introdução acima da wikipedia.

2 Motivação

A computação Ă© uma ferramenta extremamente versĂĄtil e nos possibilita a representação de muitas coisas menos tangĂ­veis. O paradoxo, ou problema, de Monty Hall Ă© uma situação que gerou muita polĂȘmica quando surgiu. A princĂ­pio, a solução Ăłbvia, mas errada, Ă© que todas as portas tem a mesma probabilidade de conter um carro. Mas Ă© aĂ­ que muitas pessoas se enganam, e nĂŁo por culpa delas, a estatĂ­stica consegue ser exaustivamente cansativa e nĂłs humanos temos muita dificuldade de generalizar algumas situaçÔes.

A resposta mais correta para a pergunta da introdução é que a probabilidade de haver um carro na porta não escolhida é de 66.6%. Isso acontece porque a probabilidade de haver um carro nas duas portas, antes de Hall intervir no jogo é de 66.6%. O simples fato de Hall conhecer a porta correta e necessariamente escolher a errada interfere no jogo.

Nesse sentido, minha motivação com esse documento é de demonstrar que essa probabilidade faz todo o sentido pråtico com simulaçÔes computacionais.

3 Pacotes utilizados neste documento

library(parallel) # Para rodar a função que criei em paralelo dentro da minha cpu
library(ggplot2) # Para gerar os grĂĄficos no Ășltimo capĂ­tulo
library(plotly) # GrĂĄficos no html mais bonitos e interativos
library(data.table) # Formato de tabela mais eficiente que existe no R

4 A simulação em função

Abaixo, determino uma função que simula perfeitamente o jogo que Hall realizava na tv. Coloquei um “extra”, que me permite aumentar o nĂșmero de portas, mantendo a lĂłgica do jogo.

monty_hall <- function(portas = 1:3, trocar = TRUE){
  
  # Determino a porta premiada antes do início da simulação.
  porta_premiada <- sample(portas, 1)
  
  # O computador escolhe uma das portas
  porta_escolhida <- sample(portas, 1)
  
  # Se o jogador nunca trocar de portas, o jogo pode acabar aqui mesmo.
  if (trocar == FALSE) {
    return(porta_escolhida == porta_premiada)
  }
  
  ### A Intervenção de Hall
  
  # Adiciono uma abordagem recursiva para permitir a simulação com mais de 3 portas.
  
  # Seleciona as n portas que o jogador nĂŁo escolheu
  portas_nao_selecionadas <- portas[!porta_escolhida == portas]
  
  while (length(portas_nao_selecionadas) > 1) {
    # Remove a porta nĂŁo premiada (se nenhuma for premiada, remove aleatorio)
    # Uma consideração importante a ser feita é sobre a natureza da função sample().
    # Caso a função seja usada da seguinte forma: sample(2, 1), o valor retornado não é 2!
    
    if (porta_premiada %in% portas_nao_selecionadas) {
      portas_nao_selecionadas_sem_porta_premiada <- portas_nao_selecionadas[portas_nao_selecionadas != porta_premiada]
      
      # Como o valor acima pode assumir um numero inteiro, adiciono a lĂłgica abaixo
      if(length(portas_nao_selecionadas_sem_porta_premiada) == 1) {
        porta_a_ser_removida <- portas_nao_selecionadas_sem_porta_premiada 
      } else {
        porta_a_ser_removida <- sample(portas_nao_selecionadas_sem_porta_premiada, 1, replace = FALSE)
      }
      
      portas_nao_selecionadas <- portas_nao_selecionadas[portas_nao_selecionadas != porta_a_ser_removida]
    } else {
      porta_a_ser_removida <- sample(portas_nao_selecionadas, 1, replace = FALSE)
      portas_nao_selecionadas <- portas_nao_selecionadas[portas_nao_selecionadas != porta_a_ser_removida]
    }
    
    # Opção de trocar ou não de porta
    if (trocar) {
      # No caso de mais de uma porta, a troca é realizada todas as vezes, até o fim.
      if (length(portas_nao_selecionadas) > 1) {
        porta_escolhida <- sample(portas_nao_selecionadas, 1, replace = FALSE)
      } else {
        porta_escolhida <- portas_nao_selecionadas # Até sobrar uma
      }
    }
  }
  
  # Retorna verdadeiro para o caso de ganho do jogo e falso para perda.
  return(porta_escolhida == porta_premiada)
}

A tĂ­tulo de ilustração, minha função que simula o paradoxo de Monty Hall retorna apenas um valor, que indica se eu ganhei o carro ou nĂŁo. Dentro dessa função, determino se eu seguirei a estratĂ©gia de trocar ou nĂŁo de portas, e o nĂșmero de portas.

Um exemplo dessa simulação, rodando apenas uma vez, com 3 portas e trocando a porta escolhida após a intervenção de Hall é a seguinte:

set.seed(42)
monty_hall(1:3, trocar = TRUE)
## [1] FALSE

Nesse caso acima, como o retorno da função foi FALSE, eu não ganhei o carro :(

5 Resultados

A função desenvolvida simula perfeitamente o problema de Monty Hall, a ideia que apresento aqui, é de rodar essa situação dez mil de vezes, uma trocando a porta escolhida inicialmente, e outra mantendo a escolha inicial. Quantas vezes eu ganho o jogo mantendo a porta que escolhi no começo? Quantas vezes eu ganho trocando a porta sempre que possível?

Observação: Isso só é possível hoje em dia porque temos o computador e a programação de forma acessível!

Abaixo, o código responsåvel por replicar a minha função 20.000 vezes, 10.000 vezes para cada caso.

nucleos <- detectCores(logical = FALSE)
cl <- makeCluster(nucleos, type = "FORK")

numero_de_vezes <- 10000

resultado_trocando_de_portas <- clusterApply(cl, rep(list(1:3), numero_de_vezes), monty_hall)
resultado_trocando_de_portas <- unlist(resultado_trocando_de_portas)

resultado_nao_trocando_de_portas <- clusterApply(cl, rep(list(1:3), numero_de_vezes), monty_hall, trocar = FALSE)
resultado_nao_trocando_de_portas <- unlist(resultado_nao_trocando_de_portas)

stopCluster(cl)

Veja, que a ideia que utilizo Ă© de simplesmente captar o resultado de cada jogo. Cada um desses resultados Ă© salvo em uma lista, que Ă© um tipo de variĂĄvel no R. Por exemplo, o que meu computador gerou para resultado_trocando_de_portas (100 de 10000) foi:

head(resultado_trocando_de_portas, 100)
##   [1]  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE
##  [13]  TRUE  TRUE  TRUE  TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
##  [25] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
##  [37] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
##  [49] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
##  [61] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
##  [73] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE  TRUE  TRUE  TRUE  TRUE
##  [85]  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE
##  [97]  TRUE  TRUE  TRUE  TRUE

Agora é simples, comparo nas duas situaçÔes quantas vezes eu ganhei o carro nas duas estratégias. Uma representação em gråfico de barras é a seguinte:

# Salvo em uma tabela so
tabela_trocar <- data.frame(resultado = resultado_trocando_de_portas, estrategia = rep("Trocar", numero_de_vezes))
tabela_sem_trocar <- data.frame(resultado = resultado_nao_trocando_de_portas, estrategia = rep("NĂŁo trocar", numero_de_vezes))
dados <- as.data.table(rbind(tabela_sem_trocar, tabela_trocar))

# Faço o cålculo dessa proporção, para cada estratégia
dados <- dados[, .(resultado = sum(resultado) / numero_de_vezes), estrategia]

ggplotly(
  ggplot(dados, aes(resultado, estrategia)) +
    geom_col(position = "dodge", fill = "steelblue") +
    theme_classic() +
    coord_flip() +
    labs(y = "SimulaçÔes em que a estratégia foi:", x = "Proporção de vitórias, com relação ao total de jogos") +
    scale_x_continuous(n.breaks = 15, limits = c(0, 1), labels = scales::label_percent()) +
    theme(plot.caption = element_text(hjust=0.5))
)

Ou seja, a estratégia de trocar de portas me faz ganhar o carro ~66.6% das vezes, enquanto não trocar me faz ganhar somente ~33.3%!

Caso eu aumente o nĂșmero de portas para 5, veja como essa estratĂ©gia fica ainda mais descarada.

nucleos <- detectCores(logical = FALSE)
cl <- makeCluster(nucleos, type = "FORK")

numero_de_vezes <- 10000

resultado_trocando_de_portas <- clusterApply(cl, rep(list(1:5), numero_de_vezes), monty_hall)
resultado_trocando_de_portas <- unlist(resultado_trocando_de_portas)

resultado_nao_trocando_de_portas <- clusterApply(cl, rep(list(1:5), numero_de_vezes), monty_hall, trocar = FALSE)
resultado_nao_trocando_de_portas <- unlist(resultado_nao_trocando_de_portas)

stopCluster(cl)

# Salvo em uma tabela so
tabela_trocar <- data.frame(resultado = resultado_trocando_de_portas, estrategia = rep("Trocar", numero_de_vezes))
tabela_sem_trocar <- data.frame(resultado = resultado_nao_trocando_de_portas, estrategia = rep("NĂŁo trocar", numero_de_vezes))
dados <- as.data.table(rbind(tabela_sem_trocar, tabela_trocar))

# Faço o cålculo dessa proporção, para cada estratégia
dados <- dados[, .(resultado = sum(resultado) / numero_de_vezes), estrategia]

ggplotly(
  ggplot(dados, aes(resultado, estrategia)) +
    geom_col(position = "dodge", fill = "steelblue") +
    theme_classic() +
    coord_flip() +
    labs(y = "SimulaçÔes em que a estratégia foi:", x = "Proporção de vitórias, com relação ao total de jogos") +
    scale_x_continuous(n.breaks = 15, limits = c(0, 1), labels = scales::label_percent()) +
    theme(plot.caption = element_text(hjust=0.5))
)

Em uma simulação com 5 portas, cada uma teria a mesma chance inicial de 20% de conter um carro, mas se, para cada vez que Hall fizer a pergunta de trocar ou não de porta, o jogador decidir trocar, ele consegue ganhar 80% das vezes que troca sempre que possível! Que absurdo né? Nada intuitivo isso.

E com 100 portas? A probabilidade do carro estar atrĂĄs de qualquer uma dessas seria de 1%. Se eu trocar absolutamente todas as vezes, o que acontece?

nucleos <- detectCores(logical = FALSE)
cl <- makeCluster(nucleos, type = "FORK")

numero_de_vezes <- 10000

resultado_trocando_de_portas <- clusterApply(cl, rep(list(1:100), numero_de_vezes), monty_hall)
resultado_trocando_de_portas <- unlist(resultado_trocando_de_portas)

resultado_nao_trocando_de_portas <- clusterApply(cl, rep(list(1:100), numero_de_vezes), monty_hall, trocar = FALSE)
resultado_nao_trocando_de_portas <- unlist(resultado_nao_trocando_de_portas)

stopCluster(cl)

# Salvo em uma tabela so
tabela_trocar <- data.frame(resultado = resultado_trocando_de_portas, estrategia = rep("Trocar", numero_de_vezes))
tabela_sem_trocar <- data.frame(resultado = resultado_nao_trocando_de_portas, estrategia = rep("NĂŁo trocar", numero_de_vezes))
dados <- as.data.table(rbind(tabela_sem_trocar, tabela_trocar))

# Faço o cålculo dessa proporção, para cada estratégia
dados <- dados[, .(resultado = sum(resultado) / numero_de_vezes), estrategia]

ggplotly(
  ggplot(dados, aes(resultado, estrategia)) +
    geom_col(position = "dodge", fill = "steelblue") +
    theme_classic() +
    coord_flip() +
    labs(y = "SimulaçÔes em que a estratégia foi:", x = "Proporção de vitórias, com relação ao total de jogos") +
    scale_x_continuous(n.breaks = 15, limits = c(0, 1), labels = scales::label_percent()) +
    theme(plot.caption = element_text(hjust=0.5))
)

Trocar de portas é sempre a melhor estratégia possível!

Espero que tenha gostado desse documento, e até uma próxima!