Great Expectations com Python: Testes de Qualidade de Dados
Aprenda a usar Great Expectations com Python para testar qualidade de dados em CSV, Pandas e pipelines, com exemplos práticos para projetos no Brasil.
Qualidade de dados é uma das diferenças mais claras entre um script que roda uma vez e um projeto Python pronto para trabalho real. Em uma planilha pequena, talvez você perceba manualmente que uma coluna veio vazia, que uma data está no formato errado ou que um valor de venda ficou negativo. Em um pipeline recorrente, esse tipo de erro passa silenciosamente para dashboards, modelos, relatórios financeiros, automações de CRM e decisões de negócio.
O problema não é apenas técnico. Quem busca vaga de análise, engenharia de dados, QA de dados ou automação com Python precisa mostrar que sabe transformar dados em confiança. Um projeto que só lê CSV e gera gráfico é útil; um projeto que valida contrato de entrada, bloqueia dados suspeitos e deixa evidência do que foi testado comunica maturidade profissional.
O Great Expectations é uma das ferramentas mais conhecidas para esse trabalho. Ele permite declarar expectativas sobre dados: colunas obrigatórias, tipos, faixas, valores permitidos, unicidade, ausência de nulos, formatos de texto e regras customizadas. Em vez de espalhar if solto pelo pipeline, você cria uma suíte de validações que pode rodar localmente, no CI ou antes de carregar dados em produção.
Neste guia, vamos usar Great Expectations com Pandas para validar uma base simples, conectar o fluxo com pytest, comparar com Pandera e mostrar como transformar o resultado em um projeto de portfólio voltado ao mercado brasileiro.
Quando usar Great Expectations
Use Great Expectations quando os dados têm um contrato que precisa ser verificado de forma repetível. Alguns exemplos comuns:
| Cenário | Expectativas úteis |
|---|---|
| CSV de vendas | pedido único, valor positivo, data válida, status permitido |
| Base de clientes | e-mail não nulo, CPF com formato esperado, cidade preenchida |
| Pipeline de vagas | título obrigatório, empresa obrigatória, URL única, salário coerente |
| Dados públicos | código IBGE válido, UF permitida, ano dentro do intervalo esperado |
| Analytics | eventos com nome conhecido, usuário anônimo presente, timestamp válido |
Se você só precisa validar um DataFrame pequeno dentro do próprio código Python, Pandera pode ser mais direto. Se quer documentação de qualidade de dados, suítes reutilizáveis, relatórios de validação e uma ferramenta conhecida por times de dados, Great Expectations faz sentido.
O ponto principal é não tratar qualidade como etapa manual. Dados mudam. APIs quebram. Exportações de sistema ganham coluna nova. Alguém altera separador decimal. Uma validação automatizada transforma esse risco em sinal visível.
Instalando o ambiente
Crie um projeto simples:
uv init qualidade-dados-python
cd qualidade-dados-python
uv add great-expectations pandas pytest
Com pip, o equivalente seria:
python -m venv .venv
source .venv/bin/activate
pip install great-expectations pandas pytest
Monte esta estrutura:
qualidade-dados-python/
data/
vendas.csv
src/
validar_vendas.py
tests/
test_qualidade_vendas.py
O arquivo data/vendas.csv pode começar assim:
pedido_id,cliente_email,uf,valor,status,data_pedido
1001,[email protected],SP,129.90,pago,2026-05-20
1002,[email protected],MG,89.50,enviado,2026-05-21
1003,[email protected],PE,240.00,cancelado,2026-05-22
Essa base parece simples, mas já tem regras de negócio: pedido_id não deve repetir, uf precisa estar em uma lista conhecida, valor não pode ser negativo e status deve ficar dentro dos valores aceitos.
Validando um DataFrame com expectativas
Crie src/validar_vendas.py:
from pathlib import Path
import great_expectations as gx
import pandas as pd
UFS_BRASIL = [
"AC", "AL", "AP", "AM", "BA", "CE", "DF", "ES", "GO",
"MA", "MT", "MS", "MG", "PA", "PB", "PR", "PE", "PI",
"RJ", "RN", "RS", "RO", "RR", "SC", "SP", "SE", "TO",
]
def carregar_vendas(caminho: str | Path) -> pd.DataFrame:
return pd.read_csv(caminho, parse_dates=["data_pedido"])
def validar_vendas(df: pd.DataFrame) -> bool:
batch = gx.from_pandas(df)
batch.expect_table_columns_to_match_ordered_list(
["pedido_id", "cliente_email", "uf", "valor", "status", "data_pedido"]
)
batch.expect_column_values_to_be_unique("pedido_id")
batch.expect_column_values_to_not_be_null("cliente_email")
batch.expect_column_values_to_match_regex(
"cliente_email",
r"^[^@\s]+@[^@\s]+\.[^@\s]+$",
)
batch.expect_column_values_to_be_in_set("uf", UFS_BRASIL)
batch.expect_column_values_to_be_between("valor", min_value=0.01)
batch.expect_column_values_to_be_in_set("status", ["pago", "enviado", "cancelado"])
batch.expect_column_values_to_not_be_null("data_pedido")
resultado = batch.validate()
return bool(resultado.success)
if __name__ == "__main__":
vendas = carregar_vendas("data/vendas.csv")
if not validar_vendas(vendas):
raise SystemExit("Falha na validação de qualidade de dados")
print("Dados de vendas validados com sucesso")
O exemplo usa gx.from_pandas() para ficar didático. Em projetos maiores, você pode evoluir para Data Context, fontes de dados configuradas e checkpoints. Para começar, porém, o valor já aparece: as regras ficam explícitas e qualquer pessoa consegue entender o contrato da base.
O que cada expectativa protege
As expectativas não devem ser escolhidas por volume, e sim por risco. No exemplo acima:
expect_table_columns_to_match_ordered_listprotege o contrato de entrada. Se a exportação mudar nome ou ordem de coluna, o pipeline para cedo.expect_column_values_to_be_uniqueevita duplicar pedidos em métricas de receita.expect_column_values_to_not_be_nullimpede registros sem e-mail quando a comunicação com cliente depende desse campo.expect_column_values_to_match_regexcaptura e-mails claramente inválidos.expect_column_values_to_be_in_setemufimpede valores comoSao Paulo,SPouXX.expect_column_values_to_be_betweenemvalorevita vendas negativas ou zeradas entrando em indicadores.expect_column_values_to_be_in_setemstatusimpede que um status novo passe sem decisão explícita.
Essa leitura é importante para carreira. Em entrevistas, não basta dizer “usei Great Expectations”. Melhor é explicar qual risco cada validação reduz e qual decisão o time deve tomar quando ela falha.
Integrando com pytest
Para um projeto de portfólio, uma boa prática é colocar a validação no mesmo fluxo de testes. Crie tests/test_qualidade_vendas.py:
import pandas as pd
from src.validar_vendas import validar_vendas
def test_vendas_validas_passam_na_validacao():
df = pd.DataFrame(
{
"pedido_id": [1001, 1002],
"cliente_email": ["[email protected]", "[email protected]"],
"uf": ["SP", "MG"],
"valor": [129.90, 89.50],
"status": ["pago", "enviado"],
"data_pedido": pd.to_datetime(["2026-05-20", "2026-05-21"]),
}
)
assert validar_vendas(df) is True
def test_venda_com_valor_negativo_falha():
df = pd.DataFrame(
{
"pedido_id": [1001],
"cliente_email": ["[email protected]"],
"uf": ["SP"],
"valor": [-10.0],
"status": ["pago"],
"data_pedido": pd.to_datetime(["2026-05-20"]),
}
)
assert validar_vendas(df) is False
Agora rode:
pytest
Esse teste não substitui testes unitários de transformação, mas adiciona um nível de contrato para dados. Em um pipeline real, você pode rodar testes de função, validações de schema e validações de amostra antes de publicar a tabela final.
Usando em um pipeline simples
Um fluxo mínimo de ETL pode ficar assim:
from pathlib import Path
from src.validar_vendas import carregar_vendas, validar_vendas
def transformar(caminho_entrada: Path, caminho_saida: Path) -> None:
vendas = carregar_vendas(caminho_entrada)
if not validar_vendas(vendas):
raise ValueError("Base de vendas rejeitada pela validação")
resumo = (
vendas.groupby("uf", as_index=False)
.agg(receita=("valor", "sum"), pedidos=("pedido_id", "count"))
.sort_values("receita", ascending=False)
)
resumo.to_csv(caminho_saida, index=False)
O detalhe é a ordem: valide antes de transformar. Se dados ruins entram no agrupamento, você pode gerar relatório bonito com número errado. Em dados, erro silencioso é pior do que falha explícita.
Também vale registrar falhas em um arquivo separado. Por exemplo, se o pipeline recebe CSV de fornecedores, você pode salvar o lote rejeitado em data/rejeitados/ com timestamp e motivo. Isso ajuda a conversar com a área de negócio sem perder evidência.
Great Expectations ou Pandera?
A comparação mais útil é por contexto:
| Ferramenta | Melhor uso |
|---|---|
| Pandera | validação tipada dentro do código Python, especialmente com Pandas |
| Great Expectations | suítes de expectativas, documentação de qualidade e validações compartilháveis |
| pytest puro | regras muito específicas de função ou transformação |
| Pydantic | validação de objetos, configurações, APIs e dados de entrada estruturados |
Em um projeto real, elas podem conviver. Você pode usar Pydantic para validar payloads de API, Pandera para validar DataFrame intermediário, Great Expectations para validar contrato de tabela e pytest para garantir comportamento de funções.
Evite a armadilha de instalar ferramenta demais sem clareza. Para um primeiro projeto, escolha uma base de dados, escreva 8 a 12 expectativas relevantes, rode no CI e documente o que acontece quando a validação falha.
Exemplo com dados públicos brasileiros
Um bom projeto para currículo é validar um fluxo com dados públicos brasileiros. Você pode usar uma base de municípios, indicadores educacionais, dados de saúde, despesas públicas ou séries temporais. O README poderia explicar:
- fonte dos dados;
- frequência esperada de atualização;
- colunas obrigatórias;
- expectativas de qualidade;
- exemplos de falha;
- comando para rodar validação;
- saída gerada após aprovação.
Imagine uma base mensal de municípios:
codigo_ibge,municipio,uf,ano,mes,valor
3550308,São Paulo,SP,2026,5,1024.50
2611606,Recife,PE,2026,5,310.40
As expectativas poderiam validar que codigo_ibge tem sete dígitos, uf está no conjunto de estados, ano está entre 2000 e 2030, mes fica entre 1 e 12 e valor não é negativo. Esse tipo de projeto conversa bem com APIs públicas brasileiras com Python, ETL com Python, DuckDB e GeoPandas.
Para deixar o portfólio mais forte, inclua um caso de falha proposital. Um diretório examples/dados_invalidos.csv mostra que você pensou no caminho ruim, não apenas no demo perfeito.
Rodando no CI
Em GitHub Actions ou Gitea Actions, a validação pode rodar junto com os testes:
name: quality
on:
push:
pull_request:
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v5
- run: uv sync
- run: uv run pytest
- run: uv run python src/validar_vendas.py
Esse fluxo evita que uma mudança de dados ou código seja aceita sem passar pelo contrato mínimo. Em times maiores, você pode separar validações rápidas de PR e validações completas de agenda diária, porque bases grandes podem demorar.
Cuidados em produção
Algumas práticas evitam frustração:
- Comece por expectativas críticas. Não tente modelar 100% da realidade no primeiro dia.
- Defina severidade. Uma coluna opcional ausente pode gerar alerta; CPF inválido em base de cobrança talvez bloqueie carga.
- Versione expectativas junto com o código. Mudança de regra deve passar por revisão.
- Não valide apenas schema. Distribuição, faixas e cardinalidade também importam.
- Separe dado rejeitado de dado aprovado. Isso facilita auditoria.
- Monitore tendência de falhas. Se a mesma expectativa quebra toda semana, o contrato ou a fonte precisam ser revistos.
Também cuidado com dados pessoais. Se o projeto usa e-mail, CPF, telefone ou endereço, trabalhe com dados fictícios ou anonimizados. Para portfólio público, mostre estrutura e validação sem expor informação real.
Como apresentar no portfólio
Um bom README para esse tipo de projeto pode ter esta estrutura:
# Validação de qualidade de dados com Python
## Problema
Base de vendas mensal pode chegar com duplicidade, valores inválidos e estados incorretos.
## Solução
Pipeline em Python com Pandas, Great Expectations e pytest.
## Regras de qualidade
- pedido_id único
- valor maior que zero
- UF dentro da lista oficial
- status dentro do contrato
- e-mail com formato válido
## Como rodar
uv run pytest
uv run python src/validar_vendas.py
## Evidência
Inclui exemplo de base válida, base inválida e saída de validação.
Esse formato é mais convincente do que apenas listar tecnologias. Ele mostra problema, decisão, validação e evidência. Para vagas de dados, QA, engenharia analítica e automação, essa clareza pesa bastante.
Próximos passos
Depois do primeiro fluxo funcionando, evolua por etapas:
- Adicione validações para dados públicos brasileiros.
- Gere documentação HTML das expectativas.
- Rode validação em CI.
- Salve lotes rejeitados com timestamp.
- Publique um dashboard simples com apenas dados aprovados.
- Compare Great Expectations com Pandera no README.
Se o volume crescer, combine a validação com Parquet, DuckDB, Polars ou banco analítico. Quando a rotina precisar de agendamento, retries e histórico por etapa, conecte a validação a um orquestrador como Airflow com Python. Se o gargalo for ingestão em alta concorrência, uma arquitetura mista pode usar Go para coletar eventos e Python para validação, análise e ciência de dados.
Great Expectations não resolve qualidade de dados sozinho. A ferramenta só funciona quando você transforma conhecimento de negócio em contrato verificável. Esse é o ganho real: menos confiança cega em planilha, mais evidência automatizada antes de tomar decisão.
Equipe Python Brasil
Contribuidor do Python Brasil — Aprenda Python em Português