Bem-vindo à aula 94 do nosso curso de Python! Hoje vamos colocar em prática tudo o que você aprendeu sobre manipulação de arquivos, estruturas de dados e estatística, construindo um projeto completo de análise de dados simples. O objetivo é criar um script que leia dados de um arquivo (CSV ou JSON), realize agregações, calcule estatísticas básicas e gere uma saída clara e organizada.

Este projeto é um marco importante porque integra várias habilidades: lidar com formatos de dados do mundo real, usar bibliotecas padrão do Python e pensar como um analista de dados. Vamos começar do zero, entendendo cada etapa, para que você possa adaptar o código aos seus próprios projetos futuros.

Lendo CSV/JSON

O primeiro passo em qualquer análise de dados é carregar os dados para dentro do programa. Python oferece módulos nativos para trabalhar com CSV e JSON, que são formatos extremamente comuns para armazenar dados tabulares e estruturados.

Para CSV, usamos o módulo csv. Ele permite ler e escrever arquivos onde os valores são separados por vírgulas (ou outros delimitadores). Para JSON, usamos o módulo json, que converte dados JSON em estruturas Python (listas, dicionários, etc.). Vamos ver como fazer isso de forma robusta, tratando possíveis erros de arquivo não encontrado ou formato inválido.

Exemplo de leitura de CSV:

import csv

def ler_csv(caminho):
    """Lê um arquivo CSV e retorna uma lista de dicionários."""
    try:
        with open(caminho, 'r', encoding='utf-8') as arquivo:
            leitor = csv.DictReader(arquivo)
            return list(leitor)
    except FileNotFoundError:
        print(f"Erro: arquivo não encontrado: {caminho}")
        return []
    except Exception as e:
        print(f"Erro ao ler CSV: {e}")
        return []

Para JSON, a leitura é ainda mais simples:

import json

def ler_json(caminho):
    """Lê um arquivo JSON e retorna os dados (geralmente uma lista ou dicionário)."""
    try:
        with open(caminho, 'r', encoding='utf-8') as arquivo:
            dados = json.load(arquivo)
            return dados
    except FileNotFoundError:
        print(f"Erro: arquivo não encontrado: {caminho}")
        return []
    except json.JSONDecodeError as e:
        print(f"Erro: JSON inválido: {e}")
        return []
    except Exception as e:
        print(f"Erro ao ler JSON: {e}")
        return []

No nosso projeto, vamos criar uma função que detecta a extensão do arquivo e chama o leitor adequado. Isso torna o código mais flexível e reutilizável.

Agregação

Agregação é o processo de combinar dados para obter informações resumidas. Por exemplo, calcular a soma, a média, o valor máximo ou mínimo de uma coluna, ou agrupar dados por categorias. Em Python, podemos fazer isso manualmente com laços e dicionários, ou usar a biblioteca statistics para algumas funções.

Vamos supor que temos um conjunto de dados de vendas com campos como 'produto', 'quantidade' e 'preco'. Uma agregação comum é calcular o total de vendas por produto. Podemos fazer isso com um dicionário:

def agregar_por_produto(dados):
    """Agrega a quantidade e o total vendido por produto."""
    total_por_produto = {}
    for registro in dados:
        produto = registro['produto']
        quantidade = int(registro['quantidade'])
        preco = float(registro['preco'])
        total = quantidade * preco
        if produto not in total_por_produto:
            total_por_produto[produto] = {'quantidade': 0, 'total': 0.0}
        total_por_produto[produto]['quantidade'] += quantidade
        total_por_produto[produto]['total'] += total
    return total_por_produto

Outra agregação útil é contar a frequência de valores em uma coluna, por exemplo, quantos registros existem para cada categoria. Isso pode ser feito com collections.Counter:

from collections import Counter

def contar_por_categoria(dados, campo):
    """Conta a frequência de valores em um campo específico."""
    valores = [registro[campo] for registro in dados if campo in registro]
    return Counter(valores)

Essas funções fornecem a base para análises mais complexas. No projeto, vamos combinar várias agregações para responder perguntas sobre os dados.

