Capítulo 22, Intermediário
Estatística: percentis, histogramas e correlação
Resumir um conjunto de dados em poucos números, e descobrir se duas coisas variam juntas. O NumPy traz o essencial; o resto você encontra no pandas e no SciPy.
Código deste capítulo: intermediario/cap22_estatistica.py
Correlação entre duas variáveis
A correlação mede o quanto duas variáveis andam juntas, de -1 (andam em sentidos opostos) a +1 (andam juntas), com 0 significando que não há relação linear. O np.corrcoef devolve a matriz de correlações, e a diagonal é sempre 1 (cada variável com ela mesma):
import numpy as np
rng = np.random.default_rng(42)
altura = rng.normal(170, 10, 500)
peso = 0.9 * altura - 85 + rng.normal(0, 5, 500)
matriz = np.corrcoef(altura, peso)
print(matriz.round(2))
print(0.8 < matriz[0, 1] < 0.95)
[[1. 0.86]
[0.86 1. ]]
True
Eu construí o peso a partir da altura mais um ruído, então a correlação alta é esperada (perto de 0,87, em teoria). Correlação não é causa, e dois números podem andar juntos por coincidência ou porque um terceiro fator move os dois.
Histogramas
O np.histogram conta quantos valores caem em cada faixa. Você escolhe as faixas, com um número de faixas iguais ou com os limites exatos. Cada faixa inclui o limite esquerdo e exclui o direito (menos a última, que inclui os dois):
notas = np.array([1, 2, 2, 3, 3, 3, 4, 4, 5])
contagens, limites = np.histogram(notas, bins=[0, 2, 4, 6])
print(contagens, limites)
contagens, limites = np.histogram(altura, bins=5)
print(contagens.sum(), len(limites))
[1 5 3] [0 2 4 6]
500 6
O número de limites é sempre um a mais que o de faixas, e a soma das contagens é o total de valores.
Variações ao longo do tempo
O np.diff calcula a diferença entre elementos consecutivos, e dividi-la pelo valor anterior dá a variação percentual. O np.cumsum acumula uma soma ao longo do array:
precos = np.array([100.0, 102.0, 99.0, 105.0])
retornos = np.diff(precos) / precos[:-1]
print(retornos.round(4), np.cumsum([1, 2, 3, 4]))
[ 0.02 -0.0294 0.0606] [ 1 3 6 10]
O resultado tem um elemento a menos que a entrada, porque o primeiro preço não tem anterior.
Agrupar com `bincount`
O np.bincount conta ocorrências de cada inteiro, e com o parâmetro weights soma valores por grupo. É um GROUP BY do SQL em uma linha, para categorias numeradas de 0 em diante:
categorias = np.array([0, 1, 1, 2, 2, 2])
valores = np.array([10, 20, 30, 1, 2, 3])
print(np.bincount(categorias), np.bincount(categorias, weights=valores))
[1 2 3] [10. 50. 6.]
Detectar valores atípicos (outliers)
Uma regra clássica usa o intervalo interquartil (IQR, a distância entre o quartil de 25% e o de 75%): é atípico o que passa de 1,5 IQR além dos quartis. Ela resiste a extremos, ao contrário de uma regra baseada na média:
dados = np.array([10, 12, 11, 13, 12, 95])
q1, q3 = np.percentile(dados, [25, 75])
iqr = q3 - q1
limite = q3 + 1.5 * iqr
print(dados[dados > limite])
[95]
Média e desvio dependem dos extremos
O valor 95 desloca muito a média e o desvio padrão deste conjunto. Por isso, para achar outliers e para descrever dados assimétricos, eu prefiro mediana e quartis. O capítulo 14 mostrou o mesmo com salários.
Exercício 1
Detectar outliers dos dois lados
Escreva outliers(x, k=1.5) que devolva os valores abaixo de Q1 - k*IQR ou acima de Q3 + k*IQR.