Quando comecei a trabalhar com dados, lembro-me de haver passado 3 horas em uma análise incrível só para descobrir que metade dos dados estavam errados. Idades negativas, e-mails sem "@", datas no futuro... foi frustrante. Apanhei muito.

Desde então, sempre valido meus dados antes de começar qualquer análise. Nesse post vou mostrar como fazer isso de forma simples, sem complicação.

Por que Eu Sempre Valido Dados?

Vou contar uma história real. Uma vez, estava analisando dados de vendas de uma empresa e encontrei um vendedor que tinha "vendido" R$ -50.000 em um mês. Obviamente era um erro, mas se não tivesse checado, essa informação iria direto para o relatório do CEO.

Dados sujos podem arruinar toda sua análise. É melhor gastar 10 minutos validando, do que horas explicando por que os números não fazem sentido.

Ferramentas que Vamos Usar

Para esse post, vamos usar apenas:

  • Pandas (que você provavelmente já tem)
  • Um pouco de criatividade

Se não tem o pandas ainda:

pip install pandas

Pronto. Vamos começar.

Criando Dados de Exemplo

Vou criar uns dados bagunçados para a gente trabalhar. Esses são problemas reais que já encontrei por aí:

import pandas as pd
import numpy as np

# Dados problemáticos (baseados em casos reais)
dados = {
    'nome': ['Ana Silva', 'Bruno', '', 'Carolina Santos', None, 'Pedro'],
    'idade': [25, 150, -5, 30, 28, 200],
    'email': ['ana@email.com', 'bruno@', 'carolina.email', 'carol@teste.com', 'joao@email.com', ''],
    'salario': [5000, 50000, -1000, 7500, 6000, 0],
    'data_nascimento': ['1998-05-15', '1870-01-01', '2025-12-31', '1993-03-20', '1995-07-10', ''],
    'telefone': ['11999887766', '123', '', '11888777666', '11777666555', 'abc123']
}

df = pd.DataFrame(dados)
print("Nossos dados problemáticos:")
print(df)

Só de olhar já dá para ver vários problemas, né?

Primeira Coisa: Caçar os Valores Nulos

Sempre começo pelos valores nulos. É o mais fácil de detectar:

print("Valores nulos por coluna:")
print(df.isnull().sum())

# Vamos ver quais linhas têm algum problema
linhas_com_problemas = df[df.isnull().any(axis=1)]
print(f"\nLinhas com algum valor nulo: {len(linhas_com_problemas)}")

Mas cuidado! Nem todo valor "vazio" é detectado pelo .isnull(). Strings vazias passam batido.

O Truque das Strings Vazias

Aprendi isso da pior forma. Strings vazias ('') não são consideradas nulas pelo pandas. Vamos caça-las:

# Função para encontrar strings vazias
def encontrar_strings_vazias(df):
    for coluna in df.columns:
        # Só checa colunas de texto
        if df[coluna].dtype == 'object':
            vazias = (df[coluna].astype(str).str.strip() == '')
            if vazias.any():
                print(f"Coluna '{coluna}': {vazias.sum()} strings vazias")

encontrar_strings_vazias(df)

Essa função salvou minha vida várias vezes.

Validando Idades (Meu Pesadelo)

Idades são onde mais encontro problemas. Já vi "gente" com -5 anos e outros com 300 anos:

print("Problemas com idades:")

# Idades negativas
idade_negativa = df[df['idade'] < 0]
if not idade_negativa.empty:
    print(f"Idades negativas: {len(idade_negativa)}")
    print(idade_negativa[['nome', 'idade']])

# Idades impossíveis (acima de 120)
idade_alta = df[df['idade'] > 120]
if not idade_alta.empty:
    print(f"Idades acima de 120: {len(idade_alta)}")
    print(idade_alta[['nome', 'idade']])

Simples e efetivo.

E-mails: O Básico que Funciona

Para emails, uso uma verificação bem simples. Não precisa ser perfeita:

def validar_emails_simples(df):
    # Checa se tem @ e um ponto depois do @
    emails_validos = df['email'].str.contains('@.*\.', na=False)
    emails_problema = df[~emails_validos]
    
    print(f"Emails com problema: {len(emails_problema)}")
    if not emails_problema.empty:
        print(emails_problema[['nome', 'email']])

validar_emails_simples(df)

Não é perfeito, mas pega 90% dos problemas.

