Distribuição Normal de Dados
Aprenda a distribuição normal (Gaussiana) em Python: PDF, CDF, Z-scores, regra empírica, testes de normalidade e quando usar transformações logarítmicas.
A distribuição normal (também chamada de distribuição Gaussiana) é a distribuição de probabilidade mais importante em estatística e aprendizado de máquina. Compreendê-la profundamente — não apenas reconhecer sua forma de sino, mas saber como medi-la, testá-la e trabalhar com ela em Python — oferece uma base sólida para preparação de dados, seleção de modelos e interpretação de resultados.
Este capítulo abrange:
- O que é a distribuição normal e por que sua forma importa
- A regra empírica (regra 68–95–99,7) e como verificá-la
- Como gerar dados normalmente distribuídos com NumPy
- Como avaliar a função de densidade de probabilidade (PDF) e a função de distribuição acumulada (CDF) com SciPy
- Como calcular e interpretar Z-scores
- Como testar se seus dados seguem uma distribuição normal
- O que fazer quando os dados não são normais
O Que É a Distribuição Normal?
Uma distribuição de probabilidade descreve a probabilidade de cada valor de um conjunto de dados aparecer. A distribuição normal é uma distribuição contínua, simétrica e em forma de sino, definida por dois parâmetros:
- Média (μ) — o centro do sino; onde fica o pico
- Desvio padrão (σ) — o quão largo ou estreito é o sino; um σ maior dispersa os valores mais longe do centro
Como a curva é simétrica em torno da média, a média, mediana e moda são todas iguais para uma distribuição perfeitamente normal.
A fórmula para a função de densidade de probabilidade é:
f(x) = (1 / (σ√(2π))) × e^(−(x−μ)² / (2σ²))Você não precisa aplicar essa fórmula manualmente — o stats.norm do SciPy cuida disso — mas é útil saber que a forma depende apenas de μ e σ.
A Regra Empírica (68–95–99,7)
Uma das propriedades mais práticas da distribuição normal é a regra empírica, também chamada de regra 68–95–99,7:
| Intervalo em relação à média | Percentagem de valores |
|---|---|
| Dentro de ±1 desvio padrão | ~68% |
| Dentro de ±2 desvios padrão | ~95% |
| Dentro de ±3 desvios padrão | ~99,7% |
Isso significa que em um conjunto de dados normalmente distribuído, quase todos os valores estão a três desvios padrão da média. Valores além de ±3σ são genuinamente raros — aproximadamente 1 em cada 370 observações.
O exemplo a seguir simula 10 000 valores de uma distribuição normal padrão (μ=0, σ=1) e verifica a regra:
import numpy as np
rng = np.random.default_rng(seed=42)
mu, sigma = 0, 1
data = rng.normal(loc=mu, scale=sigma, size=10000)
w1 = np.mean(np.abs(data - mu) < 1 * sigma) * 100
w2 = np.mean(np.abs(data - mu) < 2 * sigma) * 100
w3 = np.mean(np.abs(data - mu) < 3 * sigma) * 100
print(f"Within 1 std: {w1:.1f}% (expected ~68%)")
print(f"Within 2 std: {w2:.1f}% (expected ~95%)")
print(f"Within 3 std: {w3:.1f}% (expected ~99.7%)")Saída:
Within 1 std: 67.8% (expected ~68%)
Within 2 std: 95.4% (expected ~95%)
Within 3 std: 99.7% (expected ~99.7%)A regra empírica é imediatamente útil: se a pontuação de um teste está mais de dois desvios padrão acima da média, ela está no topo de ~2,5% de todas as pontuações.
Gerando Dados Normalmente Distribuídos com NumPy
O gerador de números aleatórios do NumPy produz amostras que se aproximam de uma distribuição normal. O método rng.normal() aceita a média (loc), o desvio padrão (scale) e o número de amostras (size):
import numpy as np
rng = np.random.default_rng(seed=7)
# Simulate IQ scores: mean=100, std=15
samples = rng.normal(loc=100, scale=15, size=1000)
print(f"Sample mean: {samples.mean():.1f}")
print(f"Sample std: {samples.std():.1f}")
print(f"Min: {samples.min():.1f}")
print(f"Max: {samples.max():.1f}")
print(f"Sample size: {len(samples)}")Saída:
Sample mean: 98.9
Sample std: 14.1
Min: 51.2
Max: 138.6
Sample size: 1000A média e o desvio padrão da amostra são próximos — mas não exatamente — de 100 e 15, porque uma amostra finita tem variação aleatória natural. Com 10 000 amostras, as estimativas convergem mais para os parâmetros verdadeiros.
Por que usar uma semente? Passar seed=7 para default_rng torna os resultados reproduzíveis. Qualquer pessoa que execute o mesmo código obtém os mesmos números, o que é essencial para depuração e compartilhamento de resultados. A interface default_rng (introduzida no NumPy 1.17) é preferível à antiga np.random.normal() porque evita o uso de estado global compartilhado.
Avaliando a PDF e a CDF com SciPy
Função de Densidade de Probabilidade (PDF)
A PDF informa a probabilidade relativa de um valor. Um valor de PDF mais alto em x significa que x é mais provável de ocorrer. Para uma distribuição normal, o pico está na média:
from scipy import stats
mu, sigma = 0, 1
x_values = [-2, -1, 0, 1, 2]
for x in x_values:
pdf = stats.norm.pdf(x, loc=mu, scale=sigma)
print(f" pdf({x:2d}) = {pdf:.4f}")Saída:
pdf(-2) = 0.0540
pdf(-1) = 0.2420
pdf( 0) = 0.3989
pdf( 1) = 0.2420
pdf( 2) = 0.0540A PDF é simétrica: pdf(-1) é igual a pdf(1), e pdf(0) é o máximo (o pico da curva em sino). O próprio valor da PDF não é uma probabilidade — é uma densidade. Para obter uma probabilidade, você integra a PDF em um intervalo, que é o que a CDF faz.
Função de Distribuição Acumulada (CDF)
A CDF em um valor x fornece a probabilidade de uma observação extraída aleatoriamente ser menor ou igual a x. Use stats.norm.cdf() para responder a perguntas práticas de probabilidade:
from scipy import stats
mu, sigma = 170, 10 # adult heights in cm
# Probability that a randomly chosen person is shorter than 180 cm
p_below_180 = stats.norm.cdf(180, loc=mu, scale=sigma)
print(f"P(height < 180 cm) = {p_below_180:.4f}")
# Probability of being taller than 185 cm
p_above_185 = 1 - stats.norm.cdf(185, loc=mu, scale=sigma)
print(f"P(height > 185 cm) = {p_above_185:.4f}")
# Probability of falling between 160 cm and 180 cm
p_range = stats.norm.cdf(180, loc=mu, scale=sigma) - stats.norm.cdf(160, loc=mu, scale=sigma)
print(f"P(160 < height < 180 cm) = {p_range:.4f}")Saída:
P(height < 180 cm) = 0.8413
P(height > 185 cm) = 0.0668
P(160 < height < 180 cm) = 0.6827O terceiro resultado confirma a regra empírica: o intervalo μ±σ (160–180 cm) contém cerca de 68% das observações.
Z-Scores: Padronizando a Distribuição
Um Z-score (também chamado de pontuação padrão) mede quantos desvios padrão um valor está da média:
Z = (x − μ) / σOs Z-scores permitem comparar valores de distribuições com médias e desvios padrão diferentes em uma escala comum. Um Z-score de +2.0 significa que um valor está dois desvios padrão acima da média, independentemente das unidades originais.
import numpy as np
from scipy import stats
heights = np.array([171.3, 168.7, 176.4, 171.0, 164.6, 173.6, 183.0, 179.5])
# Calculate Z-scores manually
mean = heights.mean()
std = heights.std(ddof=1) # ddof=1 for the sample standard deviation
z_manual = (heights - mean) / std
print("Z-scores (manual):", np.round(z_manual, 2))
# Or use scipy directly
z_scipy = stats.zscore(heights, ddof=1)
print("Z-scores (scipy): ", np.round(z_scipy, 2))Saída:
Z-scores (manual): [-0.37 -0.81 0.49 -0.42 -1.5 0.01 1.59 1.01]
Z-scores (scipy): [-0.37 -0.81 0.49 -0.42 -1.5 0.01 1.59 1.01]Uma pessoa com altura de 183,0 cm tem um Z-score de +1.59, o que significa que está 1,59 desvios padrão acima da média. Em uma distribuição normal, isso a coloca aproximadamente nos 6% superiores da população.
Quando os Z-scores são úteis
- Detecção de outliers: valores com
|Z| > 3são quase certamente outliers em uma distribuição normal. - Escalonamento de características: converter características em Z-scores (média zero, variância unitária) é chamado de padronização, e é exigido por algoritmos sensíveis à magnitude das características, como SVM e k-vizinhos mais próximos. Veja o capítulo sobre escalonamento de características para saber como aplicar isso com o
StandardScalerdo scikit-learn. - Comparar grandezas diferentes: se você quiser comparar a pontuação de matemática de um aluno (média 70, desvio padrão 10) com sua pontuação de leitura (média 50, desvio padrão 5), os Z-scores fornecem uma comparação justa.
Testando se os Dados Seguem uma Distribuição Normal
Antes de aplicar algoritmos que assumem normalidade, verifique a suposição. As duas abordagens mais comuns são o teste de Shapiro-Wilk (para amostras de até ~5 000) e uma rápida inspeção visual.
Teste de Shapiro-Wilk
O teste de Shapiro-Wilk retorna uma estatística W e um p-valor. Um p-valor acima de 0,05 significa que não há evidências suficientes para rejeitar a normalidade. Um p-valor em ou abaixo de 0,05 indica que é improvável que os dados sejam normais.
import numpy as np
from scipy import stats
rng = np.random.default_rng(seed=42)
# Normally distributed sample
normal_sample = rng.normal(loc=0, scale=1, size=50)
stat_n, p_n = stats.shapiro(normal_sample)
print(f"Normal sample — W={stat_n:.3f}, p={p_n:.3f}")
if p_n > 0.05:
print(" => Cannot reject normality.")
else:
print(" => Reject normality.")
# Skewed sample (exponential distribution)
skewed_sample = rng.exponential(scale=1, size=50)
stat_s, p_s = stats.shapiro(skewed_sample)
print(f"Skewed sample — W={stat_s:.3f}, p={p_s:.3f}")
if p_s > 0.05:
print(" => Cannot reject normality.")
else:
print(" => Reject normality.")Saída:
Normal sample — W=0.984, p=0.730
=> Cannot reject normality.
Skewed sample — W=0.808, p=0.000
=> Reject normality.A amostra normal é aprovada facilmente (p=0,730). A amostra exponencial falha decisivamente (p≈0).
Quando o teste de normalidade importa
Nem todo algoritmo precisa de características normalmente distribuídas. Modelos baseados em árvores (árvores de decisão, florestas aleatórias, gradient boosting) e redes neurais são completamente agnósticos em relação à distribuição. A normalidade é mais importante para:
- Testes estatísticos paramétricos (teste t, ANOVA, correlação de Pearson) — os p-valores desses testes só são válidos se os dados forem aproximadamente normais.
- Análise discriminante linear (LDA) — assume que cada classe é normalmente distribuída.
- Naive Bayes Gaussiano — modela explicitamente cada característica como uma Gaussiana.
- Intervalos de confiança e de previsão em regressão linear — derivados da normalidade dos resíduos.
Quando os Dados Não São Normais: Transformação Logarítmica
Um remédio comum para dados com assimetria à direita (por exemplo, renda, preços de imóveis, valores de transações) é a transformação logarítmica, que comprime valores grandes e expande valores pequenos. O resultado frequentemente se aproxima de uma distribuição normal:
import numpy as np
from scipy import stats
rng = np.random.default_rng(seed=7)
# Simulate log-normally distributed incomes (a realistic pattern)
incomes = rng.lognormal(mean=10.5, sigma=0.5, size=1000)
log_incomes = np.log(incomes)
sk_before = stats.skew(incomes)
sk_after = stats.skew(log_incomes)
stat_before, p_before = stats.shapiro(incomes[:50])
stat_after, p_after = stats.shapiro(log_incomes[:50])
print(f"Before transform — skewness: {sk_before:.2f}, Shapiro p={p_before:.3f}")
print(f"After log transform — skewness: {sk_after:.2f}, Shapiro p={p_after:.3f}")Saída:
Before transform — skewness: 1.44, Shapiro p=0.000
After log transform — skewness: 0.01, Shapiro p=0.940Após a transformação logarítmica, a assimetria cai de 1,44 para quase zero, e o p-valor do Shapiro-Wilk sobe de 0,000 para 0,940 — os dados transformados passam facilmente no teste de normalidade.
Atenção:
- A transformação logarítmica exige que todos os valores sejam estritamente positivos. Se seus dados contêm zeros, use
np.log(x + 1)(transformação log-mais-um). - Se seus dados têm assimetria à esquerda (cauda longa à esquerda), tente uma transformação quadrática ou de reflexão.
- Sempre aplique a mesma transformação tanto ao conjunto de treinamento quanto a quaisquer novos dados no momento da inferência.
Por Que a Distribuição Normal Importa no Aprendizado de Máquina
A distribuição normal aparece em todos os lugares no ML, frequentemente de forma implícita:
- Teorema Central do Limite: a média de uma grande amostra aleatória é aproximadamente normalmente distribuída, independentemente da distribuição original. Isso fundamenta intervalos de confiança e testes de hipóteses sobre métricas de modelos.
- Análise de resíduos: os resíduos de uma regressão linear bem ajustada devem ser aproximadamente normais. Desvios indicam má especificação do modelo.
- Inicialização de pesos em redes neurais: esquemas como a inicialização Xavier e He extraem pesos iniciais de distribuições normais para evitar gradientes que desaparecem ou explodem.
- Processos Gaussianos: uma família de modelos probabilísticos que colocam uma distribuição normal sobre funções, usados em otimização Bayesiana e quantificação de incerteza.
- Detecção de outliers: em muitos domínios, dados além de ±3σ da média são sinalizados como anomalias.
Entender onde a distribuição normal é assumida — e quando essa suposição se rompe — ajuda a evitar erros silenciosos de modelagem.
Resumo
- A distribuição normal é definida por sua média (μ) e desvio padrão (σ); sua curva em forma de sino é simétrica em torno da média.
- A regra empírica afirma que 68%, 95% e 99,7% dos valores estão dentro de 1, 2 e 3 desvios padrão da média.
- Use
numpy.random.default_rng().normal()para gerar amostras normalmente distribuídas escipy.stats.norm.pdf()/.cdf()para avaliar probabilidades. - Os Z-scores padronizam valores para uma escala comum; são essenciais para detecção de outliers e escalonamento de características.
- Use
scipy.stats.shapiro()para testar formalmente a normalidade — mas lembre-se de que muitos algoritmos modernos de ML não a exigem. - Quando os dados têm assimetria à direita, uma transformação logarítmica frequentemente os torna aproximadamente normais.
Para uma comparação mais ampla dos tipos de distribuição (uniforme, assimétrica, multimodal), consulte o capítulo sobre Distribuição de Dados. Para saber como medir dispersão e centro, veja Desvio Padrão e Média, Mediana e Moda.