Você está num jogo na TV e o apresentador pede para escolher uma entre 3 portas. Atrás de uma dessas portas tem uma Ferrari e nas outras duas temos cabras. Você escolhe uma porta. Depois, o apresentador retira uma porta que tem uma cabra e pergunta: você quer trocar de porta?
A princípio, você pode achar que sua probabilidade de ganhar é 1/2, já que uma das portas foi retirada, então não importa se você troca ou não. Mas a resposta é que sim, vale à pena trocar de porta! A probabilidade de vencer o jogo trocando a porta é de 2/3.
Figura 1: Brincadeira do XKCD.
O problema de Monty Hall é talvez o mais eloquente exemplo de como a probabilidade pode confundir a mente humana. Esse problema desafiou a comunidade científica no final do século XX e chegou até a ser considerado um paradoxo. Recomendo ler o livro O Andar do Bêbado, de Leonard Mlodinow, que conta essa e muitas outras histórias interessantes sobre a probabilidade.
Existem várias formas de explicar por quê trocar a porta é a melhor estratégia. A que eu mais gosto é a do próprio Andar do Bêbado, que mostra que, quando você escolhe a primeira porta, você está apostando se acertou ou não a Ferrari. Se você apostar que acertou a Ferrari, não deve trocar a porta e, se você apostar que errou a Ferrari, deve trocar. A aposta de errar a Ferrari de primeira tem probabilidade 2/3, logo, vale à pena trocar.
Nesse post, mostramos uma solução alternativa, simples e elegante para o problema usando diagramas de influência e o pacote bnlearn.
Redes bayesianas
As redes Bayesianas são o resultado da combinação de conceitos probabilísticos e conceitos da teoria dos grafos. Segundo Pearl, tal união tem como consequências três benefícios: i) prover formas convenientes para expressar suposições do modelo; ii) facilitar a representação de funções de probabilidade conjuntas; e iii) facilitar o cálculo eficiente de inferências a partir de observações.
Da teoria de probabilidades precisamos apenas de alguns resultados básicos sobre probabilidade condicional. Primeiramente, pela definição de probabilidade condicional, sabemos que
\[ p(x_1, x_2) = p(x_1)p(x_2|x_1). \]
Aplicando essa regra iterativamente para \(n\) variáveis, temos
\[ p(x_1, \dots, x_p) = \prod_j p(x_j|x_1,\dots, x_{j-1}). \]
Agora, imagine que, no seu problema, a variável aleatória \(X_j\) não dependa probabilisticamente de todas as variáveis \(X_1,\dots, X_{j-1}\), e sim apenas de um subconjunto \(\Pi_j\) dessas variáveis. Fazendo isso, a equação pode ser escrita como
\[ p(x_1, \dots, x_p) = \prod_j p(x_j|\pi_j). \]
Chamamos \(\Pi_j\) de pais de \(X_j\). Esse conjunto pode ser pensado como as variáveis que são suficientes para determinar as probabilidades de \(X_j\).
A parte mais legal das redes Bayesianas é que elas podem ser representadas a partir de DAGs (grafos direcionados acíclicos). No grafo, se \(X_1\) aponta para \(X_2\), então \(X_1\) é pai de \(X_2\). Por exemplo, esse grafo aqui
representa a distribuição de probabilidades \(p(x_1, \dots, x_5)\) com
\[ p(x_1, \dots, x_5) = p(x_1)p(x_2|x_1)p(x_3|x_1)p(x_4|x_3,x_2)p(x_5|x_4). \]
Diagrama de influências
Um diagrama e influências é uma rede Bayesiana com nós de decisão e utilidade (ganhos). Ou seja, é uma junção de três conceitos:
\[ \underbrace{\text{prob. condicional} + \text{grafos}}_{\text{rede Bayesiana}} + \text{teoria da decisão} = \text{diagrama de influências} \]
Na teoria da decisão, usualmente estamos interessados em maximizar a utilidade esperada. No diagrama, considerando a estrutura de probabilidades dada pela rede Bayesiana e as informações disponíveis, queremos escolher a decisão que faz com que, em média, nosso retorno seja mais alto.
Com diagramas de influências, é possível organizar sistemas complexos com múltiplas decisões, considerando diferentes conjuntos de informações disponíveis. É uma ferramenta realmente muito poderosa.
Voltando ao Monty Hall
Agora que sabemos um pouquinho de diagramas de influência, podemos desenhar o do Monty Hall:
O jogador tem duas decisões a tomar:
- \(D_1\) (
escolha_inicial): A escolha da porta inicial (1,2,3). - \(D_2\) (
trocar): Trocar a porta ou não (s,n). 
Também temos duas fontes de incerteza:
- \(X_1\) (
ferrari): Em qual porta está a Ferrari (1,2,3). - \(X_2\) (
porta_retirada): Qual porta foi retirada (1,2,3). Essa variável não é sempre aleatória: se eu escolho a porta 1 e a Ferrari está em 2, o apresentador é obrigado a retirar a porta 3. Se o apresentador tiver a opção de escolher (que acontece no caso da escolha inicial ser a Ferrari), o apresentador escolhe uma porta para retirar aleatoriamente. 
Finalmente, temos um nó de utilidade:
- \(U_1\) (
result): Ganhei a Ferrari (ganhei,perdi). 
Em R, podemos construir a rede Bayesiana do problema utilizando o pacote bnlearn:
# nós do grafo
nodes <- c("escolha_inicial", "ferrari", "porta_retirada", "trocar", "result")
# matriz de adjacências
edges <- matrix(
  c("escolha_inicial", "porta_retirada",
    "ferrari", "porta_retirada",
    "porta_retirada", "trocar",
    "trocar", "result",
    "ferrari", "result",
    "escolha_inicial", "result"),
  ncol = 2, 
  byrow = TRUE)
