Curso de Python / Limpar e analisar tabelas com pandas

pandas representa dados tabulares como um DataFrame e oferece operações de seleção, conversão e agregação. Separe leitura, validação, cálculo e gravação, mantendo visível quais valores foram rejeitados para interpretar corretamente os totais.

Escolher os tipos antes de analisar os dados

Um DataFrame é uma estrutura de linhas e colunas; uma Series representa uma sequência unidimensional, como uma coluna da tabela. A inferência automática de um CSV pode contrariar o contrato da aplicação: um ID como 0012 precisa ser lido como texto se os zeros iniciais forem significativos.

O exemplo lê todas as colunas como strings e só depois converte amount para um tipo numérico. pandas é um pacote externo, não um módulo da biblioteca padrão. Selecione o ambiente virtual do projeto e instale uma versão compatível com seu Python e sistema:

python -m pip install pandas

Confirme que o terminal usa o mesmo ambiente na instalação e na execução. Se ocorrer ModuleNotFoundError, verifique o interpretador selecionado antes de repetir a instalação em outro lugar. A documentação de pandas.read_csv detalha tipos de colunas e opções de leitura.

Executar uma validação e um resumo completos

Salve o programa abaixo como pandas_demo.py, em UTF-8, em uma pasta de prática. Execute python pandas_demo.py com o ambiente selecionado. No macOS ou Ubuntu, fora de um ambiente virtual, use python3 se esse for o comando do interpretador.

from io import StringIO
import pandas as pd

raw = "id,category,amount\ne1,food,3500\ne2,food,8000\ne3,transport,2500\ne4,food,bad\n"
df = pd.read_csv(StringIO(raw), dtype="string", keep_default_na=False)
amounts = pd.to_numeric(df["amount"], errors="coerce")
valid = amounts.notna() & (amounts >= 0) & (amounts % 1 == 0)
clean = df.loc[valid].copy()
clean["amount"] = amounts.loc[valid].astype("int64")
report = clean.groupby("category", as_index=False, sort=True)["amount"].sum()
print("accepted:", len(clean), "rejected:", int((~valid).sum()))
for row in report.itertuples(index=False):
    print(row.category, row.amount)

Resultado esperado:

accepted: 3 rejected: 1
food 11500
transport 2500

StringIO apresenta o texto da amostra como um arquivo em memória. keep_default_na=False evita transformar automaticamente certas strings em valores ausentes durante essa leitura. A conversão seguinte marca bad como ausente; a máscara valid exige um número presente, não negativo e sem parte fracionária.

df.loc[valid] seleciona os registros aceitos, e copy() cria uma tabela separada para receber a coluna convertida. O agrupamento soma as quantias por categoria e ordena as chaves. as_index=False mantém a categoria como coluna no relatório; itertuples(index=False) permite percorrer suas linhas sem incluir o índice.

Interpretar coerção, ausências e o alcance da máscara

errors="coerce" em to_numeric transforma falhas de conversão em valores ausentes. Isso permite identificar a rejeição, não tratá-la como se o registro nunca tivesse existido. Compare len(clean), a contagem de rejeitados e a quantidade original de linhas. A referência de to_numeric também alerta sobre limites e precisão em valores muito grandes.

O critério do exemplo é numérico: uma representação como 3.0 pode ser aceita porque seu valor não tem parte fracionária, enquanto 3.5 é excluído. Isso difere da regra textual de aceitar somente algarismos usada no projeto CSV com a biblioteca padrão. Escolha deliberadamente qual contrato atende à sua entrada.

Esta prática pressupõe quantias pequenas e resultados que cabem em int64. Grandes inteiros, somas muito grandes e valores monetários decimais exatos exigem uma política própria para precisão, intervalo e unidade. A conversão e o tipo escolhido não oferecem uma garantia universal de exatidão para qualquer quantia.

A máscara verifica somente o valor numérico: ela não valida ID vazio, ID repetido ou categoria ausente. Ao ler um arquivo real, substitua a criação de df por pd.read_csv("expenses-input.csv", dtype={"id":"string", "category":"string", "amount":"string"}, keep_default_na=False) para manter a mesma política de leitura. Adicione as regras de identidade e categoria de que a aplicação necessita. Se optar pelo reconhecimento padrão de valores ausentes, reavalie também o agrupamento: categorias ausentes podem não aparecer nos grupos padrão.

Para investigar os descartes, retenha separadamente os registros de df.loc[~valid].copy() antes de distribuir o relatório. Não preencha quantias ausentes com zero sem uma regra explícita; “quantia desconhecida” e “quantia igual a zero” têm significados diferentes.

Gravar e reconciliar o relatório

Acrescente esta linha ao final de pandas_demo.py, depois de criar e imprimir o relatório:

report.to_csv("category-report.csv", index=False, encoding="utf-8")

Use uma pasta ou caminho de saída novo: esta chamada substitui um arquivo existente com o mesmo nome. index=False evita gravar o índice do DataFrame como uma coluna extra; encoding="utf-8" define a codificação textual. Veja as opções de DataFrame.to_csv.

Abra category-report.csv e confira o cabeçalho category,amount e os dois totais: food 11500 e transport 2500. O total dos registros aceitos e a soma do relatório devem ser 14000. Conferir somente uma soma correta não revela por si só se uma categoria ficou ausente ou um ID foi duplicado.

  1. Troque bad por 0. Devem aparecer quatro aceitos e nenhum rejeitado, mantendo o total 14000.
  2. Troque o mesmo campo por -1. Ele deve voltar a ser rejeitado: três aceitos e um rejeitado.
  3. Experimente 3.5 para observar a condição de parte fracionária e 3.0 para compará-la com um valor inteiro representado em decimal.
  4. Restaure bad e confirme novamente a saída original e a relação três aceitos + um rejeitado = quatro registros de entrada. Faça as gravações de teste em um caminho descartável.