Depois de trabalhar com dados por alguns anos, percebi que 80% do meu tempo é gasto limpando dados. Não é a parte mais glamorosa do trabalho, mas é a mais importante.
Nesse post vou mostrar técnicas de limpeza que uso todo dia. Coisas que aprendi na prática, errando muito até acertar.
Por que "Avançado" se É para Iniciantes?
Bem, avançado aqui não significa complicado. Significa que vamos além do básico de remover valores nulos. Vamos lidar com problemas reais que você vai encontrar: duplicadas escondidas, dados inconsistentes, outliers que não são erros, e por aí vai.
Os Dados que Vamos Usar
Vou criar um dataset bagunçado de verdade. Baseado em coisas reais que já vi:
import pandas as pd
import numpy as np
# Dados do mundo real (bagunçados de propósito)
dados = {
'produto': ['iPhone 13', 'iphone 13', 'IPHONE 13 ', 'Samsung S21',
'samsung s21', 'Notebook Dell', 'notebook dell', 'Mouse Logitech'],
'preco': [4500, 4500, 4500, 3200, 3200, 5500, 5500, 150],
'vendas': [50, 30, 20, 40, 35, 25, 15, 200],
'data': ['2024-01-15', '15/01/2024', '2024-01-15', '2024-02-20',
'20-02-2024', '2024-03-10', '10/03/2024', '2024-04-05'],
'vendedor': ['João Silva', 'Joao Silva', 'João Silva', 'Maria Santos',
'maria santos', 'Pedro Costa', 'Pedro Costa', 'Ana Lima'],
'regiao': ['SP', 'sp', 'São Paulo', 'RJ', 'rio de janeiro', 'MG', 'minas gerais', 'SP']
}
df = pd.DataFrame(dados)
print("Nossos dados bagunçados:")
print(df)
Só de olhar já dá para ver vários problemas. Vamos resolver um por um.
Problema 1: Nomes Inconsistentes
O pior problema que sempre encontro: o mesmo produto escrito de jeito diferentes. Isso estraga qualquer análise de vendas.
print("Produtos únicos (antes):")
print(df['produto'].unique())
print(f"Total: {df['produto'].nunique()} produtos")
Você verá que temos 8 produtos, mas na verdade são só 4. Vamos consertar:
# Padroniza tudo: minúsculo e sem espaços extras
df['produto'] = df['produto'].str.lower().str.strip()
print("\nProdutos únicos (depois):")
print(df['produto'].unique())
print(f"Total: {df['produto'].nunique()} produtos")
Muito melhor! Agora temos 4 produtos como deveria ser.
Problema 2: Formatos de Datas Diferentes
Isso aqui me deu muita dor de cabeça no início. Datas em formatos diferentes no mesmo dataset:
print("Datas originais:")
print(df['data'])
Vamos padronizar tudo:
# O pandas é esperto e consegue entender vários formatos
df['data'] = pd.to_datetime(df['data'], errors='coerce')
print("\nDatas padronizadas:")
print(df['data'])
Agora, todas as datas estão no mesmo formato. O errors='coerce' transforma datas inválidas em NaT (Not a Time), que é tipo o NaN para datas.
Problema 3: Duplicadas Escondidas
O mais traiçoeiro de todos. Registros que parecem diferentes, mas que são a mesma coisa:
print("Total de linhas:", len(df))
print("Duplicatas óbvias:", df.duplicated().sum())
Zero duplicadas? Mentira! Vamos investigar melhor:
# Agrupa por produto e soma as vendas
vendas_por_produto = df.groupby('produto')['vendas'].sum()
print("\nVendas por produto:")
print(vendas_por_produto)
Viu? O iPhone 13 aparece 3 vezes porque os nomes estavam diferentes. Vamos consolidar:
# Agrupa tudo e soma
df_limpo = df.groupby(['produto', 'data']).agg({
'preco': 'first', # Pega o primeiro preço
'vendas': 'sum', # Soma as vendas
'vendedor': 'first',
'regiao': 'first'
}).reset_index()
print("\nDados consolidados:")
print(df_limpo)
print(f"De {len(df)} linhas para {len(df_limpo)} linhas")
Agora sim! Reduzimos de 8 para 5 linhas, eliminando as duplicadas escondidas.
Problema 4: Padronização de Categorias
Regiões escritas de jeito diferentes vão bagunçar seus gráficos:
print("Regiões únicas:")
print(df_limpo['regiao'].unique())
Vamos criar um dicionário para padronizar:
# Mapeamento manual (melhor jeito para categorias pequenas)
mapa_regioes = {
'sp': 'SP',
'são paulo': 'SP',
'rj': 'RJ',
'rio de janeiro': 'RJ',
'mg': 'MG',
'minas gerais': 'MG'
}
# Primeiro normaliza tudo
df_limpo['regiao'] = df_limpo['regiao'].str.lower()
# Depois aplica o mapeamento
df_limpo['regiao'] = df_limpo['regiao'].replace(mapa_regioes)
print("\nRegiões padronizadas:")
print(df_limpo['regiao'].unique())
Muito mais limpo!
Problema 5: Outliers que Não São Erros
Vou criar uma situação que sempre me pega:
# Adiciona mais dados para exemplo
vendas_exemplo = pd.Series([100, 105, 98, 102, 99, 103, 101, 500, 97, 104])
print("Vendas:")
print(vendas_exemplo)
print(f"\nMédia: {vendas_exemplo.mean():.2f}")
O 500 é um outlier óbvio. Mas será que é um erro? Vamos investigar:
# Calcula estatísticas
Q1 = vendas_exemplo.quantile(0.25)
Q3 = vendas_exemplo.quantile(0.75)
IQR = Q3 - Q1
limite_inferior = Q1 - 1.5 * IQR
limite_superior = Q3 + 1.5 * IQR
print(f"Limite inferior: {limite_inferior}")
print(f"Limite superior: {limite_superior}")
# Encontra outliers
outliers = vendas_exemplo[(vendas_exemplo < limite_inferior) | (vendas_exemplo > limite_superior)]
print(f"\nOutliers encontrados: {outliers.values}")
Agora você decide: é um erro ou uma Black Friday? Essa decisão precisa de contexto do negócio.
Problema 6: Valores Faltando (Mas Não Nulos)
Já peguei datasets onde valores vazios eram representados como "N/A", "n/a", "NA", "-", "null"... tudo menos NaN de verdade:
# Exemplo de dados com valores "falsos"
dados_sujos = {
'vendedor': ['João', 'Maria', 'N/A', 'Pedro', 'n/a', '-', 'Ana'],
'comissao': [500, 600, 0, 700, 0, 0, 800]
}
df_sujo = pd.DataFrame(dados_sujos)
print("Dados com valores falsos:")
print(df_sujo)
Vamos converter todos esses valores para NaN de verdade:
# Lista de valores que significam "vazio"
valores_vazios = ['N/A', 'n/a', 'NA', 'na', '-', '', ' ', 'null', 'NULL']
# Substitui todos por NaN
df_sujo = df_sujo.replace(valores_vazios, np.nan)
print("\nDepois da limpeza:")
print(df_sujo)
print(f"\nNulos por coluna:")
print(df_sujo.isnull().sum())
Muito melhor para trabalhar!
Problema 7: Espaços em Branco Escondidos
Esse é ninja. Você não vê, mas ele está lá arruinando tudo:
# Exemplo com espaços escondidos
nomes = pd.Series(['João', 'João ', ' João', 'João ', 'Maria'])
print("Valores únicos (antes):")
print(nomes.unique())
print(f"Total: {nomes.nunique()}")
Remove espaços extras
nomes_limpos = nomes.str.strip()
print("\nValores únicos (depois):") print(nomes_limpos.unique()) print(f"Total: {nomes_limpos.nunique()}") De 4 "Joãos" diferentes para 1 só. Esse truque do .str.strip() é ouro.
Problema 8: Colunas com Tipos Errados
O pandas às vezes erra o tipo da coluna:
# Exemplo com tipos errados
dados_tipos = {
'codigo': ['001', '002', '003', '004'],
'preco': ['100.50', '200.00', '150.75', '300.25'],
'ativo': ['Sim', 'Não', 'Sim', 'Sim']
}
df_tipos = pd.DataFrame(dados_tipos)
print("Tipos originais:")
print(df_tipos.dtypes)
Tudo como texto (object). Vamos consertar:
# Converte preço para número
df_tipos['preco'] = pd.to_numeric(df_tipos['preco'])
# Converte ativo para booleano
df_tipos['ativo'] = df_tipos['ativo'].map({'Sim': True, 'Não': False})
print("\nTipos corrigidos:")
print(df_tipos.dtypes)
print("\nDados:")
print(df_tipos)
Agora sim! Agora você pode fazer operações matemáticas com preços e filtros com ativo.
Problema 9: Nomes de Colunas Ruins
Já recebi arquivos com nomes de colunas assim:
# Exemplo do mundo real
df_colunas_ruins = pd.DataFrame({
'Nome do Cliente': ['João', 'Maria'],
'Valor Total (R$)': [100, 200],
'Data de Nascimento': ['1990-01-01', '1985-05-15']
})
print("Colunas originais:")
print(df_colunas_ruins.columns.tolist())
Espaços e caracteres especiais são um pesadelo. Vamos padronizar:
# Função simples para limpar nomes de colunas
def limpar_nome_coluna(nome):
# Minúsculo, substitui espaços por underline, remove caracteres especiais
nome = nome.lower()
nome = nome.replace(' ', '_')
nome = nome.replace('(', '').replace(')', '')
nome = nome.replace('$', '').replace('%', '')
return nome
# Aplica na lista de colunas
df_colunas_ruins.columns = [limpar_nome_coluna(col) for col in df_colunas_ruins.columns]
print("\nColunas limpas:")
print(df_colunas_ruins.columns.tolist())
Muito mais fácil de trabalhar agora!
Minha Rotina de Limpeza
Sempre que recebo um dataset novo, faço isso nessa ordem:
# 1. Entende o que você tem
print("Shape:", df.shape)
print("\nTipos:")
print(df.dtypes)
print("\nPrimeiras linhas:")
print(df.head())
# 2. Limpa nomes de colunas
df.columns = [limpar_nome_coluna(col) for col in df.columns]
# 3. Identifica valores nulos
print("\nNulos:")
print(df.isnull().sum())
# 4. Padroniza strings
for col in df.select_dtypes(include=['object']).columns:
df[col] = df[col].str.strip().str.lower()
# 5. Converte tipos
# (isso você faz baseado no que descobriu acima)
# 6. Remove duplicatas
print(f"\nDuplicatas: {df.duplicated().sum()}")
df = df.drop_duplicates()
# 7. Documenta o que foi feito
print("\nLimpeza concluída!")
print(f"Shape final: {df.shape}")
Tratando Valores Nulos: Quando Remover vs Preencher
Nem sempre você deve remover linhas com valores nulos. Às vezes é melhor preencher:
# Exemplo com vendas mensais
vendas_mensais = pd.DataFrame({
'mes': ['Jan', 'Fev', 'Mar', 'Abr', 'Mai'],
'vendas': [100, np.nan, 120, np.nan, 130]
})
print("Vendas com dados faltando:")
print(vendas_mensais)
# Opção 1: Preencher com a média
vendas_mensais['vendas_media'] = vendas_mensais['vendas'].fillna(vendas_mensais['vendas'].mean())
# Opção 2: Preencher com o último valor conhecido (forward fill)
vendas_mensais['vendas_ffill'] = vendas_mensais['vendas'].fillna(method='ffill')
# Opção 3: Interpolação linear
vendas_mensais['vendas_interpolada'] = vendas_mensais['vendas'].interpolate()
print("\nDiferentes métodos de preenchimento:")
print(vendas_mensais)
Qual usar? Depende do contexto:
- Média: Quando os valores são estáveis
- Forward fill: Quando valores tendem a se manter
- Interpolação: Quando há uma tendência clara
Detectando Padrões Estranhos
Às vezes os dados estão tecnicamente corretos, mas tem algo errado:
# Exemplo: vendas sempre terminam em 0 ou 5
vendas_suspeitas = pd.Series([100, 95, 200, 105, 150, 95, 200])
# Checa o padrão
ultimos_digitos = vendas_suspeitas % 10
print("Últimos dígitos das vendas:")
print(ultimos_digitos.value_counts())
Se todos os valores terminam em 0 ou 5, provavelmente são estimativas, não valores reais. Isso muda como você deve interpretar os dados.
Consolidando Tudo: Meu Checklist
Aqui está o que eu sempre faço, sem exceção:
# Carrega os dados
df = pd.read_csv('seus_dados.csv')
# 1. Primeira olhada
print("=" * 50)
print("INSPEÇÃO INICIAL")
print("=" * 50)
print(f"Linhas: {len(df)}")
print(f"Colunas: {len(df.columns)}")
print(f"\nPrimeiras linhas:")
print(df.head())
# 2. Limpa nomes de colunas
df.columns = df.columns.str.lower().str.strip().str.replace(' ', '_')
# 3. Checa nulos
print("\n" + "=" * 50)
print("VALORES NULOS")
print("=" * 50)
print(df.isnull().sum())
# 4. Substitui valores vazios por NaN
valores_para_nan = ['N/A', 'n/a', 'NA', '-', '', ' ']
df = df.replace(valores_para_nan, np.nan)
# 5. Padroniza strings
for col in df.select_dtypes(include=['object']).columns:
df[col] = df[col].str.strip()
# 6. Checa duplicatas
print("\n" + "=" * 50)
print("DUPLICATAS")
print("=" * 50)
duplicatas = df.duplicated().sum()
print(f"Total: {duplicatas}")
if duplicatas > 0:
df = df.drop_duplicates()
print("Duplicatas removidas!")
# 7. Resume estatísticas
print("\n" + "=" * 50)
print("ESTATÍSTICAS")
print("=" * 50)
print(df.describe())
print("\nLimpeza concluída! ✅")
Dicas que Aprendi Errando
- Sempre faça backup dos dados originais:
df_original = df.copy() # Salva sua vida
- Documente cada transformação:
# Ruim: df['preco'] = df['preco'] * 1.1
# Bom:
# Aplica aumento de 10% nos preços (solicitação do gerente em 15/03/2024)
df['preco'] = df['preco'] * 1.1
- Use .shape para acompanhar mudanças:
print(f"Antes: {df.shape}")
df = df.dropna()
print(f"Depois: {df.shape}")
print(f"Removido: {len(df_original) - len(df)} linhas")
- Cuidado com .fillna() - não use sem pensar:
# Ruim: preencher tudo com zero
# df = df.fillna(0)
# Bom: preencher cada coluna de forma apropriada
df['idade'] = df['idade'].fillna(df['idade'].median())
df['nome'] = df['nome'].fillna('Desconhecido')
Conclusão
Limpeza de dados não é glamorosa, mas é essencial. Quanto melhor você limpar seus dados, mais confiáveis serão suas análises.
Não existe uma receita única. Cada dataset é diferente e vai exigir técnicas diferentes. O importante é ser metódico e sempre documentar o que você fez.
Com o tempo, você vai desenvolver seu próprio estilo de limpeza. Essas técnicas que mostrei são as que funcionam para mim. Teste, adapte, e encontre o que funciona para você.
Alguma técnica de limpeza que você usa e eu não mencionei? Conta nos comentários!
Comentários (0)
Seja o primeiro a comentar!
Deixe um comentário