Capítulo 05 - Probabilidade
As leis de probabilidade, tão verdadeiras em geral, tão falaciosas em particular. (Edward Gibbon)
As leis de probabilidade, tão verdadeiras em geral, tão falaciosas em particular. (Edward Gibbon)
É difícil fazer ciência de dados sem algum tipo de compreensão da probabilidade e sua matemática. Como no nosso tratamento de estatísticas no Capítulo 4, eliminaremos muitos dos detalhes técnicos.
Para nossos propósitos, você deve pensar em probabilidade como uma maneira de quantificar a incerteza associada aos eventos escolhidos de algum universo de eventos. Em vez de se tornar técnico sobre o que esses termos significam, pense em rolar um dado. O universo consiste em todos os resultados possíveis. E qualquer subconjunto desses resultados é um evento; Por exemplo, o resultado do dado é 1 ou o resultado do dado é um número par.
Escrevemos P(E) para significar a probabilidade do evento E.
Usaremos a teoria da probabilidade para construir e avaliar modelos. Usaremos a teoria da probabilidade em todo o lugar.
Dependência e independência
Aproveitando, dizemos que dois eventos E e F dependem se saber algo sobre se E acontece que nos dá informações sobre se F acontece (e vice-versa). Caso contrário, eles são independentes.
Por exemplo, se jogarmos uma moeda justa duas vezes, saber se o primeiro lançamento é cara não nos dá informações sobre se o segundo lançamento será cara. Esses eventos são independentes. Por outro lado, saber se o primeiro lançamento é cara certamente nos dá informações sobre se ambos os lançamentos são coroa. Se o primeiro lançamento é cara, definitivamente não é o caso de ambos os lançamentos serem coroa. Esses dois eventos são dependentes.
Matematicamente, dizemos que dois eventos E e F são independentes se a probabilidade de que ambos aconteçam é o produto das probabilidades que cada um acontece:
P(E,F) = P(E)P(F)
No exemplo, a probabilidade do primeiro lançamento ser cara é de 1/2, e a probabilidade de ambos os lançamentos deem coroa é 1/4, mas a probabilidade de do primeiros lançamento ser cara e ambos serem coroa é 0.
Probabilidade condicional
Quando dois eventos E e F são independentes, então, por definição, temos:
P(E,F) = P(E)P(F)
Se eles não forem necessariamente independentes (e se a probabilidade de F não for zero), definimos a probabilidade de E condicional em F como:
P(E|F) = P(E,F) / P(F)
Você deve pensar nisso como a probabilidade de que E aconteça, já que sabemos que F acontece. Frequentemente reescrevemos isso como:
P(E,F) = P(E|F) / P(F)
Quando E e F são independentes, você pode verificar se isso dá:
P(E|F) = P(E)
Que é a maneira matemática de expressar que o conhecimento do F ocorreu não nos fornece informações adicionais sobre se E ocorreu.
Um exemplo complicado comum envolve uma família com duas crianças (desconhecidas). Se assumirmos isso:
Cada criança é igualmente provável de ser menino ou menina.
O gênero do segundo filho é independente do gênero do primeiro filho.
Então o evento não meninas tem probabilidade 1/4, o evento uma menina e um menino tem probabilidade 1/2 e o evento duas meninas tem probabilidade 1/4.
Agora, podemos perguntar qual é a probabilidade do evento as duas crianças são meninas (B) condicionadas ao evento a criança mais velha é uma garota (G)? Usando a definição de probabilidade condicional:
P(B|G) = P(B,G) / P(G) = P(B) / P(G) = 1/2
Como o evento B e G (ambas as crianças são meninas e a criança mais velha é uma menina) é apenas o evento B, uma vez que você sabe que as duas crianças são meninas, é necessariamente verdade que a criança mais velha é uma menina. Provavelmente, esse resultado está de acordo com sua intuição.
Também poderíamos perguntar sobre a probabilidade de o evento ambas as crianças são meninas condicionais no evento pelo menos uma das crianças é uma garota (L). Surpreendentemente, a resposta é diferente de antes!
Como antes, o evento B e L (ambas as crianças são meninas e pelo menos uma das crianças é uma garota) é apenas o evento B. Isso significa que temos:
P(B|L) = P(B,L) / P(L) = P(B) / P(L) = 1/3
Como esse pode ser o caso? Bem, se tudo o que você sabe é que pelo menos uma das crianças é uma menina, é duas vezes mais provável que a família tenha um menino e uma menina do que duas meninas. Podemos verificar isso gerando muitas famílias:
import enum, random
# --- Um Enum é um conjunto digitado de valores enumerados.--- #
# --- Podemos usá-los para tornar nosso código mais descritivo e legível. --- #
class Crianca(enum.Enum):
MENINO = 0
MENINA = 1
def crianca_aleatoria() -> Crianca:
return random.choice([Crianca.MENINO, Crianca.MENINA])
ambas_meninas = 0
menina_velha = 0
ao_menos_1_menina = 0
random.seed(0)
for _ in range(10000):
jovem = crianca_aleatoria()
velho = crianca_aleatoria()
if velho == Crianca.MENINA:
menina_velha += 1
if velho == Crianca.MENINA and jovem == Crianca.MENINA:
ambas_meninas += 1
if velho == Crianca.MENINA or jovem == Crianca.MENINA:
ao_menos_1_menina += 1
print(f'P(ambas|mais velha): {ambas_meninas / menina_velha:.3f}')
print(f'P(ambas|ao menos uma menina): {ambas_meninas / ao_menos_1_menina:.3f}')
Teorema de Bayes
Um dos melhores amigos do cientista de dados é o teorema de Bayes, que é uma maneira de "reverter" as probabilidades condicionais. Digamos que precisamos saber a probabilidade de algum evento E condicional em algum outro evento F ocorrendo. Mas temos apenas informações sobre a probabilidade de F condicionado na ocorrência de E. Usar a definição de probabilidade condicional duas vezes nos diz que:
P(E|F) = P(E,F) / P(F) = P(F|E)P(E) / P(F)
O evento F pode ser dividido nos dois eventos mutuamente exclusivos F e E e F e não E. Se escrevermos para não E (ou seja, E não acontecer), então:
P(F) = P(F,E) + P(F,¬E)
Então:
P(E|F) = P(F|E)P(E) / [P(F|E)P(E) + P(F|¬E)P(¬E)]
É assim que o teorema de Bayes é frequentemente declarado.
Esse teorema geralmente é usado para demonstrar por que os cientistas de dados são mais inteligentes que os médicos. Imagine uma certa doença que afeta 1 em cada 10.000 pessoas. E imagine que existe um teste para esta doença que dê o resultado correto (doente se você tiver a doença e não doente se não tiver) 99% das vezes.
O que significa um teste positivo? Vamos usar T para o evento seu teste é positivo e D para o evento você tem a doença. Então o teorema de Bayes diz que a probabilidade de você ter a doença, condicionada ao teste positivo, é:
P(D|T) = P(T|D)/P(D) / [P(T|D)P(D) + P(T|¬D)P(¬D)
Aqui sabemos que P(T|D), a probabilidade de que alguém com a doença testa positivo, seja de 0,99. P(D), a probabilidade de que qualquer pessoa tenha a doença é 1/10.000 = 0,0001. P(T|¬D), a probabilidade de que alguém sem a doença testa positivo, seja 0,01. EP(¬D), a probabilidade de que qualquer pessoa não tenha a doença é 0,9999. Se você substituir esses números no teorema de Bayes, encontrará:
P(D/T) = 0.98%
Ou seja, menos de 1% das pessoas que testam positivas realmente têm a doença.
Isso pressupõe que as pessoas fazem o teste mais ou menos aleatoriamente. Se apenas as pessoas com certos sintomas fizessem o teste, teremos que condicionar o evento teste e sintomas positivos e o número provavelmente seria muito maior.
Uma maneira mais intuitiva de ver isso é imaginar uma população de 1 milhão de pessoas. Você espera que 100 deles tenham a doença e 99 desses 100 testem positivos. Por outro lado, você espera que 999.900 deles não tenham a doença e 9.999 deles testem positivo. Isso significa que você espera que apenas 99 dos testadores positivos (99 + 9999) tenham a doença.
Variáveis aleatórias
Uma variável aleatória é uma variável cujos valores possíveis têm uma distribuição de probabilidade associada. Uma variável aleatória muito simples é igual a 1 se um lançamento de moeda aumentará as caras e 0 se o lançamento der coroa. Um mais complicado pode medir o número de caras que você observa ao lançar uma moeda 10 vezes, onde cada número é igualmente provável.
A distribuição associada fornece as probabilidades de que a variável realiza cada um de seus valores possíveis. A variável lançamento de moedas é igual a 0 com probabilidade 0,5 e 1 com probabilidade 0,5. A variável range(10) possui uma distribuição que atribui probabilidade 0,1 a cada um dos números de 0 a 9.
Às vezes, falamos sobre o valor esperado de uma variável aleatória, que é a média de seus valores ponderados por suas probabilidades. A variável lançamento de moedas possui um valor esperado de 1/2 (= 0 * 1/2 + 1 * 1/2) e a variável range(10) tem um valor esperado de 4,5.
Variáveis aleatórias podem ser condicionadas a eventos, assim como outros eventos podem. Voltando ao exemplo de dois filhos de Probabilidade Condicional, se x é a variável aleatória que representa o número de meninas, x é igual a 0 com probabilidade 1/4 (nenhuma menina), 1 com probabilidade 1/2 (ao menos uma menina) e 2 com probabilidade 1/4 (ambas meninas).
Podemos definir uma nova variável aleatória y, que dá o número de meninas condicionadas a pelo menos uma das crianças sendo uma menina. Então y é igual a 1 com probabilidade 2/3 e 2 com probabilidade 1/3. E uma variável z que é o número de meninas condicionadas para a criança mais velha ser uma menina é igual a 1 com probabilidade 1/2 e 2 com probabilidade 1/2.
Na maioria das vezes, usaremos variáveis aleatórias implicitamente no que fazemos sem chamar atenção especial a elas. Mas se você olhar profundamente, você os verá.
Distribuições contínuas
Um lançamento de moedas corresponde a uma distribuição discreta - uma que associa a probabilidade positiva a resultados discretos. Muitas vezes, queremos modelar distribuições em um continuum de resultados. (Para nossos propósitos, esses resultados sempre serão números reais, embora esse nem sempre seja o caso na vida real). Por exemplo, a distribuição uniforme coloca o mesmo peso em todos os números entre 0 e 1.
Como existem infinitamente muitos números entre 0 e 1, isso significa que o peso que atribui a pontos individuais deve necessariamente ser zero. Por esse motivo, representamos uma distribuição contínua com uma função de densidade de probabilidade (FDP), de modo que a probabilidade de ver um valor em um determinado intervalo é igual à integral da função de densidade ao longo do intervalo.
A função de densidade para a distribuição uniforme é apenas:
def fdp_uniforme(x: float) -> float:
return 1 if 0 <= x < 1 else 0
A probabilidade de que uma variável aleatória após essa distribuição esteja entre 0,2 e 0,3 seja 1/10, como seria de esperar. A função random é (pseudo) aleatória com uma densidade uniforme.
Frequentemente estaremos mais interessados na função de distribuição cumulativa (FDC), que fornece a probabilidade de que uma variável aleatória seja menor ou igual a um determinado valor. Não é difícil criar a FDC para a distribuição uniforme (Figura 1):
def cdf_uniforme(x: float) -> float:
if x < 0:
return 0 # a uniformidade aleatória nunca é menor do que 0
elif x < 1:
return x # Ex: P(x <= 0.4) = 0.4
else :
return 1 # a uniformidade aleatória é sempre menor do que 1
Figura 1: A FCD para a distribuição uniforme.
A distribuição normal
A distribuição normal é a distribuição clássica da curva de sino e é completamente determinada por dois parâmetros: sua média μ (mu) e seu desvio padrão σ (sigma). A média indica onde o "sino" está centrado e o desvio padrão quão "largo" é. Tem a FDP:
Que podemos implementar como:
import math
RQ_2_PI = math.sqrt(2 * math.pi)
def fdp_normal(x: float, mu: float=0, sigma: float=1) -> float:
return (math.exp(- (x - mu) ** 2 / 2 / sigma ** 2) / (RQ_2_PI * sigma))
Na Figura 2, traçamos alguns desses FDPs para ver como eles são:
import matplotlib.pyplot as plt
xs = [x / 10.0 for x in range(-50, 50)]
plt.plot(xs, [fdp_normal(x, sigma=1) for x in xs], '-', label='mu=0,sigma=1')
plt.plot(xs, [fdp_normal(x, sigma=2) for x in xs], '--', label='mu=0,sigma=2')
plt.plot(xs, [fdp_normal(x, sigma=0.5) for x in xs], ':', label='mu=0,sigma=0.5')
plt.plot(xs, [fdp_normal(x, mu=-1) for x in xs], '-.', label='mu-1,sigma=1')
plt.legend()
plt.title('Várias FDPs')
plt.show()
Figura 2: Várias FDPs normais.
Quando μ = 0 e σ = 1, é chamado de distribuição normal padrão. Se z é uma variável aleatória normal padrão, acontece que:
X = σZ + μ
Também é normal, mas com μ médio e desvio padrão σ. Por outro lado, se x é uma variável aleatória normal com μ médio e desvio padrão σ:
Z = (X − μ)/σ
É uma variável normal padrão.
O FCD para a distribuição normal não pode ser escrito de uma maneira "elementar", mas podemos escrevê-la usando a função de erro math.erf do Python:
def fcd_normal(x: float, mu:float=0, sigma:float=1) -> float:
return (1 + math.erf((x - mu) / math.sqrt(2) / sigma)) / 2
Novamente, na Figura 3, traçamos alguns FCDs:
xs = [x / 10.0 for x in range(-50, 50)]
plt.plot(xs, [fcd_normal(x, sigma=1) for x in xs], '-', label='mu=0,sigma=1')
plt.plot(xs, [fcd_normal(x, sigma=2) for x in xs], '--', label='mu=0,sigma=2')
plt.plot(xs, [fcd_normal(x, sigma=0.5) for x in xs], ':', label='mu=0,sigma=0.5')
plt.plot(xs, [fcd_normal(x, mu=-1) for x in xs], '-.', label='mu-1,sigma=1')
plt.legend()
plt.title('Várias FCDs')
plt.show()
Figura 3: Várias FCDs normais.
Às vezes, precisamos inverter a fcd_normal para encontrar o valor correspondente a uma probabilidade especificada. Não existe uma maneira simples de calcular seu inverso, mas fcd_normal é contínuo e aumentando estritamente, para que possamos usar uma busca binária:
def fcd_normal_inversa(p:float,
mu:float=0,
sigma:float=1,
tolerancia:float=0.00001) -> float:
"""Encontra aproximadamente a inversão usando a busca binária."""
# --- Se não for o padrão, calcular o padrão e reescalar --- #
if mu != 0 or sigma != 1:
return mu + sigma * fcd_normal_inversa(p, tolerancia=tolerancia)
z_baixo = -10.0 # fcd_normal(-10) é bem próximo de 0
z_alto = 10.0 # fcd_normal(10) é bem próximo de 1
while z_alto - z_baixo > tolerancia:
z_medio = (z_baixo + z_alto) / 2 # considerar o ponto médio
p_medio = fcd_normal(z_medio) # e o valor dos FCDs
if p_medio < p:
z_baixo = z_medio # valor médio muito baixo, buscar acima
else:
z_alto = z_medio # valor médio muito alto, buscar abaixo
return z_medio
A função divide repetidamente os intervalos até que ela se restrinja em um z que está próximo o suficiente da probabilidade desejada.
O teorema do limite central
Uma das razões pelas quais a distribuição normal é tão útil é o teorema do limite central, que diz (em essência) que uma variável aleatória definida como a média de um grande número de variáveis aleatórias independentes e distribuídas idênticas é aproximadamente distribuída normalmente.
Em particular, se x1, ..., xn são variáveis aleatórias com μ médio e desvio padrão σ, e se n é grande, então:
É aproximadamente normalmente distribuído com μ média e desvio padrão σ/√n. Equivalente, mas muitas vezes mais útil,
É aproximadamente normalmente distribuído com média 0 e desvio padrão 1.
Uma maneira fácil de ilustrar isso é observando variáveis aleatórias binomiais, que possuem dois parâmetros n e p. Uma variável aleatória binomial(n, p) é simplesmente a soma de n variáveis aleatórias independentes de bernoulli(p), cada uma das quais é igual a 1 com probabilidade p e 0 com probabilidade 1 - p:
def bernoulli(p:float) -> float:
"""Retorna 1 com probabilidade p e 0 com probabilidade 1-p."""
return 1 if random.random() < p else 0
def binomial(n:int, p:int) -> int:
"""Retorna a soma de n bernoulli(p)."""
return sum (bernoulli(p) for _ in range(n))
A média de uma variável de bernoulli(p) é p, e seu desvio padrão é √p (1 - p). O teorema do limite central diz que, como n fica grande, uma variável binomial(n, p) é aproximadamente uma variável aleatória normal com média μ = np e desvio padrão σ = √np (1 - p). Se planejarmos os dois, você poderá ver facilmente a semelhança:
from collections import Counter
def histograma_binomial(p:float, n:int, num_pontos:int) -> None:
"""Obtém os pontos de binomial(n,p) e plota em um histograma."""
dados = [binomial(n,p) for _ in range(num_pontos)]
# --- Gráfico de barras para mostrar as amostras binomiais --- #
histograma = Counter(dados)
plt.bar([x - 0.4 for x in histograma.keys()],
[v / num_pontos for v in histograma.values()],
0.8,
color='0.75')
mu = p * n
sigma = math.sqrt(n * p *(1 - p))
# --- Gráfico de linha para mostrar a aproximação normal --- #
xs = range(min(dados), max(dados)+ 1)
ys = [fcd_normal(i + 0.5, mu, sigma) - fcd_normal(i - 0.5, mu, sigma) for i in xs]
plt.plot(xs, ys)
plt.title('Distribuição binomial x Aproximação normal')
plt.show()
Por exemplo, quando você chama histograma_binomial(0,75, 100, 10000), você obtém o gráfico na Figura 4.
Figura 4: Saída da função histograma_binomial().
A moral dessa aproximação é que, se você quiser saber a probabilidade de que (digamos) que em uma moeda justa apareça mais de 60 caras em 100 lançamentos, você possa estimá-lo como a probabilidade de que um normal(50,5) seja maior que 60, o que é mais fácil do que calcular o FCD binomial(100,0,5). Embora na maioria dos aplicativos você provavelmente esteja usando software estatístico que calcule com prazer quaisquer probabilidades que desejar.
Para uma exploração adicional
O scipy.stats contém funções FDP e FCD para a maioria das distribuições de probabilidade populares.
Lembra de como, no final do Capítulo 4, eu disse que seria uma boa ideia estudar um livro de estatística? Também seria uma boa ideia estudar um livro de probabilidade. Um bom livro é o Grinstead and Snell’s Introduction to Probability de Charles M. Grinstead e J. Laurie Snell (American Mathematics Society).