Datas: Meu Maior Desafio

Datas são complicadas. Já recebi datasets com pessoas nascidas em 2030:

def verificar_datas(df):
    # Primeiro, vamos tentar converter para datetime
    try:
        datas = pd.to_datetime(df['data_nascimento'], errors='coerce')
        
        # Datas que não conseguiram ser convertidas
        datas_invalidas = datas.isna()
        print(f"Datas inválidas: {datas_invalidas.sum()}")
        
        # Datas no futuro
        hoje = pd.Timestamp.now()
        datas_futuro = datas > hoje
        print(f"Datas no futuro: {datas_futuro.sum()}")
        
        # Datas muito antigas (digamos, antes de 1900)
        datas_antigas = datas < pd.Timestamp('1900-01-01')
        print(f"Datas antes de 1900: {datas_antigas.sum()}")
        
    except Exception as e:
        print(f"Erro ao processar datas: {e}")

verificar_datas(df)

Telefones: Verificação Básica

Para telefones, mantenho simples:

def verificar_telefones(df):
    # Telefones devem ter só números e ter pelo menos 10 dígitos
    telefones_validos = df['telefone'].str.match(r'^\d{10,11}$', na=False)
    telefones_problema = df[~telefones_validos]
    
    print(f"Telefones com problema: {len(telefones_problema)}")
    if not telefones_problema.empty:
        print(telefones_problema[['nome', 'telefone']])

verificar_telefones(df)

Limpeza Automática: Minha Salvação

Depois de detectar, preciso limpar. Criei uma função que uso em todo projeto:

def limpar_dados_basico(df):
    df_limpo = df.copy()
    
    # Substitui strings vazias por NaN
    df_limpo = df_limpo.replace('', np.nan)
    
    # Remove linhas completamente vazias
    df_limpo = df_limpo.dropna(how='all')
    
    # Limpa espaços em branco das strings
    for coluna in df_limpo.select_dtypes(include=['object']).columns:
        df_limpo[coluna] = df_limpo[coluna].astype(str).str.strip()
        # Volta para NaN se ficou vazio
        df_limpo[coluna] = df_limpo[coluna].replace('nan', np.nan)
    
    # Corrige idades impossíveis
    df_limpo.loc[df_limpo['idade'] < 0, 'idade'] = np.nan
    df_limpo.loc[df_limpo['idade'] > 120, 'idade'] = np.nan
    
    # Corrige salários negativos
    df_limpo.loc[df_limpo['salario'] < 0, 'salario'] = np.nan
    
    return df_limpo

df_limpo = limpar_dados_basico(df)
print("Dados após limpeza:")
print(df_limpo)

Meu Relatório de Qualidade

Sempre faço um relatório para documentar o que encontrei:

def meu_relatorio_qualidade(df_original, df_limpo):
    print("=" * 50)
    print("RELATÓRIO DE QUALIDADE DOS DADOS")
    print("=" * 50)
    
    print(f"Registros originais: {len(df_original)}")
    print(f"Registros após limpeza: {len(df_limpo)}")
    print(f"Registros removidos: {len(df_original) - len(df_limpo)}")
    
    print("\nCompletude por coluna (após limpeza):")
    for coluna in df_limpo.columns:
        nulos = df_limpo[coluna].isnull().sum()
        percentual = (1 - nulos/len(df_limpo)) * 100
        print(f"  {coluna}: {percentual:.1f}% completo")
    
    # Duplicatas
    duplicatas = df_limpo.duplicated().sum()
    print(f"\nRegistros duplicados: {duplicatas}")
    
    print("=" * 50)

meu_relatorio_qualidade(df, df_limpo)

Validação Rápida que Uso Todo Dia

Para o dia a dia, criei uma função que roda tudo de uma vez:

