statistics em Python: média, mediana e desvio padrão
Aprenda o módulo statistics do Python para calcular média, mediana, moda, quantis, variância, desvio padrão, correlação e regressão sem instalar pandas.
O módulo statistics do Python calcula média, mediana, moda, quantis, variância, desvio padrão, correlação e regressão linear sem instalar pandas ou NumPy. Ele é uma boa escolha para listas e outros iteráveis pequenos, scripts de automação, validações, CLIs e relatórios simples. Para tabelas grandes, agrupamentos e joins, pandas continua mais conveniente; para um cálculo descritivo direto, a biblioteca padrão costuma ser suficiente.
A recomendação prática é: use fmean para uma média em float, median quando valores extremos distorcem o centro, quantiles para faixas como quartis, stdev para uma amostra, pstdev para a população inteira e correlation apenas depois de conferir os dados. Este guia mostra as diferenças com um conjunto de pedidos fictícios de um pequeno e-commerce brasileiro.
Exemplo inicial: resumo de valores de pedidos
Considere valores em reais, já convertidos para float apenas para simplificar o exemplo:
from statistics import fmean, median, multimode
pedidos = [89.90, 120.00, 120.00, 149.90, 199.90, 249.90, 899.00]
print(f"Média: R$ {fmean(pedidos):.2f}")
print(f"Mediana: R$ {median(pedidos):.2f}")
print(f"Moda(s): {multimode(pedidos)}")
A média fica acima da mediana porque o pedido de R$ 899 puxa o resultado. Isso não torna a média errada: ela responde a uma pergunta diferente. A média distribui o total igualmente entre os pedidos; a mediana identifica o valor central depois da ordenação.
Em análises de ticket, salário, tempo de atendimento ou atraso de entrega, compare as duas medidas antes de resumir a realidade com um único número.
API essencial do statistics
| Função | Pergunta que responde | Observação |
|---|---|---|
mean(dados) | Qual é a média aritmética? | Pode preservar Decimal e Fraction |
fmean(dados) | Qual é a média em ponto flutuante? | Retorna float |
geometric_mean(dados) | Qual é a média de fatores multiplicativos? | Exige valores positivos |
harmonic_mean(dados) | Qual média usar para certas taxas? | Exige valores não negativos |
median(dados) | Qual é o valor central? | Robusta a extremos |
median_low / median_high | Qual elemento central real escolher? | Útil quando interpolar não faz sentido |
mode(dados) | Qual é o valor mais frequente? | Devolve uma única moda |
multimode(dados) | Quais são todos os valores mais frequentes? | Devolve uma lista |
quantiles(dados) | Quais pontos dividem os dados em faixas? | Quartis, decis e percentis aproximados |
variance / stdev | Qual é a dispersão da amostra? | Usam correção amostral |
pvariance / pstdev | Qual é a dispersão da população? | Tratam os dados como conjunto completo |
correlation(x, y) | As duas variáveis variam juntas linearmente? | Não demonstra causalidade |
linear_regression(x, y) | Qual reta aproxima a relação? | Devolve inclinação e intercepto |
As funções levantam statistics.StatisticsError quando a entrada não atende aos requisitos, por exemplo ao calcular uma média de lista vazia ou um desvio padrão amostral com apenas um valor.
mean ou fmean: qual usar?
As duas calculam média aritmética, mas têm uma diferença importante de tipos.
from decimal import Decimal
from statistics import fmean, mean
valores = [Decimal("10.10"), Decimal("20.20"), Decimal("30.30")]
media_decimal = mean(valores)
media_float = fmean(valores)
print(media_decimal, type(media_decimal)) # Decimal('20.20')
print(media_float, type(media_float)) # 20.2 <class 'float'>
Use fmean quando float for a representação esperada, como em telemetria, notas, latências e indicadores aproximados. Use mean com Decimal quando a aritmética decimal precisar ser preservada.
Para valores monetários, não construa Decimal a partir de um float já arredondado de forma binária. Prefira strings, inteiros em centavos ou valores vindos de uma fonte que preserve a representação decimal. O guia de Decimal para dinheiro explica esse cuidado em detalhes.
Média ponderada
Em algumas versões modernas do Python, fmean aceita pesos. Para manter o exemplo portável e deixar a fórmula explícita, você também pode calcular a média ponderada assim:
from statistics import fmean
notas = [7.0, 8.5, 9.0]
pesos = [2, 3, 5]
media_ponderada = sum(
nota * peso for nota, peso in zip(notas, pesos, strict=True)
) / sum(pesos)
print(fmean(notas)) # média simples
print(media_ponderada) # média ponderada
O parâmetro strict=True faz zip levantar erro se as listas tiverem tamanhos diferentes. Isso evita ignorar silenciosamente uma nota ou um peso.
Média, mediana e outliers
Um outlier é um valor muito distante da maior parte dos dados. Ele pode ser erro, fraude, caso raro legítimo ou apenas parte natural da distribuição. Não remova extremos automaticamente.
from statistics import fmean, median
minutos_atendimento = [4, 5, 5, 6, 7, 8, 42]
print(fmean(minutos_atendimento)) # 11.0
print(median(minutos_atendimento)) # 6
Dizer que o atendimento “leva 11 minutos” esconde que seis de sete casos terminaram em até oito minutos. Dizer apenas que a mediana é seis também esconde um atendimento de 42 minutos que talvez mereça investigação.
Um relatório mais honesto poderia informar:
- mediana de 6 minutos;
- média de 11 minutos;
- maior tempo de 42 minutos;
- 6 de 7 atendimentos concluídos em até 8 minutos.
A estatística ajuda a descrever; o contexto decide o que precisa de ação.
median_low e median_high
Quando há quantidade par de itens, median interpola os dois centrais. Para categorias ordenadas ou valores indivisíveis, talvez você queira um elemento que exista na amostra:
from statistics import median, median_high, median_low
prazos_dias = [1, 2, 3, 8]
print(median(prazos_dias)) # 2.5
print(median_low(prazos_dias)) # 2
print(median_high(prazos_dias)) # 3
Use a variante de forma consciente e documente a regra. Escolher sempre o menor ou o maior centro pode influenciar indicadores operacionais.
mode e multimode: encontrando frequências
mode devolve um valor mais frequente. multimode devolve todos os valores empatados na frequência máxima, na ordem em que aparecem pela primeira vez.
from statistics import mode, multimode
formas_pagamento = [
"pix",
"cartao",
"pix",
"boleto",
"cartao",
]
print(mode(formas_pagamento)) # pix
print(multimode(formas_pagamento)) # ['pix', 'cartao']
Se empate for relevante, prefira multimode. Uma única moda pode induzir o relatório a declarar um vencedor onde existem duas categorias igualmente frequentes.
Para contagens completas, collections.Counter é mais informativo:
from collections import Counter
contagem = Counter(formas_pagamento)
print(contagem.most_common())
O módulo statistics resume; Counter permite inspecionar toda a distribuição de categorias.
Quantis, quartis e percentis
Quantis dividem dados ordenados em intervalos. Com n=4, você obtém três pontos que delimitam quatro grupos; com n=10, nove pontos para decis; com n=100, 99 pontos associados a percentis.
from statistics import quantiles
tempos_ms = [82, 90, 95, 98, 105, 110, 118, 130, 145, 190, 260, 410]
q1, q2, q3 = quantiles(tempos_ms, n=4, method="inclusive")
print(f"Q1: {q1:.1f} ms")
print(f"Q2: {q2:.1f} ms")
print(f"Q3: {q3:.1f} ms")
O argumento method muda a forma de estimar os limites:
exclusiveé o padrão e trata a amostra como parte de uma população maior;inclusiveinclui os extremos como percentis 0 e 100 do conjunto observado.
Não existe um método universalmente correto para toda situação. Ferramentas diferentes podem produzir percentis ligeiramente diferentes. Ao comparar seu resultado com Excel, banco de dados, pandas ou plataforma de observabilidade, confira a definição usada por cada sistema.
Para uma lista curta, percentil 99 transmite uma precisão que os dados não sustentam. Em relatórios pequenos, mediana, quartis, mínimo e máximo costumam ser mais interpretáveis.
Variância e desvio padrão: amostra ou população?
O desvio padrão mede a dispersão dos valores em torno da média. Um resultado pequeno indica valores mais concentrados; um resultado grande indica maior variação. Ele usa a mesma unidade dos dados, ao contrário da variância, que fica na unidade ao quadrado.
A decisão principal é se seus dados representam toda a população de interesse ou apenas uma amostra.
from statistics import pstdev, pvariance, stdev, variance
entregas_dias = [1, 2, 2, 3, 4, 6]
print(f"Variância amostral: {variance(entregas_dias):.2f}")
print(f"Desvio amostral: {stdev(entregas_dias):.2f}")
print(f"Variância populacional: {pvariance(entregas_dias):.2f}")
print(f"Desvio populacional: {pstdev(entregas_dias):.2f}")
Use stdev e variance quando os registros são uma amostra usada para estimar um universo maior. Use pstdev e pvariance quando os valores fornecidos são o conjunto completo que você quer descrever — por exemplo, todos os chamados encerrados pelo time naquele dia, se a pergunta estiver limitada exatamente àquele dia.
Não escolha a função pelo número “mais bonito”. Escreva no relatório qual universo está sendo analisado.
Coeficiente de variação
Para comparar dispersões de métricas com escalas diferentes, às vezes se usa o coeficiente de variação:
from statistics import fmean, stdev
valores = [91, 95, 98, 103, 113]
media = fmean(valores)
cv = stdev(valores) / media * 100
print(f"Coeficiente de variação: {cv:.1f}%")
A interpretação exige cautela quando a média está próxima de zero ou os valores podem ser negativos. Nesse cenário, o coeficiente pode ficar instável ou pouco significativo.
Correlação não é causalidade
correlation calcula a correlação de Pearson por padrão, medindo associação linear entre duas sequências. O resultado vai de -1 a 1:
- perto de 1: associação linear positiva forte;
- perto de -1: associação linear negativa forte;
- perto de 0: pouca associação linear.
from statistics import correlation
investimento_reais = [100, 150, 200, 250, 300, 350]
pedidos = [8, 11, 13, 17, 18, 22]
r = correlation(investimento_reais, pedidos)
print(f"Correlação: {r:.3f}")
Uma correlação alta não prova que aumentar investimento causou os pedidos. Sazonalidade, promoções, preço, canal e outros fatores podem influenciar as duas variáveis. Antes de interpretar:
- confira se as listas estão alinhadas pelo mesmo período;
- trate valores ausentes sem deslocar pares;
- visualize os pontos;
- procure outliers;
- verifique se a relação parece linear;
- evite linguagem causal sem um desenho de estudo adequado.
Em Python moderno, correlation também pode oferecer método de Spearman conforme a versão utilizada. Se seu projeto precisa funcionar em versões variadas, confira a documentação da versão mínima declarada no pyproject.toml e cubra a chamada com testes.
Regressão linear simples
linear_regression ajusta uma reta do tipo y = slope * x + intercept:
from statistics import linear_regression
horas_estudo = [1, 2, 3, 4, 5, 6]
notas = [52, 58, 67, 71, 79, 86]
modelo = linear_regression(horas_estudo, notas)
print(f"Inclinação: {modelo.slope:.2f}")
print(f"Intercepto: {modelo.intercept:.2f}")
previsao_4h30 = modelo.slope * 4.5 + modelo.intercept
print(f"Estimativa para 4,5 horas: {previsao_4h30:.1f}")
O exemplo demonstra a API, não uma regra sobre aprendizado. Uma reta baseada em poucos pontos não controla qualidade do estudo, dificuldade da prova ou conhecimento prévio. Também é arriscado extrapolar muito além do intervalo observado: um modelo treinado entre uma e seis horas não justifica previsões para cinquenta horas.
Para modelagem, validação, múltiplas variáveis e métricas de erro, use bibliotecas apropriadas, como scikit-learn ou statsmodels. O statistics.linear_regression atende bem a verificações e análises lineares pequenas.
Limpando os dados antes do cálculo
As funções estatísticas não substituem validação. Dados vazios, texto inesperado, valores ausentes e NaN podem quebrar a execução ou produzir resultados difíceis de interpretar.
import math
from statistics import StatisticsError, fmean, median
def resumir(valores: list[float | None]) -> dict[str, float | int]:
limpos = [
valor
for valor in valores
if valor is not None and math.isfinite(valor)
]
if not limpos:
raise ValueError("Nenhum valor numérico finito foi informado")
return {
"quantidade": len(limpos),
"media": fmean(limpos),
"mediana": median(limpos),
"minimo": min(limpos),
"maximo": max(limpos),
}
try:
resumo = resumir([10.0, None, 12.5, float("nan"), 15.0])
except (ValueError, StatisticsError) as erro:
print(f"Não foi possível calcular: {erro}")
else:
print(resumo)
Não remova None, infinito ou NaN sem registrar quantos valores foram descartados. A ausência pode revelar falha de coleta. Em um relatório de produção, retorne também quantidade_recebida, quantidade_valida e quantidade_descartada.
Exemplo completo: resumo de um CSV de vendas
Este script usa apenas a biblioteca padrão. Ele lê um CSV, valida a coluna valor, calcula estatísticas e gera uma saída textual.
import csv
from decimal import Decimal, InvalidOperation
from pathlib import Path
from statistics import StatisticsError, mean, median, quantiles, stdev
def carregar_valores(caminho: Path) -> tuple[list[Decimal], list[str]]:
valores: list[Decimal] = []
erros: list[str] = []
with caminho.open(encoding="utf-8", newline="") as arquivo:
leitor = csv.DictReader(arquivo)
if "valor" not in (leitor.fieldnames or []):
raise ValueError("O CSV precisa ter a coluna 'valor'")
for numero_linha, linha in enumerate(leitor, start=2):
texto = (linha.get("valor") or "").strip().replace(",", ".")
try:
valor = Decimal(texto)
except InvalidOperation:
erros.append(f"linha {numero_linha}: valor inválido")
continue
if valor < 0:
erros.append(f"linha {numero_linha}: valor negativo")
continue
valores.append(valor)
return valores, erros
def gerar_resumo(valores: list[Decimal]) -> dict[str, Decimal | int]:
if len(valores) < 2:
raise ValueError("São necessários pelo menos dois valores válidos")
q1, _, q3 = quantiles(valores, n=4, method="inclusive")
return {
"quantidade": len(valores),
"media": mean(valores),
"mediana": median(valores),
"desvio_amostral": stdev(valores),
"q1": q1,
"q3": q3,
"minimo": min(valores),
"maximo": max(valores),
}
try:
valores, erros = carregar_valores(Path("vendas.csv"))
resumo = gerar_resumo(valores)
except (OSError, ValueError, StatisticsError) as erro:
raise SystemExit(f"Falha ao gerar relatório: {erro}") from erro
print("Resumo das vendas")
for nome, valor in resumo.items():
print(f"- {nome}: {valor}")
print(f"- linhas rejeitadas: {len(erros)}")
for erro in erros:
print(f" - {erro}")
Em uma aplicação real, confirme o separador decimal e o formato de origem. Substituir vírgula por ponto funciona em um CSV controlado, mas não interpreta corretamente todos os formatos monetários brasileiros, como 1.234,56. Defina um contrato de entrada em vez de tentar adivinhar qualquer representação.
Se o arquivo exige filtros, agrupamentos por loja e junções, migre para pandas. Para dominar o formato antes disso, veja leitura e escrita de CSV com Python.
Testando os cálculos
Testes evitam regressões em regras de resumo:
from decimal import Decimal
import pytest
def test_gerar_resumo():
valores = [
Decimal("10.00"),
Decimal("20.00"),
Decimal("30.00"),
Decimal("40.00"),
]
resumo = gerar_resumo(valores)
assert resumo["quantidade"] == 4
assert resumo["media"] == Decimal("25.00")
assert resumo["mediana"] == Decimal("25.00")
assert resumo["minimo"] == Decimal("10.00")
assert resumo["maximo"] == Decimal("40.00")
def test_exige_dois_valores():
with pytest.raises(ValueError, match="pelo menos dois"):
gerar_resumo([Decimal("10.00")])
Evite afirmar igualdade exata entre floats calculados quando o resultado envolve aproximação. Use pytest.approx nesses casos. Para estruturar a suíte, consulte o guia de testes com pytest.
Quando usar statistics, pandas ou NumPy
| Cenário | Escolha recomendada |
|---|---|
| Lista pequena dentro de um script | statistics |
| CLI sem dependências externas | statistics |
| Regra estatística em teste unitário | statistics |
| CSV com filtros e agrupamentos | pandas |
| Join entre várias tabelas | pandas |
| Vetores grandes e operações numéricas | NumPy |
| Modelagem estatística e inferência | statsmodels ou SciPy |
| Machine learning com validação | scikit-learn |
Começar com statistics reduz dependências e deixa a regra explícita. Migrar depois não é fracasso: é uma resposta ao crescimento da estrutura, do volume ou da complexidade.
Erros comuns
- Calcular média de uma lista vazia sem validar a entrada.
- Usar média para dados muito assimétricos sem mostrar mediana ou distribuição.
- Confundir amostra e população ao escolher
stdevoupstdev. - Apagar outliers automaticamente sem investigar sua origem.
- Misturar
floate dinheiro quandoDecimalseria mais apropriado. - Calcular percentis em amostras minúsculas e comunicar falsa precisão.
- Interpretar correlação como causa.
- Desalinhar pares de dados ao remover ausentes de
xeyseparadamente. - Ignorar a versão mínima do Python ao usar recursos recentes da API.
- Comparar resultados entre ferramentas sem conferir o método de quantis.
Checklist para um resumo confiável
- Defini a população ou amostra que quero descrever.
- Validei entradas vazias, ausentes, infinitas e inválidas.
- Registrei quantas linhas foram rejeitadas.
- Comparei média e mediana quando há possibilidade de extremos.
- Escolhi
stdevoupstdevde acordo com o desenho da análise. - Documentei o método usado para quantis.
- Mantive pares alinhados em correlação e regressão.
- Evitei linguagem causal baseada apenas em correlação.
- Usei
Decimalquando a precisão monetária exige isso. - Escrevi testes para entradas normais e casos-limite.
Perguntas frequentes
Preciso instalar o módulo statistics com pip?
Não. Ele faz parte da biblioteca padrão. Importe apenas as funções necessárias: from statistics import mean, median, stdev.
Qual é a diferença entre mean e fmean?
mean pode preservar tipos como Decimal e Fraction. fmean produz float e é uma escolha direta quando ponto flutuante atende à análise.
Quando usar média ou mediana?
Use média para representar a distribuição proporcional do total e mediana para identificar o centro com menor influência de extremos. Em salários, tickets e tempos, apresentar ambas costuma ser melhor.
Qual é a diferença entre stdev e pstdev?
stdev estima o desvio padrão de uma população a partir de uma amostra. pstdev descreve o conjunto fornecido como população completa.
statistics substitui pandas e NumPy?
Não. Ele resolve cálculos descritivos em iteráveis simples com ótima legibilidade e zero dependências. Pandas e NumPy atendem volumes, estruturas e operações mais complexas.
Próximo passo
Pegue um conjunto pequeno que você já usa — tempos de execução, notas, pedidos fictícios ou métricas de um projeto — e calcule quantidade, média, mediana, quartis, mínimo, máximo e desvio padrão. Depois escreva duas frases: o que os números mostram e o que eles não permitem concluir.
Se o objetivo é entrar em analytics, transforme o exercício em um repositório reproduzível e siga o guia de estágio em dados com Python. Para evoluir de listas para tabelas, continue pela introdução ao pandas e pela trilha de analista de dados com Python. A biblioteca padrão é um ótimo ponto de partida porque obriga você a entender a medida antes de esconder a lógica atrás de um dataframe.