Estatísticas básicas

Estatísticas básicas incluem média, mediana, moda, desvio padrão, variância, valores mínimos e máximos. Python tem o módulo statistics que implementa essas funções de forma eficiente.

Aqui estão algumas funções essenciais:

  • statistics.mean(dados) – média aritmética
  • statistics.median(dados) – mediana
  • statistics.mode(dados) – moda (o valor mais frequente)
  • statistics.stdev(dados) – desvio padrão amostral
  • statistics.variance(dados) – variância amostral
  • min(dados) e max(dados) – mínimos e máximos

Vamos criar uma função que calcula um resumo estatístico de uma lista de números:

import statistics

def resumo_estatistico(valores):
    """Retorna um dicionário com estatísticas básicas de uma lista de números."""
    if not valores:
        return {}
    return {
        'média': statistics.mean(valores),
        'mediana': statistics.median(valores),
        'moda': statistics.mode(valores) if len(valores) > 1 else valores[0],
        'desvio_padrão': statistics.stdev(valores) if len(valores) > 1 else 0,
        'mínimo': min(valores),
        'máximo': max(valores),
        'total': sum(valores)
    }

Note que statistics.mode pode lançar uma exceção se não houver uma moda única, então tratamos isso de forma simples. No projeto, vamos aplicar essas funções a colunas numéricas dos dados.

Saída

A saída dos resultados é tão importante quanto os cálculos. Uma boa saída deve ser clara, organizada e legível. Podemos usar print com formatação, criar tabelas simples com tabulate (biblioteca externa) ou gerar um relatório em texto.

Vamos criar funções para exibir os resultados de forma amigável. Por exemplo, para mostrar um dicionário de agregação:

def exibir_agregacao(agregado):
    """Exibe uma agregação no formato chave: valor."""
    print("\n--- Agregação ---")
    for chave, valor in agregado.items():
        print(f"{chave}: {valor}")

Para estatísticas, podemos criar uma saída mais detalhada:

def exibir_estatisticas(estatisticas):
    """Exibe um dicionário de estatísticas de forma legível."""
    print("\n--- Estatísticas ---")
    for nome, valor in estatisticas.items():
        print(f"{nome}: {valor:.2f}" if isinstance(valor, float) else f"{nome}: {valor}")

Além disso, podemos salvar a saída em um arquivo texto para reutilização. Usamos open no modo 'w' e escrevemos as linhas.

def salvar_relatorio(nome_arquivo, conteudo):
    """Salva um texto em um arquivo."""
    with open(nome_arquivo, 'w', encoding='utf-8') as arquivo:
        arquivo.write(conteudo)
    print(f"Relatório salvo em {nome_arquivo}")

No projeto final, vamos combinar tudo em um script principal que lê os dados, processa e exibe/salva os resultados.

Projeto completo

Agora vamos montar o projeto completo. Vamos criar um script que aceita um arquivo (CSV ou JSON) e realiza as seguintes tarefas:

  1. Ler os dados do arquivo.
  2. Exibir as primeiras linhas (para inspeção).
  3. Calcular estatísticas básicas de colunas numéricas.
  4. Agregar por uma coluna categórica (ex.: produto).
  5. Exibir os resultados e salvar em um relatório.

Vamos escrever o código passo a passo.

import csv
import json
import statistics
from collections import Counter
import os

def ler_csv(caminho):
    try:
        with open(caminho, 'r', encoding='utf-8') as f:
            return list(csv.DictReader(f))
    except FileNotFoundError:
        print(f"Erro: arquivo não encontrado: {caminho}")
    except Exception as e:
        print(f"Erro ao ler CSV: {e}")
    return []

def ler_json(caminho):
    try:
        with open(caminho, 'r', encoding='utf-8') as f:
            return json.load(f)
    except FileNotFoundError:
        print(f"Erro: arquivo não encontrado: {caminho}")
    except json.JSONDecodeError as e:
        print(f"Erro: JSON inválido: {e}")
    except Exception as e:
        print(f"Erro ao ler JSON: {e}")
    return []

