Fase 2 — Wrangling avançado | Pré-requisitos: Aulas 0 a 12 | Tipo: projeto prático | Duração estimada: 4 a 6 horas
Introdução
Esta aula é o segundo projeto do curso. Ele consolida toda a Fase 2: coleta de múltiplas fontes, avaliação sistemática de qualidade, limpeza programática e documentada, e reconhecimento honesto de limitações e vieses.
O Projeto 1 trabalhou com um dataset já disponível, razoavelmente limpo. Este projeto é diferente: você vai coletar dados de pelo menos duas fontes diferentes, combiná-los, e documentar rigorosamente todo o processo de wrangling. O produto final continua sendo um Jupyter Notebook, mas agora a maior parte do trabalho está no processo — não na análise.
O que o projeto pede
O projeto tem três entregáveis dentro de um único notebook.
O primeiro é a coleta documentada de pelo menos dois datasets de fontes diferentes. Pelo menos uma das fontes deve ser algo diferente de um arquivo CSV já pronto — uma API, um banco de dados, dados coletados via scraping, ou um arquivo em formato não-tabular como JSON. As fontes devem ter alguma relação que permita combiná-las via merge ou concat.
O segundo é a avaliação completa de cada dataset antes da combinação, seguida da avaliação do dataset combinado. Cada problema identificado deve estar registrado com o quê é, quantas linhas afeta e qual a decisão tomada.
O terceiro é a limpeza programática e validada de todos os problemas identificados na avaliação, culminando num dataset final limpo salvo em arquivo. O notebook deve executar sem erros do início ao fim.
Ao final, uma seção de conclusões descreve o estado do dataset limpo, as decisões de limpeza mais importantes e as limitações conhecidas dos dados.
Escolhendo as fontes de dados
A escolha das fontes é a primeira decisão analítica do projeto. Algumas diretrizes.
As fontes devem ser relacionáveis. Elas precisam compartilhar pelo menos uma chave de junção — um identificador de município, um código de produto, um nome de país, um período de tempo. Sem isso você não pode combiná-las de forma significativa.
Pelo menos uma deve ter problemas reais de qualidade. O objetivo do projeto é praticar wrangling, não demonstrar que sabe carregar um CSV limpo. Fontes com problemas reais são melhores do que fontes artificialmente limpas.
As fontes devem ser publicamente acessíveis e gratuitas. Não use dados corporativos confidenciais num projeto de aprendizado.
Combinações sugeridas
Combinação 1: dados do IBGE via API + dados educacionais do INEP em CSV.
A API do IBGE fornece dados demográficos por município — população, PIB per capita, índices de desenvolvimento. O INEP disponibiliza microdados do ENEM, do Censo Escolar e do IDEB por município em formato CSV. Juntar as duas fontes pelo código de município permite análises como "municípios com maior PIB per capita têm melhor desempenho no IDEB?" ou "qual a relação entre tamanho da população e infraestrutura escolar?"
import requests
import pandas as pd
# Coletar dados de municípios via API do IBGE
resposta = requests.get(
'https://servicodados.ibge.gov.br/api/v1/localidades/municipios'
)
municipios = pd.json_normalize(resposta.json())
# Coletar PIB per capita via API do IBGE
resposta_pib = requests.get(
'https://servicodados.ibge.gov.br/api/v3/agregados/5938/periodos/2020/variaveis/37'
'?localidades=N6[all]'
)
# processar resposta...
# Carregar IDEB do CSV do INEP
ideb = pd.read_csv('ideb_municipios_2021.csv', sep=';', encoding='latin-1')
Combinação 2: dados de qualidade do ar via API + dados de saúde por município em CSV.
O INMET e outras fontes disponibilizam dados ambientais. O DATASUS disponibiliza dados de internações por causa. Juntar pelo código de município e período permite análises sobre impacto da qualidade do ar em saúde.
Combinação 3: dados de jogos da Série A via scraping ou CSV do Kaggle + dados climáticos via API.
Há datasets do Campeonato Brasileiro no Kaggle com resultados históricos. APIs meteorológicas como Open-Meteo fornecem dados históricos de clima por coordenada geográfica. Combinar pelo estádio e data permite analisar se condições climáticas afetam resultados.
Combinação 4: dados de eleições do TSE em CSV + dados socioeconômicos do IBGE via API.
O TSE disponibiliza resultados eleitorais por município. O IBGE tem indicadores socioeconômicos. Combinar os dois permite analisar como características socioeconômicas se relacionam com padrões de voto.
Combinação 5: dados de preços de imóveis em CSV + dados de infraestrutura por bairro via scraping ou API.
Portais como ZAP Imóveis têm dados públicos de anúncios. Dados de acesso a transporte, escolas e hospitais por região são disponibilizados por prefeituras em portais de dados abertos. Combinar os dois permite modelar o que explica a variação de preços.
Estrutura do notebook
Seção 1: contexto e objetivos
# Projeto 2 — Wrangling de dados reais
## Contexto
Descreva o tema, as duas fontes escolhidas e a pergunta motivadora
que justifica a combinação das duas. Por que faz sentido juntar
esses dados? O que a combinação permite responder que nenhuma fonte
sozinha conseguiria?
## Fontes
- **Fonte 1:** [nome], [origem], [período coberto], [formato]
- **Fonte 2:** [nome], [origem], [período coberto], [formato]
## Chave de junção
As duas fontes serão combinadas por [campo X], que representa [o quê].
Seção 2: coleta
Documente o processo de coleta de cada fonte, incluindo os parâmetros usados, o código completo e os prints de confirmação.
# ===========================
# COLETA — FONTE 1: IBGE API
# ===========================
import requests
import pandas as pd
import time
import json
print("Coletando municípios via API do IBGE...")
resposta = requests.get(
'https://servicodados.ibge.gov.br/api/v1/localidades/municipios',
timeout=30
)
resposta.raise_for_status()
municipios_raw = resposta.json()
# Salvar dados brutos antes de processar
with open('data/raw/municipios_ibge_raw.json', 'w', encoding='utf-8') as f:
json.dump(municipios_raw, f, ensure_ascii=False, indent=2)
df_municipios = pd.json_normalize(municipios_raw, sep='_')
print(f"Municípios coletados: {len(df_municipios):,}")
print(df_municipios.head())
# ================================
# COLETA — FONTE 2: CSV do INEP
# ================================
# Baixar arquivo se não existir localmente
import os
caminho_ideb = 'data/raw/divulgacao_regioes_ufs_municipios_ed_basica_2021.xlsx'
if not os.path.exists(caminho_ideb):
print("Arquivo IDEB não encontrado. Baixe em:")
print("https://www.gov.br/inep/pt-br/areas-de-atuacao/pesquisas-estatisticas-e-indicadores/ideb/resultados")
else:
df_ideb = pd.read_excel(caminho_ideb, sheet_name='Municípios', skiprows=9)
print(f"IDEB carregado: {df_ideb.shape}")
print(df_ideb.head())
Seção 3: avaliação de cada fonte separadamente
Execute o checklist completo de avaliação da Aula 10 para cada DataFrame antes de combiná-los. Esta seção deve ser a mais longa do notebook — é onde você demonstra que entende os dados.
# ================================
# AVALIAÇÃO — FONTE 1: MUNICÍPIOS
# ================================
print("=== Avaliação: df_municipios ===\n")
print(f"Shape: {df_municipios.shape}")
print(f"\nColunas: {df_municipios.columns.tolist()}")
print(f"\nTipos:\n{df_municipios.dtypes}")
print(f"\nAusentes:\n{df_municipios.isnull().sum()}")
print(f"\nDuplicatas: {df_municipios.duplicated().sum()}")
print(f"\nAmostra:")
print(df_municipios.sample(5))
Após o código, escreva uma célula markdown documentando cada problema encontrado:
### Problemas identificados — Fonte 1 (Municípios IBGE)
1. **Nomes aninhados:** a normalização gerou colunas como
`microrregiao_mesorregiao_UF_regiao_id` — estrutura profundamente
aninhada que precisa ser simplificada. Impacto: todas as linhas.
Decisão: manter apenas id, nome, UF e microrregião.
2. **Sem problemas de ausentes:** todas as 5.570 linhas estão completas.
3. **Sem duplicatas.**
4. **Código do município:** é um inteiro de 7 dígitos que precisará
ser alinhado com o formato do IDEB (que usa 6 dígitos sem o dígito
verificador).
Seção 4: avaliação da chave de junção
Antes do merge, avalie explicitamente as chaves:
# ====================================
# AVALIAÇÃO DA CHAVE DE JUNÇÃO
# ====================================
# Verificar formatos do código de município em cada fonte
print("Exemplos de código de município:")
print(f" IBGE: {df_municipios['id'].head().tolist()}")
print(f" IDEB: {df_ideb['CO_MUNICIPIO'].head().tolist()}")
# IBGE usa 7 dígitos, IDEB usa 6 — precisamos truncar o IBGE
df_municipios['cod_municipio_6d'] = df_municipios['id'].astype(str).str[:6].astype(int)
# Verificar cobertura
cobertura = df_ideb['CO_MUNICIPIO'].isin(df_municipios['cod_municipio_6d']).mean()
print(f"\nCobertura: {cobertura:.1%} dos municípios do IDEB existem no IBGE")
municipios_sem_par = df_ideb[~df_ideb['CO_MUNICIPIO'].isin(df_municipios['cod_municipio_6d'])]
print(f"Municípios no IDEB sem par no IBGE: {len(municipios_sem_par)}")
if len(municipios_sem_par) > 0:
print(municipios_sem_par[['CO_MUNICIPIO', 'NO_MUNICIPIO']].head(10))
Seção 5: limpeza de cada fonte
Execute a limpeza de cada fonte separadamente, com validação ao final de cada etapa:
# ============================
# LIMPEZA — FONTE 1: MUNICÍPIOS
# ============================
df_mun_limpo = (df_municipios
# Selecionar apenas colunas necessárias
.rename(columns={
'id': 'cod_municipio_7d',
'nome': 'municipio',
'microrregiao_mesorregiao_UF_sigla': 'uf',
'microrregiao_nome': 'microrregiao',
'microrregiao_mesorregiao_nome': 'mesorregiao'
})
[['cod_municipio_7d', 'municipio', 'uf', 'microrregiao', 'mesorregiao']]
# Criar chave de 6 dígitos para junção
.assign(cod_municipio=lambda x: x['cod_municipio_7d'].astype(str).str[:6].astype(int))
)
# Validação
assert df_mun_limpo['cod_municipio'].nunique() == len(df_mun_limpo), \
"Chave cod_municipio não é única após truncamento"
print(f"Municípios limpos: {len(df_mun_limpo):,}")
print(df_mun_limpo.head())
# ==========================
# LIMPEZA — FONTE 2: IDEB
# ==========================
# O Excel do INEP tem formatação complexa com linhas de cabeçalho
# e células mescladas — inspecionar cuidadosamente
print("Colunas do IDEB:")
print(df_ideb.columns.tolist())
print("\nPrimeiras linhas:")
print(df_ideb.head(10))
# Identificar as colunas relevantes e renomear
df_ideb_limpo = df_ideb.copy()
# Remover linhas de totais e subtotais (geralmente têm CO_MUNICIPIO ausente)
df_ideb_limpo = df_ideb_limpo.dropna(subset=['CO_MUNICIPIO'])
# Converter código para inteiro
df_ideb_limpo['cod_municipio'] = pd.to_numeric(
df_ideb_limpo['CO_MUNICIPIO'], errors='coerce'
).astype('Int64')
# Selecionar e renomear colunas de interesse
# (ajuste conforme o layout real do arquivo)
df_ideb_limpo = df_ideb_limpo.rename(columns={
'NO_MUNICIPIO': 'municipio_ideb',
'SG_UF': 'uf_ideb',
'VL_OBSERVADO_2021': 'ideb_2021',
'VL_PROJECAO_2021': 'meta_2021'
})[['cod_municipio', 'municipio_ideb', 'uf_ideb', 'ideb_2021', 'meta_2021']]
# Converter IDEB para numérico (pode ter '*' para dados suprimidos)
df_ideb_limpo['ideb_2021'] = pd.to_numeric(
df_ideb_limpo['ideb_2021'].astype(str).str.replace('*', '', regex=False),
errors='coerce'
)
print(f"\nIDEB limpo: {len(df_ideb_limpo):,} municípios")
print(f"IDEB ausente (suprimido ou sem dados): {df_ideb_limpo['ideb_2021'].isnull().sum()}")
Seção 6: combinação e avaliação pós-merge
# ========================
# COMBINAÇÃO DAS FONTES
# ========================
print(f"Antes do merge:")
print(f" Municípios IBGE: {len(df_mun_limpo):,} linhas")
print(f" IDEB: {len(df_ideb_limpo):,} linhas")
df_combinado = pd.merge(
df_mun_limpo,
df_ideb_limpo,
on='cod_municipio',
how='left'
)
print(f"\nApós merge (left join):")
print(f" Resultado: {len(df_combinado):,} linhas")
print(f" Municípios com IDEB: {df_combinado['ideb_2021'].notnull().sum():,}")
print(f" Municípios sem IDEB: {df_combinado['ideb_2021'].isnull().sum():,}")
# Verificar se o merge não criou linhas extras (indicativo de chave não-única)
assert len(df_combinado) == len(df_mun_limpo), \
f"Merge criou linhas extras: {len(df_combinado)} != {len(df_mun_limpo)}"
print("\nValidação: número de linhas correto após merge")
# ========================================
# AVALIAÇÃO DO DATASET COMBINADO
# ========================================
print("=== Avaliação pós-combinação ===\n")
print(df_combinado.info())
print(f"\nAusentes:\n{df_combinado.isnull().sum()}")
print(f"\nEstatísticas do IDEB:")
print(df_combinado['ideb_2021'].describe())
# Verificar se municípios sem IDEB têm padrão específico
sem_ideb = df_combinado[df_combinado['ideb_2021'].isnull()]
com_ideb = df_combinado[df_combinado['ideb_2021'].notnull()]
print(f"\nDistribuição por UF — municípios sem IDEB:")
print(sem_ideb['uf'].value_counts().head(10))
Seção 7: limpeza final do dataset combinado
# ==================================
# LIMPEZA FINAL DO DATASET COMBINADO
# ==================================
df_final = df_combinado.copy()
# Remover colunas redundantes criadas pelo merge
if 'municipio_ideb' in df_final.columns:
# Verificar consistência entre os dois nomes de município
inconsistentes = (
df_final['municipio'].str.upper() != df_final['municipio_ideb'].str.upper()
).sum()
print(f"Municípios com nome diferente entre fontes: {inconsistentes}")
df_final = df_final.drop(columns=['municipio_ideb', 'uf_ideb'])
# Criar variável indicadora para municípios sem IDEB
df_final['tem_ideb'] = df_final['ideb_2021'].notnull().astype(int)
# Padronizar texto
df_final['municipio'] = df_final['municipio'].str.title().str.strip()
df_final['uf'] = df_final['uf'].str.upper().str.strip()
# Validação final
print("\n=== Validação final ===")
assert df_final['cod_municipio'].duplicated().sum() == 0
print("OK — sem duplicatas de cod_municipio")
assert df_final['uf'].str.len().eq(2).all()
print("OK — todas as UFs têm 2 caracteres")
assert df_final[df_final['tem_ideb'] == 1]['ideb_2021'].between(0, 10).all()
print("OK — todos os valores de IDEB estão entre 0 e 10")
print(f"\nDataset final: {df_final.shape[0]:,} linhas, {df_final.shape[1]} colunas")
Seção 8: salvar e documentar
# ========================
# SALVAR DATASET LIMPO
# ========================
import os
os.makedirs('data/processed', exist_ok=True)
df_final.to_csv('data/processed/municipios_ideb_limpo.csv',
index=False, encoding='utf-8')
df_final.to_parquet('data/processed/municipios_ideb_limpo.parquet',
index=False)
print("Dataset salvo em:")
print(" data/processed/municipios_ideb_limpo.csv")
print(" data/processed/municipios_ideb_limpo.parquet")
print(f"\nColunas finais: {df_final.columns.tolist()}")
print(f"\nAmostra:")
print(df_final.sample(5))
Seção 9: conclusões e limitações
## Conclusões do processo de wrangling
### Estado do dataset final
O dataset final contém 5.570 municípios brasileiros com dados
demográficos do IBGE e IDEB 2021 do INEP. Dos 5.570 municípios,
4.891 (87.8%) têm IDEB disponível. Os 679 municípios sem IDEB são
predominantemente do Norte e Nordeste (78% dos casos), e tendem
a ser municípios pequenos (mediana de 3.200 habitantes, contra
12.400 dos municípios com IDEB).
### Decisões de wrangling mais importantes
1. Truncamento do código de município de 7 para 6 dígitos para
compatibilizar as fontes. Essa operação foi validada — nenhum
município perdeu correspondência por colisão de código.
2. Valores de IDEB suprimidos (representados por '*' no arquivo
original) foram tratados como ausentes. Esses casos representam
municípios onde o número de alunos era insuficiente para
publicação do indicador, não ausência de dados de fato.
3. Left join foi escolhido para manter todos os 5.570 municípios
do IBGE, incluindo os sem IDEB. Um inner join teria descartado
679 municípios silenciosamente.
### Limitações
- IDEB ausente está correlacionado com municípios menores e mais
pobres, o que limita análises de desigualdade educacional —
exatamente os casos mais relevantes para esse tipo de análise
estão sub-representados.
- Os dados do IBGE usados são estimativas populacionais de 2020,
não do Censo 2022. Municípios com crescimento ou declínio
populacional acelerado podem ter dados desatualizados.
- O IDEB cobre apenas escolas públicas. Municípios com maior
proporção de alunos em escolas privadas terão o IDEB menos
representativo da qualidade educacional geral.
Critérios de qualidade
O checklist de qualidade para este projeto é mais exigente que o do Projeto 1, refletindo o foco em processo.
O notebook executa sem erros com Restart & Run All? Obrigatório.
Pelo menos duas fontes foram coletadas, com pelo menos uma sendo não-CSV? Obrigatório.
Cada fonte foi avaliada separadamente com o checklist completo da Aula 10? Obrigatório.
Cada problema identificado na avaliação foi documentado com quê é, quantas linhas afeta e qual a decisão? Obrigatório.
A chave de junção foi avaliada antes do merge? Obrigatório.
O resultado do merge foi validado — o número de linhas faz sentido? Obrigatório.
Todas as limpezas foram validadas com assert ou verificação explícita? Obrigatório.
O dataset final foi salvo em arquivo? Obrigatório.
A seção de conclusões documenta as limitações dos dados, não apenas do processo de limpeza? Obrigatório.
O código está organizado em seções com markdown? Fortemente recomendado.
Os dados brutos foram salvos antes de qualquer processamento? Fortemente recomendado.
Erros comuns a evitar
Combinar fontes sem avaliar a chave de junção. O merge vai executar sem erro mesmo que a cobertura seja péssima. Você vai terminar com um DataFrame cheio de NaN sem entender por quê.
Limpar antes de avaliar completamente. A Aula 10 discutiu isso. Avalie tudo, documente, depois limpe na ordem correta.
Esquecer de salvar os dados brutos. Se a coleta falhar na segunda execução ou você precisar reprocessar, você vai querer os dados brutos disponíveis sem precisar coletar de novo.
Não documentar decisões de limpeza que "pareciam óbvias". Semanas depois você não vai lembrar por que removeu aquelas linhas. Documente tudo.
Usar inner join como padrão sem verificar o impacto. Inner join descarta silenciosamente todas as linhas sem correspondência. Sempre verifique quantas linhas você perde e se a perda é aceitável e esperada.
Não validar o resultado do merge. Um merge que multiplica linhas por causa de chave não-única não gera erro — gera silenciosamente um DataFrame com mais linhas do que o esperado, e toda análise subsequente estará errada.
Resumo
O Projeto 2 é um exercício completo de wrangling: coleta de múltiplas fontes, avaliação sistemática com documentação de todos os problemas, limpeza programática e validada, combinação cuidadosa com verificação da chave de junção, e documentação honesta das limitações. A qualidade é medida principalmente pelo rigor do processo — avaliação completa, decisões justificadas, validações explícitas — e não apenas pelo produto final. Um dataset com problemas conhecidos e bem documentados é muito mais valioso do que um dataset aparentemente limpo onde os problemas foram varridos para debaixo do tapete.
Exercícios
-
Por que é necessário salvar os dados brutos antes de qualquer processamento, mesmo que você vá sobregravar o arquivo local? Dê dois cenários concretos em que não ter os dados brutos causaria um problema sério.
✓ Resposta:Dados brutos são o ponto de partida de toda reprodutibilidade. Uma vez que você processa e sobrescreve, a transformação se torna irreversível a menos que você possa coletar novamente — o que nem sempre é possível.
Cenário 1: você descobre semanas depois que um passo de limpeza foi incorreto. Por exemplo, você removeu linhas com
valor == 0assumindo que eram erros, mas descobre depois que zeros representavam brindes — pedidos legítimos sem cobrança. Sem o dado bruto, você não consegue refazer a limpeza com a correção. Com o dado bruto salvo emdata/raw/, você reprocessa do zero em minutos.Cenário 2: a fonte de dados sai do ar ou muda o formato. APIs públicas são descontinuadas, portais governamentais mudam a estrutura dos arquivos, pages de scraping são reestruturadas. Se você coletou dados de uma API do governo em janeiro e em março a API mudou o esquema ou saiu do ar, sem o arquivo bruto salvo você não consegue reproduzir nem corrigir a análise. Com o bruto salvo, a análise é reproduzível independentemente do estado atual da fonte.
A prática correta é criar uma pasta
data/raw/que é tratada como somente-leitura após a coleta. Todo processamento acontece emdata/processed/. O custo de armazenamento é negligenciável comparado ao custo de retrabalho. -
Você faz um left join entre um DataFrame de 10.000 pedidos e um DataFrame de 3.000 clientes, usando
id_clientecomo chave. O resultado tem 14.237 linhas. O que aconteceu e como você investigaria o problema?✓ Resposta:Um left join que começa com 10.000 linhas não pode terminar com mais de 10.000 linhas a menos que haja duplicatas na chave do DataFrame da direita. O resultado de 14.237 linhas indica que alguns
id_clienteaparecem mais de uma vez no DataFrame de clientes.O que acontece mecanicamente: quando um
id_clientedo DataFrame de pedidos encontra dois registros correspondentes no DataFrame de clientes, a linha do pedido é duplicada — uma cópia para cada correspondência. Se um pedido tem um cliente que aparece 3 vezes no DataFrame de clientes, aquele pedido vira 3 linhas no resultado.A investigação:
# Verificar unicidade da chave no DataFrame da direita print(f"Clientes únicos: {df_clientes['id_cliente'].nunique()}") print(f"Total de linhas: {len(df_clientes)}") print(f"Diferença: {len(df_clientes) - df_clientes['id_cliente'].nunique()}") # Ver os clientes duplicados clientes_dup = df_clientes[df_clientes.duplicated(subset=['id_cliente'], keep=False)] print(f"\nLinhas com id_cliente duplicado: {len(clientes_dup)}") print(clientes_dup.sort_values('id_cliente').head(10)) # Calcular quantas linhas extras o merge gerou extras = 14237 - 10000 print(f"\nLinhas extras geradas: {extras}")A correção depende da causa: se são duplicatas exatas, usar
drop_duplicates()antes do merge. Se são registros históricos diferentes (o mesmo cliente com dois endereços em datas diferentes), decidir qual manter — geralmente o mais recente — antes de fazer o merge. -
Qual a diferença prática entre left join, inner join e outer join no contexto de wrangling? Quando cada um é a escolha correta?
✓ Resposta:A diferença é sobre o que acontece com linhas que não têm correspondência na outra tabela.
Inner join mantém apenas linhas com correspondência em ambas as tabelas. Linhas sem par são descartadas silenciosamente. Use quando você só quer trabalhar com registros completos e a perda de linhas sem correspondência é esperada e aceitável. Exemplo: juntar pedidos com pagamentos para calcular receita — pedidos sem pagamento registrado são provavelmente cancelados e não interessam.
Left join mantém todas as linhas da tabela da esquerda, e preenche com NaN onde não há correspondência na direita. Use quando a tabela da esquerda é sua "tabela fato" principal e você quer enriquecê-la com informações adicionais sem perder registros. Exemplo: juntar pedidos com clientes para adicionar cidade — pedidos de clientes não cadastrados devem aparecer com cidade NaN, não ser descartados.
Outer join mantém todas as linhas de ambas as tabelas, com NaN onde não há correspondência em nenhum dos lados. Use quando você quer identificar registros que existem em uma fonte mas não na outra — essencialmente uma auditoria de cobertura. Exemplo: comparar uma lista de produtos esperados com uma lista de produtos recebidos para identificar o que está faltando e o que veio a mais.
Right join existe mas é raramente usado — um right join de A com B é equivalente a um left join de B com A, então por convenção se usa left join com a ordem das tabelas invertida.
-
Você está combinando dados de duas fontes sobre municípios brasileiros. A Fonte A usa o código IBGE de 7 dígitos. A Fonte B usa o código IBGE de 6 dígitos (sem o dígito verificador). Além disso, alguns municípios da Fonte A têm nomes com acento e a Fonte B tem nomes sem acento. Quais são todas as etapas de preparação necessárias antes do merge e por que a ordem importa?
✓ Resposta:As etapas necessárias são as seguintes, e a ordem importa porque cada uma depende do estado produzido pela anterior.
Etapa 1: padronizar o código numérico. Truncar o código de 7 dígitos da Fonte A para 6 dígitos, ou converter o código de 6 dígitos da Fonte B para 7 dígitos. Truncar é mais seguro porque o 7º dígito é apenas um dígito verificador e não identifica nenhuma entidade. Essa etapa deve vir antes de qualquer verificação de cobertura.
df_a['cod_6d'] = df_a['cod_ibge_7d'].astype(str).str[:6].astype(int)Etapa 2: verificar se o truncamento criou colisões. Dois municípios diferentes poderiam ter o mesmo código de 6 dígitos se os 7º dígitos forem diferentes. Isso seria um problema grave.
assert df_a['cod_6d'].nunique() == len(df_a), "Truncamento criou colisões de código"Etapa 3: verificar cobertura antes de mergear. Quantos municípios de A existem em B e vice-versa. Isso define qual tipo de join faz mais sentido.
Etapa 4: não usar o nome como chave alternativa se o código está disponível. O nome com e sem acento é um problema de uniformidade, mas se você já tem o código numérico padronizado nas duas fontes, o merge deve usar o código. A padronização de nomes é útil para validação após o merge — você pode comparar se o nome em A bate com o nome em B para o mesmo código e detectar possíveis erros de cadastro — mas não deve ser a chave principal.
A ordem importa porque: se você usar o nome como chave antes de padronizar os acentos, o merge vai falhar para os municípios com discrepância. Se você tentar padronizar acentos antes de validar o código, pode gastar trabalho numa etapa que não é necessária para o merge. A sequência correta é: padronizar código → validar → mergear pelo código → usar nome para validação cruzada.
-
Qual a diferença entre documentar um problema de qualidade e documentar uma decisão de limpeza? Por que as duas documentações são necessárias e o que acontece se você tem apenas uma delas?
✓ Resposta:Documentar um problema de qualidade descreve o que foi encontrado nos dados: "a coluna
review_scoretem 15% de valores ausentes". Documenta o estado dos dados antes da intervenção.Documentar uma decisão de limpeza descreve o que foi feito e por quê: "as linhas com
review_scoreausente foram removidas para análises de satisfação porque a ausência está correlacionada com pedidos cancelados antes da entrega, que não são relevantes para a análise de satisfação pós-entrega. Para análises de volume, as linhas foram mantidas."As duas são necessárias porque elas respondem perguntas diferentes e para públicos diferentes.
A documentação do problema responde: "o que está errado com os dados?" Ela é necessária para qualquer um que queira entender a qualidade da fonte de dados, inclusive você mesmo voltando ao projeto meses depois. Sem ela, você não sabe quais problemas existiam e se a limpeza os resolveu todos.
A documentação da decisão responde: "por que os dados ficaram assim após a limpeza?" Ela é necessária para justificar escolhas analíticas que afetam os resultados. Sem ela, ninguém sabe por que certas linhas foram removidas, e os resultados são irreproduzíveis e injustificáveis.
Se você tem apenas a documentação do problema sem a decisão, você sabe que havia problemas mas não sabe o que foi feito com eles — o notebook é um quebra-cabeça para quem não o escreveu.
Se você tem apenas a documentação da decisão sem o problema, você sabe o que foi feito mas não tem contexto sobre por que foi necessário — é difícil avaliar se a decisão foi adequada.
Juntas, as duas documentações formam um registro auditável e reproduzível do processo de wrangling.
Referências
- Wickham, Hadley; Grolemund, Garrett. R for Data Science, 2ª edição. O'Reilly, 2023. Os capítulos sobre wrangling descrevem o processo completo com boas práticas de documentação e organização de projetos. Disponível em r4ds.hadley.nz.
- Wilson, Greg et al. "Good Enough Practices in Scientific Computing". PLOS Computational Biology, 2017. Recomendações concretas sobre organização de projetos, incluindo a separação entre dados brutos e processados e a documentação de transformações. Disponível em journals.plos.org.
- API de Localidades do IBGE: servicodados.ibge.gov.br/api/docs/localidades Documentação completa da API pública de dados geográficos do IBGE, incluindo municípios, microrregiões e estados.
- Portal de Dados Abertos do INEP: inep.gov.br/microdados Ponto de acesso aos microdados do ENEM, Censo Escolar, IDEB e outros indicadores educacionais brasileiros.
- Portal Brasileiro de Dados Abertos: dados.gov.br. Catálogo de datasets de órgãos federais brasileiros, com busca por tema, organização e formato.
- Broman, Karl; Woo, Kara. "Data Organization in Spreadsheets". The American Statistician, 2018. Artigo prático sobre os erros mais comuns em organização de dados em planilhas e como evitá-los, com princípios aplicáveis a qualquer formato. Disponível em tandfonline.com.