def validacao_rapida(df):
    print("🔍 VALIDAÇÃO RÁPIDA DOS DADOS\n")
    
    # 1. Valores nulos
    nulos = df.isnull().sum()
    print("📊 Valores nulos:")
    for coluna, qtd in nulos.items():
        if qtd > 0:
            print(f"  - {coluna}: {qtd} ({qtd/len(df)*100:.1f}%)")
    
    # 2. Strings vazias
    print("\n📝 Strings vazias:")
    for coluna in df.select_dtypes(include=['object']).columns:
        vazias = (df[coluna].astype(str).str.strip() == '').sum()
        if vazias > 0:
            print(f"  - {coluna}: {vazias}")
    
    # 3. Duplicatas
    duplicatas = df.duplicated().sum()
    if duplicatas > 0:
        print(f"\n📋 Registros duplicados: {duplicatas}")
    
    # 4. Valores suspeitos em colunas numéricas
    print("\n🔢 Valores suspeitos:")
    for coluna in df.select_dtypes(include=['number']).columns:
        negativos = (df[coluna] < 0).sum()
        if negativos > 0:
            print(f"  - {coluna}: {negativos} valores negativos")
        
        zeros = (df[coluna] == 0).sum()
        if zeros > 0:
            print(f"  - {coluna}: {zeros} valores zero")

# Testando com nossos dados
validacao_rapida(df)

Dicas que Aprendi na Prática

  1. Sempre salve os dados originais:
df_original = df.copy()  # Faça isso SEMPRE
  1. Documente o que você encontrou:
# Mantém um log dos problemas
problemas_encontrados = []
problemas_encontrados.append("5 idades negativas corrigidas")
  1. Para datasets grandes, use amostragem:
# Para datasets enormes, valide uma amostra primeiro
amostra = df.sample(1000)
validacao_rapida(amostra)
  1. Crie suas próprias regras de negócio:
# Exemplo: salário não pode ser maior que 100k
salarios_altos = df[df['salario'] > 100000]
if not salarios_altos.empty:
    print(f"Salários suspeitos: {len(salarios_altos)}")

O Código Completo para Copiar

Aqui está tudo junto, pronto para usar:

import pandas as pd
import numpy as np

def validacao_completa_simples(df):
    """
    Função que uso em todos os meus projetos
    """
    print("🔍 VALIDANDO DADOS...\n")
    
    problemas = []
    
    # 1. Valores nulos
    nulos = df.isnull().sum()
    for coluna, qtd in nulos.items():
        if qtd > 0:
            problemas.append(f"Coluna '{coluna}': {qtd} valores nulos")
    
    # 2. Strings vazias
    for coluna in df.select_dtypes(include=['object']).columns:
        vazias = (df[coluna].astype(str).str.strip() == '').sum()
        if vazias > 0:
            problemas.append(f"Coluna '{coluna}': {vazias} strings vazias")
    
    # 3. Valores suspeitos
    for coluna in df.select_dtypes(include=['number']).columns:
        negativos = (df[coluna] < 0).sum()
        if negativos > 0:
            problemas.append(f"Coluna '{coluna}': {negativos} valores negativos")
    
    # 4. Duplicatas
    duplicatas = df.duplicated().sum()
    if duplicatas > 0:
        problemas.append(f"Registros duplicados: {duplicatas}")
    
    # 5. Limpeza básica
    df_limpo = df.copy()
    df_limpo = df_limpo.replace('', np.nan)
    df_limpo = df_limpo.dropna(how='all')
    
    # Limpa espaços
    for coluna in df_limpo.select_dtypes(include=['object']).columns:
        df_limpo[coluna] = df_limpo[coluna].astype(str).str.strip()
        df_limpo[coluna] = df_limpo[coluna].replace('nan', np.nan)
    
    print(f"✅ RESULTADO:")
    print(f"  - Registros originais: {len(df)}")
    print(f"  - Registros limpos: {len(df_limpo)}")
    print(f"  - Problemas encontrados: {len(problemas)}")
    
    if problemas:
        print("\n⚠️ PROBLEMAS ENCONTRADOS:")
        for problema in problemas:
            print(f"  - {problema}")
    
    return df_limpo, problemas

# Exemplo de uso
df_limpo, lista_problemas = validacao_completa_simples(df)

Próximos Passos

Quando você dominar isso, pode partir para:

  • Validação de CPF/CNPJ
  • Consistência entre colunas (ex: data de nascimento vs idade)
  • Detectar outliers automaticamente
  • Validação de endereços

Conclusão

Validação de dados pode parecer chato, mas vai te salvar de muita dor de cabeça. Comece simples, com as verificações básicas que mostrei aqui.

Lembre-se: é melhor passar 10 minutos validando do que 2 horas explicando por que sua análise está errada.

Espero que tenha ajudado! Se tiver alguma dúvida ou quiser compartilhar suas próprias experiências com dados bagunçados, deixa um comentário aí embaixo.