def carregar_dados(caminho):
    """Carrega dados de um arquivo CSV ou JSON."""
    if not os.path.exists(caminho):
        print(f"Erro: arquivo {caminho} não existe.")
        return []
    if caminho.endswith('.csv'):
        return ler_csv(caminho)
    elif caminho.endswith('.json'):
        return ler_json(caminho)
    else:
        print("Formato não suportado. Use .csv ou .json")
        return []

def resumo_estatistico(valores):
    if not valores:
        return {}
    return {
        'média': statistics.mean(valores),
        'mediana': statistics.median(valores),
        'moda': statistics.mode(valores) if len(valores) > 1 else valores[0],
        'desvio_padrão': statistics.stdev(valores) if len(valores) > 1 else 0,
        'mínimo': min(valores),
        'máximo': max(valores),
        'total': sum(valores)
    }

def agregar_por_campo(dados, campo_agrupador, campo_valor, operacao='soma'):
    """Agrega valores de um campo numérico por um campo categórico."""
    agregado = {}
    for registro in dados:
        grupo = registro.get(campo_agrupador)
        valor = registro.get(campo_valor)
        if grupo is None or valor is None:
            continue
        try:
            valor = float(valor)
        except ValueError:
            continue
        if grupo not in agregado:
            agregado[grupo] = []
        agregado[grupo].append(valor)
    resultado = {}
    for grupo, valores in agregado.items():
        if operacao == 'soma':
            resultado[grupo] = sum(valores)
        elif operacao == 'media':
            resultado[grupo] = sum(valores) / len(valores)
        elif operacao == 'contagem':
            resultado[grupo] = len(valores)
        elif operacao == 'max':
            resultado[grupo] = max(valores)
        elif operacao == 'min':
            resultado[grupo] = min(valores)
    return resultado

def exibir_dados(dados, n=5):
    """Exibe as primeiras n linhas dos dados."""
    if not dados:
        print("Nenhum dado para exibir.")
        return
    print(f"Primeiras {n} linhas:")
    for i, linha in enumerate(dados[:n]):
        print(f"{i+1}: {linha}")

def exibir_estatisticas(estatisticas):
    print("\n=== Estatísticas da coluna 'preco' ===")
    for chave, valor in estatisticas.items():
        if isinstance(valor, float):
            print(f"{chave}: {valor:.2f}")
        else:
            print(f"{chave}: {valor}")

def exibir_agregacao(agregado):
    print("\n=== Total de vendas por produto ===")
    for produto, total in agregado.items():
        print(f"{produto}: R$ {total:.2f}")

def principal(caminho_arquivo):
    dados = carregar_dados(caminho_arquivo)
    if not dados:
        return
    exibir_dados(dados)
    # Supondo que temos colunas 'produto', 'quantidade', 'preco'
    precos = []
    for linha in dados:
        try:
            precos.append(float(linha['preco']))
        except (KeyError, ValueError):
            continue
    estat = resumo_estatistico(precos)
    exibir_estatisticas(estat)
    agregado = agregar_por_campo(dados, 'produto', 'preco', 'soma')
    # Para soma, precisamos multiplicar pela quantidade? Vamos fazer simples: soma apenas preco
    # Mas para um exemplo real, faríamos quantidade*preco.
    # Vamos ajustar: usar uma função dedicada.
    exibir_agregacao(agregado)
    # Salvar relatório
    relatorio = f"Relatório de análise\nArquivo: {caminho_arquivo}\n"
    relatorio += f"Média de preço: {estat['média']:.2f}\n"
    relatorio += "\nAgregação por produto:\n"
    for prod, total in agregado.items():
        relatorio += f"{prod}: R$ {total:.2f}\n"
    with open("relatorio.txt", "w", encoding='utf-8') as f:
        f.write(relatorio)
    print("\nRelatório salvo em relatorio.txt")

if __name__ == "__main__":
    import sys
    if len(sys.argv) != 2:
        print("Uso: python analise.py ")
    else:
        principal(sys.argv[1])

