$P()$ é o nome tradicional para a função Probabilidade:
In [187]:
from fractions import Fraction
def P(evento, espaco):
"A probabilidade de um evento, dado o espaco amostral"
return Fraction(len(evento & espaco), len(espaco))
O código implementa o conceito "Probabilidade é simplemente uma fração cujo numerador é o número de casos favorávels e cujo denominador é o número de todos os casos possíveis" (Laplace).
In [188]:
A = {1, 2, 3, 4, 5, 6}
par = {2, 4, 6}
P(par, A)
Out[188]:
Você pode perguntar: "por que não usar len(evento)
ao invés de len(evento & espaco)
? Isso é necessário porque não podem ser considerados resultados que não estejam presentes no espaço amostral. Veja só:
In [189]:
par = {2, 4, 6, 8, 10, 12}
P(par, A)
Out[189]:
Se considerasse apenas len(evento)
, o retorno seria outro:
In [190]:
Fraction(len(par), len(A))
Out[190]:
Os "casos favoráveis", da definição de Laplace, são a interseção entre par
e A
, ou seja, $\mbox{evento} \cap A$. Em Python, isso é feito usando o operador &
, por isso len(evento & espaco)
.
O tipo Fraction
do Python é usado para representar valores na forma de frações (racional). Por exemplo:
In [191]:
print(Fraction(0.5))
print(Fraction(0.3))
print(Fraction(0.3).limit_denominator()) # usa limit_denominator() para encontrar a fração mais aproximada
print(Fraction(3, 10))
print(Fraction(0.3333333))
print(Fraction(0.3333333).limit_denominator()) # limitando o denominador (padrão é max=1000000)
print(float(Fraction(0.3333333))) # convertendo o valor de Fraction() para float()
Não estou falando de política! (ok, essa piada foi péssima)
Problemas com urnas surgiram por volta de 1700, com o matemático Jacob Bernoulli. Por exemplo:
Então, um resultado é um conjunto com 6 bolas, enquanto o espaço amostral é o conjunto de todas as possíveis combinações (também conjuntos) com 6 bolas. Antes de continuar a solução, duas questões:
Para a primeira questão, vamos nomear as bolas usando uma letra e um número, assim:
W1
até W8
B1
até B6
R1
até R9
Para a segunda questão teremos que trabalhar com permutações e então encontrar combinações, dividindo o número de permutações por $c!$, onde $c$ é o número de bolas em uma combinação.
Por exemplo, se eu precisar escolher 2 bolas de 8 disponíveis, há 8 formas de escolher a primeira, e 7 formas de escolher a segunda. Sendo assim, há $8 \times 7 = 56$ permutações, mas $52/2 = 26$ combinações. Tudo isso porque $\{W1, W2\} = \{W2, W1\}$.
Vamos lá. O código a seguir define o conteúdo da urna:
In [192]:
def cross(A, B):
"O conjunto de formas de concatenar os itens de A e B (produto cartesiano)"
return {a + b
for a in A for b in B
}
urna = cross('W', '12345678') | cross('B', '123456') | cross('R', '123456789')
urna
Out[192]:
In [193]:
len(urna)
Out[193]:
Agora, vamos definir o espaço amostral, chamado U6
, o conjunto de todas as combinações com 6 bolas:
In [194]:
import itertools
def combos(items, n):
"Todas as combinações de n items; cada combinação concatenada em uma string"
return {' '.join(combo)
for combo in itertools.combinations(items, n)
}
U6 = combos(urna, 6)
len(U6)
Out[194]:
Para não mostrar todos os conjuntos, vamos mostrar uma pequena amostra:
In [195]:
import random
random.sample(U6, 10)
Out[195]:
Mas será que o $100,947$ é a quantidade correta de formas de escolher 6 de 23 bolas? Bem, o raciocínio é esse:
Como não ligamos para a ordem (enfim, é um conjunto), então dividimos por $6!$. Isso dá:
\begin{align*} 23 \mbox{ escolha } 6 = \frac{23 \times 22 \times 21 \times 20 \times 19 \times 18}{6!} = 100947 \end{align*}Note que $23 \times 22 \times 21 \times 20 \times 19 \times 18 = 23!/17!$, então, podemos generalizar:
\begin{align*} n \mbox{ escolha } c = \frac{n!}{c!\times(n - c)!} = \binom{n}{c} \end{align*}Podemos traduzir isso para código, assim:
In [196]:
from math import factorial
def escolha(n, c):
"Número de formas de escolher c itens de uma lista com n items"
return factorial(n) // (factorial(c) * factorial(n - c))
escolha(23, 6)
Out[196]:
In [197]:
red6 = {s for s in U6 if s.count('R') == 6}
P(red6, U6)
Out[197]:
ou
In [198]:
len(red6)
Out[198]:
Por que 84 formas? Por que há 9 bolas vermelhas na urna, então estamos querendo saber quantas são as formas de escolher 6 delas:
In [199]:
escolha(9, 6)
Out[199]:
Então a probabilidade de selecionar 6 bolas vermelhas da urna é escolha(9, 6)
dividido pelo tamanho do espaço amostral:
In [200]:
P(red6, U6) == Fraction(escolha(9, 6), len(U6))
Out[200]:
In [201]:
b3w2r1 = {s for s in U6 if s.count('B') == 3 and s.count('W') == 2 and s.count('R') == 1}
P(b3w2r1, U6)
Out[201]:
Podemos encontrar o mesmo resultado contando de quantas formas podemos escolher 3 de 6 azuis, 2 de 8 brancas, 1 de 9 vermelhas e dividindo o resultado pelo tamanho do espaço amostral:
In [202]:
P(b3w2r1, U6) == Fraction(escolha(6, 3) * escolha(8, 2) * escolha(9, 1), len(U6))
Out[202]:
O raciocínio seguinte também é válido:
Como $\{B1, B2, B3\} = \{B3, B2, B1\}$ e $\{W1, W2\} = \{W2, W1\}$, teríamos:
\begin{align*} \frac{(6 \times 5 \times 4) \times (8 \times 7) \times (9)}{3! \times 2! \times |A|} \end{align*}
In [203]:
P(b3w2r1, U6) == Fraction((6 * 5 * 4) * (8 * 7) * 9,
factorial(3) * factorial(2) * len(U6))
Out[203]:
In [204]:
w4 = {s for s in U6 if
s.count('W') == 4}
P(w4, U6)
Out[204]:
In [205]:
P(w4, U6) == Fraction(escolha(8, 4) * escolha(15, 2),
len(U6))
Out[205]:
In [206]:
P(w4, U6) == Fraction((8 * 7 * 6 * 5) * (15 * 14),
factorial(4) * factorial(2) * len(U6))
Out[206]:
Esse último raciocínio, em particular, é interpretado assim:
Ou seja:
\begin{align*} \frac{(8 \times 7 \times 6) \times (15 \times 14)}{4! \times 2! \times |A|} \end{align*}
In [207]:
def P(evento, espaco):
"""A probabilidade de um evento, dado um espaco amostral.
evento pode ser um conjunto ou um predicado"""
if callable(evento):
evento = tal_que(evento, espaco)
return Fraction(len(evento & espaco), len(espaco))
def tal_que(predicado, colecao):
"O subconjunto de elementos da colecao para os quais o predicado é verdadeiro"
return {e for e in colecao if predicado(e)}
Vamos verificar como isso se comporta.
In [208]:
def eh_par(n):
return n % 2 == 0
P(eh_par, A)
Out[208]:
In [209]:
D12 = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}
tal_que(eh_par, D12)
Out[209]:
In [210]:
P(eh_par, D12)
Out[210]:
Isso permite coisas interessantes. Por exemplo, como determinar a probabilidade de que a soma de três dados é um número primo?
In [211]:
A3 = {(a1, a2, a3) for a1 in A for a2 in A for a3 in A}
def soma_eh_primo(r): return eh_primo(sum(r))
def eh_primo(n): return n > 1 and not any(n % i == 0 for i in range(2, n))
P(soma_eh_primo, A3)
Out[211]:
In [212]:
naipes = 'SHDC'
graus = 'A23456789TJQK'
baralho = cross(graus, naipes)
len(baralho)
Out[212]:
Qual a quantidade de jogadas (mãos) com 5 cartas?
In [213]:
jogadas = combos(baralho, 5)
print(len(jogadas) == escolha(52, 5))
random.sample(jogadas, 5)
Out[213]:
Qual a probabilidade de dar 'flush' (5 cartas do mesmo naipe)?
In [214]:
def flush(jogada):
return any(jogada.count(n) == 5 for n in naipes)
P(flush, jogadas)
Out[214]:
Ou... qual a probabilidade de dar 'four of a kind'?
In [215]:
def four_kind(jogada):
return any(jogada.count(g) == 4 for g in graus)
P(four_kind, jogadas)
Out[215]:
Considere um jogo de apostas, consistindo do [simples] fato de jogar uma moeda. Dois jogadores: H aposta em cara; e T aposta em coroa. Ganha o jogador que conseguir 10 acertos primeiro. Se o jogo for interrompido quando H tiver acertado 8 e T, acertado 7? Como dividir o pote de dinheiro? Fermat e Pascam trocaram correspondências sobre esse problema, o que pode ser lido aqui.
Podemos resolver o problema com as ferramentas que temos:
In [216]:
def ganhar_jogo_incompleto(h_pontos, t_pontos):
"A probabilidade de que H vai ganhar o jogo não terminado, dados os pontos necessários para H e T ganharem."
def h_ganha(r): return r.count('h') >= h_pontos
return P(h_ganha, continuacoes(h_pontos, t_pontos))
def continuacoes(h_pontos, t_pontos):
"Todas as continuações possíveis quando H precisa de `h_pontos` e T precisa de `t_pontos`"
rodadas = ['ht' for _ in range(h_pontos + t_pontos - 1)]
return set(itertools.product(*rodadas))
continuacoes(2, 3)
Out[216]:
In [217]:
ganhar_jogo_incompleto(2, 3)
Out[217]:
O resultado foi confirma o encontrado por Pascal e Fermat.
Até aqui, lidamos com casos em que a probabilidade de um retorno no espaço amostral é a mesma (uniforme). No mundo real, isso não é sempre verdade. Por exemplo, a probabilidade de uma criança ser uma menina não é exatamente 1/2, e a probabilidade é um pouco diferente para a segunda criança. Uma pesquisa encontrou as seguintes contagens de famílias com dois filhos na Dinamarca (GB significa uma família em que o primeiro filho é uma garota e o segundo filho é um menino):
GG: 121801 GB: 126840
BG: 127123 BB: 135138
Vamos introduzir mais três definições:
Definimos a classe ProbDist
(um subtipo do dict
do Python):
In [218]:
class ProbDist(dict):
"Uma distribuição de probablidade; um mapeamento {resultado: probabilidade}"
def __init__(self, mapping=(), **kwargs):
self.update(mapping, **kwargs)
total = sum(self.values())
for outcome in self:
self[outcome] = self[outcome]/total
assert self[outcome] >= 0
Também redefinimos as funções P
e tal_que
:
In [219]:
def P(evento, espaco):
"""A probabilidade de um evento, dado um espaço amostral de resultados equiprováveis.
evento: uma coleção de resultados, ou um predicado.
espaco: um conjunto de resultados ou a distribuicao de probabilidade na forma de pares {resultado: frequencia}.
"""
if callable(evento):
evento = tal_que(evento, espaco)
if isinstance(espaco, ProbDist):
return sum(espaco[o] for o in espaco if o in evento)
else:
return Fraction(len(evento & espaco), len(espaco))
def tal_que(predicado, espaco):
"""Os resultados no espaço amostral para os quais o predicado é verdadeiro.
Se espaco é um conjunto, retorna um subconjunto {resultado, ...}
Se espaco é ProbDist, retorna um ProbDist{resultado, frequencia}"""
if isinstance(espaco, ProbDist):
return ProbDist({o:espaco[o] for o in espaco if predicado(o)})
else:
return {o for o in espaco if predicado(o)}
Aqui está a distribuição de probabilidade para as famílias Dinamarquesas com dois filhos:
In [220]:
DK = ProbDist(GG=121801, GB=126840, BG=127123, BB=135138)
DK
Out[220]:
Vamos entender o resultado por partes. Para isso, alguns predicados:
In [221]:
def primeiro_menina(r): return r[0] == 'G'
def primeiro_menino(r): return r[0] == 'B'
def segundo_menina(r): return r[1] == 'G'
def segundo_menino(r): return r[1] == 'B'
def duas_meninas(r): return r == 'GG'
def dois_meninos(r): return r == 'BB'
In [222]:
P(primeiro_menina, DK)
Out[222]:
In [223]:
P(segundo_menina, DK)
Out[223]:
Isso indica que a probabilidade de uma criança ser menina está entre 48% e 49%, mas isso é um pouco diferente entre o primeiro e o segundo filhos.
In [224]:
P(segundo_menina, tal_que(primeiro_menina, DK)), P(segundo_menina, tal_que(primeiro_menino, DK))
Out[224]:
In [225]:
P(segundo_menino, tal_que(primeiro_menina, DK)), P(segundo_menino, tal_que(primeiro_menino, DK))
Out[225]:
Isso diz que é mais provável que o sexo do segundo filho seja igual ao do primeiro cerca de 50%.
Outro problema de urna (Allen Downey):
O M&M azul foi introduzido em 1995. Antes disso, a mistura de cores em um pacote de M&Ms era formado por: 30% marrom, 20% amarelo, 20% vermelho, 10% verde, 10% laranja, 10% tostado. Depois, ficou: 24% azul, 20% verde, 16% laranja, 14% amarelo, 14% vermelho, 13% marrom. Um amigo meu possui dois pacotes de M&Ms e ele me diz que um é de 1994 e outro é de 1996. Ele não me diz qual é qual, mas me dá um M&M de cada pacote. Um é amarelho e outro é verde. Qual a probabilidade de que o M&M amarelo seja do pacote de 1994?
Para resolver esse problema, primeiro representamos as distribuições de probabilidade de cada pacote:
In [226]:
bag94 = ProbDist(brown=30, yellow=20, red=20, green=10, orange=10, tan=10)
bag96 = ProbDist(blue=24, green=20, orange=16, yellow=14, red=13, brown=13)
A seguir, definimos MM
como a probabilidade conjunta -- o espaço amostral para escolher um M&M de cada pacote. O resultado yellow green
significa que um M&M amarelo foi selecionado do pacote de 1994 e um verde, do pacote de 1996.
In [227]:
def joint(A, B, sep=''):
"""A probabilidade conjunta de duas distribuições de probabilidade independentes.
Resultado é todas as entradas da forma {a+sep+b: P(a)*P(b)}"""
return ProbDist({a + sep + b: A[a] * B[b]
for a in A
for b in B})
MM = joint(bag94, bag96, ' ')
MM
Out[227]:
Primeiro, o predicado que trata "um é amarelho e o outro é verde":
In [228]:
def yellow_and_green(r): return 'yellow' in r and 'green' in r
tal_que(yellow_and_green, MM)
Out[228]:
Agora podemos responder a pergunta: dado que tivemos amarelo e verde (mas não sabemos sabemos qual vem de qual pacote), qual é a probabilidade de que o amarelo tenha vido do pacote de 1994?
In [229]:
def yellow94(r): return r.startswith('yellow')
P(yellow94, tal_que(yellow_and_green, MM))
Out[229]:
Então, há 74% de chance de que o amarelo tenha vindo do pacote de 1994.
A forma de resolver o problema foi semelhante ao que já vínhamos fazendo: criar um espaço amostral, usar P para escolher a probabilidade do evento em questão, dado que sabemos sobre o retorno.
Poderíamos usar o Teorema de Bayes, mas por que? Porque queremos saber a probabilidade de um evento dada uma evidência, que não está imediatamente disponível; entretanto, a probabilidade da evidência dado o evento está [disponível].
Antes de ver as cores dos M&Ms, há duas hipóteses, A e B, ambas com igual probabilidade:
A: primeiro M&M do pacote de 1994, segundo do pacote de 1996
B: primeiro M&M do pacote de 1996, segundo do pacote de 1994
\begin{align*}
P(A) = P(B) = 0.5
\end{align*}Então, temos uma evidência:
E: primeiro M&M amarelo, depois verde
Queremos saber a probabilidade da hipótese A, dada a evidência E: $P(A \mid E)$.
Isso não é fácil de calcular (exceto numerando o espaço amostral), mas o Teorema de Bayes diz:
\begin{align*} P(A \mid E) = \frac{P(E \mid A) \times P(A)}{P(E)} \end{align*}As quantidades do lado direito são mais fáceis de calcular:
\begin{align*} P(E \mid A) &= P(Yellow94) \times P(Green96) &= 0.20 \times 0.20 &= 0.04 \\ P(E \mid B) &= P(Yellow96) \times P(Green94) &= 0.10 \times 0.14 &= 0.014 \\ P(A) &= 0.5 \\ P(B) &= 0.5 \\ P(E) &= P(E \mid A) \times P(A) + P(E \mid B) \times P(B) \\ &= 0.04 \times 0.5 + 0.014 \times 0.5 = 0.027 \end{align*}O resultado final:
\begin{align*} P(A \mid E) &= \frac{P(E \mid A) \times P(A)}{P(E)} \\ &= \frac{0.4 \times 0.5}{0.027} \\ &= 0.7407407407 \end{align*}Então é isso. Você tem uma escolha: O Teorema de Bayes permite fazer menos cálculos, mas usa mais álgebra; é melhor custo-benefício se você estiver trabalhando com lápis e papel. Por outro lado, enumerar o espaço amostrar usa menos álgebra, pelo custo de requerer mais cálculos; é melhor custo-benefício se você estiver usando um computador. Idependentemente da abordagem utilizada, é importante conhecer o Teorema de Bayes e como ele funciona.
Mais importante ainda: você comeria M&Ms de 20 anos?