edges
##      [,1]              [,2]            
## [1,] "escolha_inicial" "porta_retirada"
## [2,] "ferrari"         "porta_retirada"
## [3,] "porta_retirada"  "trocar"        
## [4,] "trocar"          "result"        
## [5,] "ferrari"         "result"        
## [6,] "escolha_inicial" "result"
# criando o grafo a partir de um grafo vazio
g <- bnlearn::empty.graph(nodes)
bnlearn::arcs(g) <- edges
O output desse conjunto de operações é um objeto do tipo bn com várias propriedades pré calculadas pelo pacote bnlearn:
g
  Random/Generated Bayesian network
  model:
   [escolha_inicial][ferrari][porta_retirada|escolha_inicial:ferrari][trocar|porta_retirada]
   [result|escolha_inicial:ferrari:trocar]
  nodes:                                 5 
  arcs:                                  6 
    undirected arcs:                     0 
    directed arcs:                       6 
  average markov blanket size:           3.60 
  average neighbourhood size:            2.40 
  average branching factor:              1.20 
  generation algorithm:                  Empty 
Com as especificação do problema dada, se gerarmos aleatoriamente todos os cenários, chegamos à essa combinação de casos equiprováveis (ver Extra 2)
Agora, vamos escrever todas as combinações possíveis de cenários e guardar num data.frame chamado dados:
| escolha_inicial | ferrari | porta_retirada | trocar | result | 
|---|---|---|---|---|
| 1 | 1 | 2 | n | ganhei | 
| 1 | 1 | 2 | s | perdi | 
| 1 | 1 | 3 | n | ganhei | 
| 1 | 1 | 3 | s | perdi | 
| 1 | 2 | 3 | n | perdi | 
| 1 | 2 | 3 | s | ganhei | 
| 1 | 3 | 2 | n | perdi | 
| 1 | 3 | 2 | s | ganhei | 
| 2 | 1 | 3 | n | perdi | 
| 2 | 1 | 3 | s | ganhei | 
| 2 | 2 | 1 | n | ganhei | 
| 2 | 2 | 1 | s | perdi | 
| 2 | 2 | 3 | n | ganhei | 
| 2 | 2 | 3 | s | perdi | 
| 2 | 3 | 1 | n | perdi | 
| 2 | 3 | 1 | s | ganhei | 
| 3 | 1 | 2 | n | perdi | 
| 3 | 1 | 2 | s | ganhei | 
| 3 | 2 | 1 | n | perdi | 
| 3 | 2 | 1 | s | ganhei | 
| 3 | 3 | 2 | n | ganhei | 
| 3 | 3 | 2 | s | perdi | 
| 3 | 3 | 1 | n | ganhei | 
| 3 | 3 | 1 | s | perdi | 
Finalmente, ajustamos nossa rede Bayesiana, usando a função bnlearn::bn.fit().
fit <- bnlearn::bn.fit(g, dados)
A função bnlearn::cpquery() (conditional probability query) serve para realizar uma consulta de probabilidades dada a rede ajustada. No nosso caso, a partir de uma escolha inicial qualquer \(d_1\), queremos saber o ganho ao trocar é maior que o ganho ao não trocar.
\[ \mathbb E(U_1\; |\; D_2 = \text{s}, D_1 = d_1) > \mathbb E(U_1\; |\; D_2 = \text{n}, D_1 = d_1). \]
Fazendo contas, isso equivale matematicamente a consultar se
\[ \mathbb P(U_1=\text{ganhei}\; |\; D_2 = \text{s}) > \mathbb P(U_1=\text{ganhei}\; |\; D_2 = \text{n}) \]
Agora, podemos consultar \(\mathbb P(U_1=\text{ganhei}\; |\; D_2 = \text{s})\) com nosso modelo!
set.seed(13)                    # reprodutibilidade
bnlearn::cpquery(
  fitted = fit, 
  event = (result == "ganhei"), # o que queremos saber?
  evidence = (trocar == "s"),   # qual informação adicionar?
  n = 5e6)                      # n grande para aumentar a precisão