Este código é um esqueleto; você pode adaptá-lo conforme seus dados. Note que a agregação de vendas por produto deve multiplicar quantidade por preço. Vamos corrigir isso nos exercícios.

Boas práticas

Ao trabalhar com análise de dados, algumas boas práticas são essenciais:

  • Valide os dados antes de processar: verifique se os campos existem e são do tipo esperado.
  • Trate erros com try/except para evitar que o programa quebre com dados inesperados.
  • Documente suas funções com docstrings para facilitar a manutenção.
  • Separe a lógica de leitura, processamento e saída em funções, tornando o código modular e testável.
  • Use nomes de variáveis descritivos para tornar o código legível.

Essas práticas não só ajudam você agora, mas também em projetos maiores.

Conclusão

Nesta aula, você construiu um projeto completo de análise de dados simples em Python. Você aprendeu a ler arquivos CSV e JSON, realizar agregações, calcular estatísticas básicas e gerar saídas formatadas. Esse conhecimento é a base para ferramentas mais avançadas como pandas e NumPy. Continue praticando com seus próprios conjuntos de dados para dominar essas técnicas.

Exercícios

  1. Exercício 1: Escreva uma função que leia um arquivo CSV e retorne o número de linhas (ignorando o cabeçalho). Teste com um arquivo de exemplo.

✓ Resposta:
def contar_linhas_csv(caminho):
    import csv
    with open(caminho, 'r', encoding='utf-8') as arquivo:
        leitor = csv.reader(arquivo)
        next(leitor)  # pula cabeçalho
        return sum(1 for _ in leitor)
  • Exercício 2: Dada uma lista de números, calcule a mediana sem usar o módulo statistics. Implemente manualmente.
  • ✓ Resposta:
    def mediana_manual(lista):
        ordenada = sorted(lista)
        n = len(ordenada)
        if n % 2 == 1:
            return ordenada[n//2]
        else:
            meio1 = ordenada[n//2 - 1]
            meio2 = ordenada[n//2]
            return (meio1 + meio2) / 2
  • Exercício 3: Crie uma função que agrupe uma lista de dicionários por um campo e retorne a contagem de itens em cada grupo.
  • ✓ Resposta:
    def contar_por_grupo(dados, campo):
        contagem = {}
        for item in dados:
            chave = item.get(campo)
            if chave is None:
                continue
            contagem[chave] = contagem.get(chave, 0) + 1
        return contagem
  • Exercício 4: Escreva um script que leia um arquivo JSON contendo uma lista de produtos (com 'nome' e 'preco') e exiba o produto mais caro e o mais barato.
  • ✓ Resposta:
    import json
    
    def principal():
        with open('produtos.json', 'r', encoding='utf-8') as f:
            produtos = json.load(f)
        mais_caro = max(produtos, key=lambda p: p['preco'])
        mais_barato = min(produtos, key=lambda p: p['preco'])
        print(f"Mais caro: {mais_caro['nome']} - R$ {mais_caro['preco']:.2f}")
        print(f"Mais barato: {mais_barato['nome']} - R$ {mais_barato['preco']:.2f}")
    
    if __name__ == '__main__':
        principal()
  • Exercício 5: Modifique a função de agregação do projeto para calcular o total de vendas por produto multiplicando quantidade pelo preço. Crie um exemplo de dados e teste.
  • ✓ Resposta:
    def total_vendas_por_produto(dados):
        totais = {}
        for reg in dados:
            produto = reg['produto']
            qtd = int(reg['quantidade'])
            preco = float(reg['preco'])
            total = qtd * preco
            totais[produto] = totais.get(produto, 0) + total
        return totais
    
    # Exemplo de uso:
    dados = [
        {'produto': 'A', 'quantidade': 2, 'preco': 10.0},
        {'produto': 'B', 'quantidade': 1, 'preco': 20.0},
        {'produto': 'A', 'quantidade': 3, 'preco': 10.0}
    ]
    print(total_vendas_por_produto(dados))  # {'A': 50.0, 'B': 20.0}

    Referências