[1] 0.6666704
E não é que dá 2/3 mesmo? Da mesma forma, temos
bnlearn::cpquery(fit, (result == "ganhei"), (trocar == "n"), n = 5e6)
[1] 0.3333187
Resolvido!
Wrap-up
- Vale à pena trocar a porta!
 - Redes Bayesianas juntam grafos e probabilidades condicionais
 - Diagramas de influência juntam redes Bayesianas e teoria da decisão
 - Essas ferramentas podem ser utilizadas tanto para resolver Monty Hall quanto para ajudar em sistemas complexos.
 
É isso pessoal. Happy coding ;)
Extra
Se você ficou interessada em como eu fiz o diagrama, utilizei o pacote DiagrammeR. O código está aqui:
diagrama <- "
graph LR;
A{D1Escolha inicial}-->B(X2Porta retirada);
C(X1Ferrari)-->B;
B-->D{D2Trocar porta};
D-->E[U1Ganhar]
C-->E
A-->E
"
# tweak para centralizar e grifar as variáveis
diagrama <- stringr::str_replace_all(
  diagrama, 
  pattern = "([XDU][0-9])", 
  replacement = "<center><b>\\1</b></center><br/>")
DiagrammeR::DiagrammeR(diagrama)
Extra 2
É possível simular os dados que coloquei no post com uma função simples, que adicionei abaixo. Na verdade, o fato de eu ter considerado somente as combinações únicas de cenários e não os dados simulados abaixo é um pouco roubado, e só funciona porque os cenários calham de ser, de fato, equiprováveis.
set.seed(13)
simular_monty_hall <- function(z = 0) {
  v <- 1:3                                  # opcoes
  escolha_inicial <- sample(v, 1)           # escolha inicial aleatoria
  ferrari <- sample(v, 1)                   # ferrari aleatoria
  
  # qual porta retirar?
  if (escolha_inicial == ferrari) {
    porta_retirada <- sample(setdiff(v, ferrari), 1)
  } else {
    porta_retirada <- setdiff(v, c(escolha_inicial, ferrari))
  }
  
  # trocar porta?
  trocar <- sample(c("s", "n"), 1)
  
  # calculando resultado
  if (trocar == "s") {
    escolha_final <- setdiff(v, c(escolha_inicial, porta_retirada))
  } else {
    escolha_final <- escolha_inicial
  }
  result <- ifelse(escolha_final == ferrari, "ganhei", "perdi")
  
  # guardando no BD
  tibble::tibble(escolha_inicial, ferrari, porta_retirada, trocar, result)
}
dados_simulados <- purrr::map_dfr(seq_len(1e4), simular_monty_hall) %>% 
  dplyr::mutate_all(as.factor)
dplyr::glimpse(dados_simulados)
Observations: 10,000
Variables: 5
$ escolha_inicial <fct> 3, 1, 2, 1, 1, 1, 3, 1, 2, 3, 3, 1, 3, 1, 2, 2, 2,...
$ ferrari         <fct> 1, 1, 2, 1, 1, 2, 3, 3, 1, 2, 3, 3, 2, 1, 1, 3, 1,...
$ porta_retirada  <fct> 2, 3, 1, 3, 2, 3, 2, 2, 3, 1, 1, 2, 1, 2, 3, 1, 3,...
$ trocar          <fct> n, s, s, n, s, n, n, n, n, s, s, s, s, n, n, s, n,...
$ result          <fct> perdi, perdi, perdi, ganhei, perdi, perdi, ganhei,...
Os dados do post podem ser obtidos fazendo isso aqui:
dados_simulados %>% 
  dplyr::distinct() %>% 
  dplyr::arrange(escolha_inicial, ferrari)
Agradecimentos: Rafael Stern, que me convenceu de que vale à pena mostrar os dados simulados 😉