Notebook de Análise e Tratamento de Dados¶

Demonstração Prática - Base Sintética¶

Este notebook demonstra as técnicas de tratamento de dados ensinadas no E-Book, usando uma base sintética com problemas realistas.

In [4]:
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import re
from datetime import datetime
import warnings
warnings.filterwarnings('ignore')

plt.style.use('seaborn-v0_8')
plt.rcParams['figure.figsize'] = (12, 8)

print("Bibliotecas importadas com sucesso!")
Bibliotecas importadas com sucesso!

1. Carregando e Explorando a Base de Dados¶

In [5]:
# Carregando a base de dados
df = pd.read_csv('base_dados_sintetica_suja.csv')

print(f"Shape da base: {df.shape}")
print(f"Colunas: {list(df.columns)}")
print("\nPrimeiras 5 linhas:")
df.head()
Shape da base: (10800, 12)
Colunas: ['id', 'nome', 'email', 'cpf', 'telefone', 'data_cadastro', 'idade', 'renda', 'estado', 'cidade', 'status', 'valor_compra']

Primeiras 5 linhas:
Out[5]:
id nome email cpf telefone data_cadastro idade renda estado cidade status valor_compra
0 1 Brenda Alves samuel32@example.net.invalid 36295062838123 (91) 9116-68732 15-05-2025 56.0 1434.0 CE Recife Ativo 376.0
1 2 Yago Borges bandradeexample.org 20974745121 (61) 9461-64955 00/00/0000 NaN 7067228.0 RS São Paulo Pendente 1801.0
2 3 Igor Montenegro JR novaismiguel@example.net 995.619.255-56 NaN NaN 64.0 5010.0 BA Porto Alegre Pendente -10000.0
3 4 NaN manuelapastor@example.com 43788280537 (71) 9862-82117 2023-13-45 65.0 8019.0 BA Belo Horizonte Ativo -50000.0
4 5 Emanuel Sampaio rfernandes@example.org 66813220242 (11) 9913-06093 2021-09-22 67.0 -999999.0 PR Belo Horizonte Ativo 661.0

2. Diagnóstico Inicial - Identificando Problemas¶

In [6]:
# Função para análise de valores únicos
def analisar_coluna(coluna, df):
    print(f"\n=== ANÁLISE DA COLUNA: {coluna.upper()} ===")
    print(f"Tipo de dados: {df[coluna].dtype}")
    print(f"Valores únicos: {df[coluna].nunique()}")
    print(f"Valores nulos: {df[coluna].isnull().sum()}")
    
    if df[coluna].dtype == 'object':
        print("\nTop 10 valores mais frequentes:")
        print(df[coluna].value_counts().head(10))
    else:
        print(f"\nEstatísticas:")
        print(f"Mínimo: {df[coluna].min()}")
        print(f"Máximo: {df[coluna].max()}")
        print(f"Média: {df[coluna].mean():.2f}")
        print(f"Mediana: {df[coluna].median():.2f}")

# Analisando cada coluna
for coluna in df.columns:
    analisar_coluna(coluna, df)
=== ANÁLISE DA COLUNA: ID ===
Tipo de dados: int64
Valores únicos: 10799
Valores nulos: 0

Estatísticas:
Mínimo: 1
Máximo: 10799
Média: 5400.43
Mediana: 5400.50

=== ANÁLISE DA COLUNA: NOME ===
Tipo de dados: object
Valores únicos: 8339
Valores nulos: 1093

Top 10 valores mais frequentes:
nome
Dr....    14
Ana...    12
Sr....    11
Mar...     9
Sra...     9
Dra...     8
Srt...     7
Car...     5
Cec...     5
Joã...     5
Name: count, dtype: int64

=== ANÁLISE DA COLUNA: EMAIL ===
Tipo de dados: object
Valores únicos: 8069
Valores nulos: 761

Top 10 valores mais frequentes:
email
teste@teste.com         233
invalid-email           218
user@domain             208
exemplo@exemplo.com     196
@example.com             87
@example.net             68
@example.org             60
esampaio@example.org      4
hda-rosa@example.com      4
lmendes@example.org       4
Name: count, dtype: int64

=== ANÁLISE DA COLUNA: CPF ===
Tipo de dados: object
Valores únicos: 8520
Valores nulos: 627

Top 10 valores mais frequentes:
cpf
11111111111       224
99999999999       213
123.456.789-AB    187
00000000000       182
ABC12345678       175
75727950065         2
28044796875         2
62468578070         2
23252640664         2
10560486873         2
Name: count, dtype: int64

=== ANÁLISE DA COLUNA: TELEFONE ===
Tipo de dados: object
Valores únicos: 8620
Valores nulos: 979

Top 10 valores mais frequentes:
telefone
ABC123456          294
000000000          254
(91) 9116-68732      2
(71) 9452-30811      2
(41) 929153182       2
(51) 9729-90827      2
(81) 9729-36603      2
(31) 9323-10103      2
(71) 9778-72047      2
(61) 9105-42619      2
Name: count, dtype: int64

=== ANÁLISE DA COLUNA: DATA_CADASTRO ===
Tipo de dados: object
Valores únicos: 2229
Valores nulos: 315

Top 10 valores mais frequentes:
data_cadastro
01/01/1900    166
00/00/0000    148
2023-13-45    144
32/13/2023    141
31/12/2030    126
2024-08-04     16
2020-11-11     15
2025-03-29     14
2023-09-18     13
2021-05-19     12
Name: count, dtype: int64

=== ANÁLISE DA COLUNA: IDADE ===
Tipo de dados: float64
Valores únicos: 195
Valores nulos: 443

Estatísticas:
Mínimo: -50.0
Máximo: 999.0
Média: 64.24
Mediana: 49.00

=== ANÁLISE DA COLUNA: RENDA ===
Tipo de dados: float64
Valores únicos: 5964
Valores nulos: 515

Estatísticas:
Mínimo: -999999.0
Máximo: 999999999.0
Média: 7199909.09
Mediana: 5199.00

=== ANÁLISE DA COLUNA: ESTADO ===
Tipo de dados: object
Valores únicos: 35
Valores nulos: 921

Top 10 valores mais frequentes:
estado
PE    806
RS    804
CE    799
MG    786
SC    782
GO    782
PR    768
RJ    764
SP    759
BA    737
Name: count, dtype: int64

=== ANÁLISE DA COLUNA: CIDADE ===
Tipo de dados: object
Valores únicos: 166
Valores nulos: 1254

Top 10 valores mais frequentes:
cidade
Brasília          891
Manaus            885
Fortaleza         871
São Paulo         865
Recife            855
Curitiba          838
Rio de Janeiro    825
Porto Alegre      812
Belo Horizonte    794
Salvador          792
Name: count, dtype: int64

=== ANÁLISE DA COLUNA: STATUS ===
Tipo de dados: object
Valores únicos: 33
Valores nulos: 1389

Top 10 valores mais frequentes:
status
Pendente     2101
Bloqueado    2090
Ativo        2077
Inativo      1981
STATUS        311
ATIVO          79
bloqueado      76
pendente       75
BLOQUEADO      74
INATIVO        73
Name: count, dtype: int64

=== ANÁLISE DA COLUNA: VALOR_COMPRA ===
Tipo de dados: float64
Valores únicos: 2339
Valores nulos: 693

Estatísticas:
Mínimo: -999999.0
Máximo: 999999999.0
Média: 5139469.17
Mediana: 1007.00

3. Identificando Problemas Específicos¶

In [7]:
# Problemas identificados
print("=== PROBLEMAS IDENTIFICADOS ===")

# 1. Dados duplicados
duplicatas = df.duplicated().sum()
print(f"1. Registros duplicados: {duplicatas}")

# 2. Emails inválidos
emails_invalidos = df[~df['email'].str.contains('@', na=False)].shape[0]
print(f"2. Emails inválidos: {emails_invalidos}")

# 3. CPFs com problemas
cpfs_problemas = df[df['cpf'].str.len() != 11].shape[0]
print(f"3. CPFs com problemas: {cpfs_problemas}")

# 4. Telefones sem DDD
telefones_sem_ddd = df[~df['telefone'].str.contains(r'\(', na=False)].shape[0]
print(f"4. Telefones sem DDD: {telefones_sem_ddd}")

# 5. Idades impossíveis
idades_impossiveis = df[(df['idade'] < 0) | (df['idade'] > 120)].shape[0]
print(f"5. Idades impossíveis: {idades_impossiveis}")

# 6. Rendas negativas
rendas_negativas = df[df['renda'] < 0].shape[0]
print(f"6. Rendas negativas: {rendas_negativas}")

# 7. Valores de compra negativos
valores_negativos = df[df['valor_compra'] < 0].shape[0]
print(f"7. Valores de compra negativos: {valores_negativos}")
=== PROBLEMAS IDENTIFICADOS ===
1. Registros duplicados: 0
2. Emails inválidos: 1413
3. CPFs com problemas: 1372
4. Telefones sem DDD: 3021
5. Idades impossíveis: 785
6. Rendas negativas: 598
7. Valores de compra negativos: 389

4. Visualizando os Problemas¶

In [8]:
# Criando visualizações dos problemas
fig, axes = plt.subplots(2, 3, figsize=(18, 12))
fig.suptitle('Análise de Problemas na Base de Dados', fontsize=16, fontweight='bold')

# 1. Distribuição de idades
axes[0,0].hist(df['idade'].dropna(), bins=30, alpha=0.7)
axes[0,0].set_title('Distribuição de Idades')
axes[0,0].set_xlabel('Idade')
axes[0,0].set_ylabel('Frequência')

# 2. Distribuição de rendas
axes[0,1].hist(df['renda'].dropna(), bins=30, alpha=0.7)
axes[0,1].set_title('Distribuição de Rendas')
axes[0,1].set_xlabel('Renda')
axes[0,1].set_ylabel('Frequência')

# 3. Distribuição de valores de compra
axes[0,2].hist(df['valor_compra'].dropna(), bins=30, alpha=0.7)
axes[0,2].set_title('Distribuição de Valores de Compra')
axes[0,2].set_xlabel('Valor')
axes[0,2].set_ylabel('Frequência')

# 4. Estados mais frequentes
estados_counts = df['estado'].value_counts().head(10)
axes[1,0].bar(range(len(estados_counts)), estados_counts.values)
axes[1,0].set_title('Top 10 Estados')
axes[1,0].set_ylabel('Quantidade')
axes[1,0].tick_params(axis='x', rotation=45)

# 5. Status dos clientes
status_counts = df['status'].value_counts()
axes[1,1].pie(status_counts.values, labels=status_counts.index, autopct='%1.1f%%')
axes[1,1].set_title('Distribuição de Status')

# 6. Problemas identificados
problemas = ['Duplicatas', 'Emails Inválidos', 'CPFs Problemas', 'Telefones Sem DDD', 'Idades Impossíveis', 'Rendas Negativas']
valores = [duplicatas, emails_invalidos, cpfs_problemas, telefones_sem_ddd, idades_impossiveis, rendas_negativas]
axes[1,2].bar(problemas, valores, color='red', alpha=0.7)
axes[1,2].set_title('Quantidade de Problemas')
axes[1,2].tick_params(axis='x', rotation=45)

plt.tight_layout()
plt.show()
No description has been provided for this image

5. Tratamento de Dados - Passo a Passo¶

In [9]:
# Criando uma cópia para tratamento
df_limpo = df.copy()
print("Iniciando tratamento de dados...")
print(f"Registros iniciais: {len(df_limpo)}")
Iniciando tratamento de dados...
Registros iniciais: 10800

5.1 Análise Detalhada de Dados Nulos¶

In [10]:
# Análise detalhada de dados nulos
print("=== ANÁLISE DE DADOS NULOS ===")

# 1. Total de nulos por coluna
nulos_por_coluna = df_limpo.isnull().sum()
print("\n1. Total de nulos por coluna:")
for coluna, nulos in nulos_por_coluna.items():
    if nulos > 0:
        pct = (nulos / len(df_limpo)) * 100
        print(f"   {coluna}: {nulos} ({pct:.1f}%)")

# 2. Registros com pelo menos um nulo
registros_com_nulos = df_limpo.isnull().any(axis=1).sum()
print(f"\n2. Registros com pelo menos um nulo: {registros_com_nulos}")

# 3. Padrões de nulos (quais colunas têm nulos juntos)
print("\n3. Análise de padrões de nulos:")
colunas_com_nulos = df_limpo.columns[df_limpo.isnull().any()].tolist()
for i, col1 in enumerate(colunas_com_nulos):
    for col2 in colunas_com_nulos[i+1:]:
        ambos_nulos = df_limpo[df_limpo[col1].isnull() & df_limpo[col2].isnull()].shape[0]
        if ambos_nulos > 0:
            print(f"   {col1} e {col2}: {ambos_nulos} registros com ambos nulos")
=== ANÁLISE DE DADOS NULOS ===

1. Total de nulos por coluna:
   nome: 1093 (10.1%)
   email: 761 (7.0%)
   cpf: 627 (5.8%)
   telefone: 979 (9.1%)
   data_cadastro: 315 (2.9%)
   idade: 443 (4.1%)
   renda: 515 (4.8%)
   estado: 921 (8.5%)
   cidade: 1254 (11.6%)
   status: 1389 (12.9%)
   valor_compra: 693 (6.4%)

2. Registros com pelo menos um nulo: 6288

3. Análise de padrões de nulos:
   nome e email: 83 registros com ambos nulos
   nome e cpf: 48 registros com ambos nulos
   nome e telefone: 116 registros com ambos nulos
   nome e data_cadastro: 34 registros com ambos nulos
   nome e idade: 32 registros com ambos nulos
   nome e renda: 55 registros com ambos nulos
   nome e estado: 94 registros com ambos nulos
   nome e cidade: 124 registros com ambos nulos
   nome e status: 146 registros com ambos nulos
   nome e valor_compra: 71 registros com ambos nulos
   email e cpf: 48 registros com ambos nulos
   email e telefone: 58 registros com ambos nulos
   email e data_cadastro: 21 registros com ambos nulos
   email e idade: 29 registros com ambos nulos
   email e renda: 40 registros com ambos nulos
   email e estado: 59 registros com ambos nulos
   email e cidade: 94 registros com ambos nulos
   email e status: 101 registros com ambos nulos
   email e valor_compra: 55 registros com ambos nulos
   cpf e telefone: 61 registros com ambos nulos
   cpf e data_cadastro: 16 registros com ambos nulos
   cpf e idade: 24 registros com ambos nulos
   cpf e renda: 38 registros com ambos nulos
   cpf e estado: 51 registros com ambos nulos
   cpf e cidade: 83 registros com ambos nulos
   cpf e status: 93 registros com ambos nulos
   cpf e valor_compra: 48 registros com ambos nulos
   telefone e data_cadastro: 33 registros com ambos nulos
   telefone e idade: 37 registros com ambos nulos
   telefone e renda: 38 registros com ambos nulos
   telefone e estado: 76 registros com ambos nulos
   telefone e cidade: 107 registros com ambos nulos
   telefone e status: 127 registros com ambos nulos
   telefone e valor_compra: 64 registros com ambos nulos
   data_cadastro e idade: 27 registros com ambos nulos
   data_cadastro e renda: 19 registros com ambos nulos
   data_cadastro e estado: 33 registros com ambos nulos
   data_cadastro e cidade: 38 registros com ambos nulos
   data_cadastro e status: 43 registros com ambos nulos
   data_cadastro e valor_compra: 26 registros com ambos nulos
   idade e renda: 12 registros com ambos nulos
   idade e estado: 41 registros com ambos nulos
   idade e cidade: 52 registros com ambos nulos
   idade e status: 48 registros com ambos nulos
   idade e valor_compra: 23 registros com ambos nulos
   renda e estado: 49 registros com ambos nulos
   renda e cidade: 53 registros com ambos nulos
   renda e status: 55 registros com ambos nulos
   renda e valor_compra: 31 registros com ambos nulos
   estado e cidade: 112 registros com ambos nulos
   estado e status: 113 registros com ambos nulos
   estado e valor_compra: 61 registros com ambos nulos
   cidade e status: 168 registros com ambos nulos
   cidade e valor_compra: 85 registros com ambos nulos
   status e valor_compra: 64 registros com ambos nulos

5.2 Tratamento de Dados Nulos¶

In [11]:
# Tratamento de dados nulos
print("=== TRATAMENTO DE DADOS NULOS ===")

# 1. Remover registros com muitos nulos (>50% das colunas)
print("\n1. Removendo registros com muitos nulos...")
limite_nulos = len(df_limpo.columns) * 0.5
registros_muitos_nulos = df_limpo[df_limpo.isnull().sum(axis=1) > limite_nulos].shape[0]
print(f"   Registros com >50% de nulos: {registros_muitos_nulos}")

df_limpo = df_limpo[df_limpo.isnull().sum(axis=1) <= limite_nulos]
print(f"   Registros após remoção: {len(df_limpo)}")

# 2. Preencher nulos com estratégias específicas
print("\n2. Preenchendo nulos com estratégias específicas:")

# Nomes: preencher com 'Nome não informado'
if df_limpo['nome'].isnull().sum() > 0:
    df_limpo['nome'] = df_limpo['nome'].fillna('Nome não informado')
    print(f"   Nomes nulos preenchidos: {df_limpo['nome'].isnull().sum()}")

# Emails: remover registros (não é possível inferir)
if df_limpo['email'].isnull().sum() > 0:
    df_limpo = df_limpo[df_limpo['email'].notna()]
    print(f"   Registros com email nulo removidos: {df_limpo['email'].isnull().sum()}")

# CPFs: remover registros (não é possível inferir)
if df_limpo['cpf'].isnull().sum() > 0:
    df_limpo = df_limpo[df_limpo['cpf'].notna()]
    print(f"   Registros com CPF nulo removidos: {df_limpo['cpf'].isnull().sum()}")

# Telefones: preencher com 'Não informado'
if df_limpo['telefone'].isnull().sum() > 0:
    df_limpo['telefone'] = df_limpo['telefone'].fillna('Não informado')
    print(f"   Telefones nulos preenchidos: {df_limpo['telefone'].isnull().sum()}")

# Idades: preencher com mediana
if df_limpo['idade'].isnull().sum() > 0:
    mediana_idade = df_limpo['idade'].median()
    df_limpo['idade'] = df_limpo['idade'].fillna(mediana_idade)
    print(f"   Idades nulas preenchidas com mediana: {df_limpo['idade'].isnull().sum()}")

# Rendas: preencher com mediana
if df_limpo['renda'].isnull().sum() > 0:
    mediana_renda = df_limpo['renda'].median()
    df_limpo['renda'] = df_limpo['renda'].fillna(mediana_renda)
    print(f"   Rendas nulas preenchidas com mediana: {df_limpo['renda'].isnull().sum()}")

# Estados: preencher com moda
if df_limpo['estado'].isnull().sum() > 0:
    moda_estado = df_limpo['estado'].mode()[0]
    df_limpo['estado'] = df_limpo['estado'].fillna(moda_estado)
    print(f"   Estados nulos preenchidos com moda: {df_limpo['estado'].isnull().sum()}")

# Cidades: preencher com moda
if df_limpo['cidade'].isnull().sum() > 0:
    moda_cidade = df_limpo['cidade'].mode()[0]
    df_limpo['cidade'] = df_limpo['cidade'].fillna(moda_cidade)
    print(f"   Cidades nulas preenchidas com moda: {df_limpo['cidade'].isnull().sum()}")

# Status: preencher com 'Pendente'
if df_limpo['status'].isnull().sum() > 0:
    df_limpo['status'] = df_limpo['status'].fillna('Pendente')
    print(f"   Status nulos preenchidos: {df_limpo['status'].isnull().sum()}")

# Valores de compra: preencher com mediana
if df_limpo['valor_compra'].isnull().sum() > 0:
    mediana_valor = df_limpo['valor_compra'].median()
    df_limpo['valor_compra'] = df_limpo['valor_compra'].fillna(mediana_valor)
    print(f"   Valores de compra nulos preenchidos com mediana: {df_limpo['valor_compra'].isnull().sum()}")

print(f"\n✅ Registros após tratamento de nulos: {len(df_limpo)}")
=== TRATAMENTO DE DADOS NULOS ===

1. Removendo registros com muitos nulos...
   Registros com >50% de nulos: 0
   Registros após remoção: 10800

2. Preenchendo nulos com estratégias específicas:
   Nomes nulos preenchidos: 0
   Registros com email nulo removidos: 0
   Registros com CPF nulo removidos: 0
   Telefones nulos preenchidos: 0
   Idades nulas preenchidas com mediana: 0
   Rendas nulas preenchidas com mediana: 0
   Estados nulos preenchidos com moda: 0
   Cidades nulas preenchidas com moda: 0
   Status nulos preenchidos: 0
   Valores de compra nulos preenchidos com mediana: 0

✅ Registros após tratamento de nulos: 9460

5.3 Análise de Outliers¶

In [12]:
# Análise de outliers
print("=== ANÁLISE DE OUTLIERS ===")

# Variáveis numéricas para análise
colunas_numericas = ['idade', 'renda', 'valor_compra']

for coluna in colunas_numericas:
    if coluna in df_limpo.columns:
        print(f"\n📊 Análise de outliers em {coluna}:")
        
        # Estatísticas básicas
        Q1 = df_limpo[coluna].quantile(0.25)
        Q3 = df_limpo[coluna].quantile(0.75)
        IQR = Q3 - Q1
        limite_inferior = Q1 - 1.5 * IQR
        limite_superior = Q3 + 1.5 * IQR
        
        outliers = df_limpo[(df_limpo[coluna] < limite_inferior) | (df_limpo[coluna] > limite_superior)]
        
        print(f"   Mínimo: {df_limpo[coluna].min():.2f}")
        print(f"   Q1: {Q1:.2f}")
        print(f"   Mediana: {df_limpo[coluna].median():.2f}")
        print(f"   Q3: {Q3:.2f}")
        print(f"   Máximo: {df_limpo[coluna].max():.2f}")
        print(f"   IQR: {IQR:.2f}")
        print(f"   Limite inferior: {limite_inferior:.2f}")
        print(f"   Limite superior: {limite_superior:.2f}")
        print(f"   Outliers encontrados: {len(outliers)} ({len(outliers)/len(df_limpo)*100:.1f}%)")
        
        # Mostrar alguns exemplos de outliers
        if len(outliers) > 0:
            print(f"   Exemplos de outliers: {outliers[coluna].head(5).tolist()}")
=== ANÁLISE DE OUTLIERS ===

📊 Análise de outliers em idade:
   Mínimo: -50.00
   Q1: 32.00
   Mediana: 49.00
   Q3: 66.00
   Máximo: 999.00
   IQR: 34.00
   Limite inferior: -19.00
   Limite superior: 117.00
   Outliers encontrados: 594 (6.3%)
   Exemplos de outliers: [-25.0, 300.0, 145.0, -50.0, 200.0]

📊 Análise de outliers em renda:
   Mínimo: -999999.00
   Q1: 2632.00
   Mediana: 5185.00
   Q3: 7768.25
   Máximo: 999999999.00
   IQR: 5136.25
   Limite inferior: -5072.38
   Limite superior: 15472.62
   Outliers encontrados: 842 (8.9%)
   Exemplos de outliers: [7067228.0, -999999.0, -999999.0, -100000.0, 2242892.0]

📊 Análise de outliers em valor_compra:
   Mínimo: -999999.00
   Q1: 492.75
   Mediana: 1009.00
   Q3: 1513.00
   Máximo: 999999999.00
   IQR: 1020.25
   Limite inferior: -1037.62
   Limite superior: 3043.38
   Outliers encontrados: 704 (7.4%)
   Exemplos de outliers: [-10000.0, -50000.0, -1095.0, 999999999.0, 379795.0]

5.4 Tratamento de Outliers¶

In [13]:
# Tratamento de outliers
print("=== TRATAMENTO DE OUTLIERS ===")

# Criar cópia antes do tratamento
df_sem_outliers = df_limpo.copy()

for coluna in colunas_numericas:
    if coluna in df_sem_outliers.columns:
        print(f"\n🔧 Tratando outliers em {coluna}:")
        
        # Calcular limites
        Q1 = df_sem_outliers[coluna].quantile(0.25)
        Q3 = df_sem_outliers[coluna].quantile(0.75)
        IQR = Q3 - Q1
        limite_inferior = Q1 - 1.5 * IQR
        limite_superior = Q3 + 1.5 * IQR
        
        # Contar outliers antes
        outliers_antes = df_sem_outliers[(df_sem_outliers[coluna] < limite_inferior) | 
                                           (df_sem_outliers[coluna] > limite_superior)].shape[0]
        
        # Estratégia 1: Winsorização (capar os valores)
        df_sem_outliers[coluna] = df_sem_outliers[coluna].clip(limite_inferior, limite_superior)
        
        # Contar outliers depois
        outliers_depois = df_sem_outliers[(df_sem_outliers[coluna] < limite_inferior) | 
                                            (df_sem_outliers[coluna] > limite_superior)].shape[0]
        
        print(f"   Outliers antes: {outliers_antes}")
        print(f"   Outliers depois: {outliers_depois}")
        print(f"   Valores ajustados: {outliers_antes - outliers_depois}")

print(f"\n✅ Registros após tratamento de outliers: {len(df_sem_outliers)}")
=== TRATAMENTO DE OUTLIERS ===

🔧 Tratando outliers em idade:
   Outliers antes: 594
   Outliers depois: 0
   Valores ajustados: 594

🔧 Tratando outliers em renda:
   Outliers antes: 842
   Outliers depois: 0
   Valores ajustados: 842

🔧 Tratando outliers em valor_compra:
   Outliers antes: 704
   Outliers depois: 0
   Valores ajustados: 704

✅ Registros após tratamento de outliers: 9460

5.5 Removendo Duplicatas¶

In [14]:
# Removendo duplicatas
print("=== REMOVENDO DUPLICATAS ===")
print(f"Duplicatas encontradas: {df_limpo.duplicated().sum()}")

df_limpo = df_limpo.drop_duplicates()
print(f"Registros após remoção: {len(df_limpo)}")
print(f"Registros removidos: {len(df) - len(df_limpo)}")
=== REMOVENDO DUPLICATAS ===
Duplicatas encontradas: 0
Registros após remoção: 9460
Registros removidos: 1340

5.2 Padronizando Nomes¶

In [15]:
# Padronizando nomes
print("=== PADRONIZANDO NOMES ===")

def padronizar_nome(nome):
    if pd.isna(nome):
        return nome
    
    # Remover espaços extras
    nome = str(nome).strip()
    
    # Converter para título (primeira letra maiúscula)
    nome = nome.title()
    
    # Corrigir caracteres especiais
    nome = nome.replace('ã', 'ã').replace('ç', 'ç')
    
    return nome

# Aplicando padronização
df_limpo['nome'] = df_limpo['nome'].apply(padronizar_nome)

print("Exemplos de nomes padronizados:")
print(df_limpo['nome'].head(10))
=== PADRONIZANDO NOMES ===
Exemplos de nomes padronizados:
0                  Brenda Alves
1                   Yago Borges
2            Igor Montenegro Jr
3            Nome Não Informado
4               Emanuel Sampaio
5                Lucca Caldeira
6    Dr. Augusto Siqueira Silva
7            Nome Não Informado
8             João Vitor Barros
9            Maria Clara Castro
Name: nome, dtype: object

5.3 Limpando Emails¶

In [16]:
# Limpando emails
print("=== LIMPANDO EMAILS ===")

def limpar_email(email):
    if pd.isna(email):
        return email
    
    email = str(email).strip().lower()
    
    # Verificar se é válido
    if '@' not in email or email.count('@') != 1:
        return np.nan
    
    # Remover emails temporários
    if any(temp in email for temp in ['temp', 'teste', 'exemplo']):
        return np.nan
    
    return email

# Aplicando limpeza
df_limpo['email'] = df_limpo['email'].apply(limpar_email)

print(f"Emails válidos após limpeza: {df_limpo['email'].notna().sum()}")
print(f"Emails removidos: {df_limpo['email'].isna().sum()}")
=== LIMPANDO EMAILS ===
Emails válidos após limpeza: 8436
Emails removidos: 1024

5.4 Validando CPFs¶

In [17]:
# Validando CPFs
print("=== VALIDANDO CPFS ===")

def validar_cpf(cpf):
    if pd.isna(cpf):
        return np.nan
    
    cpf = str(cpf).replace('.', '').replace('-', '')
    
    # Verificar se tem 11 dígitos
    if len(cpf) != 11:
        return np.nan
    
    # Verificar se são todos iguais
    if cpf == cpf[0] * 11:
        return np.nan
    
    # Validar CPF (algoritmo simplificado)
    try:
        # Calcular primeiro dígito verificador
        soma = sum(int(cpf[i]) * (10 - i) for i in range(9))
        digito1 = (soma * 10) % 11
        if digito1 == 10:
            digito1 = 0
        
        # Calcular segundo dígito verificador
        soma = sum(int(cpf[i]) * (11 - i) for i in range(10))
        digito2 = (soma * 10) % 11
        if digito2 == 10:
            digito2 = 0
        
        # Verificar se os dígitos verificadores estão corretos
        if int(cpf[9]) == digito1 and int(cpf[10]) == digito2:
            return f"{cpf[:3]}.{cpf[3:6]}.{cpf[6:9]}-{cpf[9:]}"
        else:
            return np.nan
    except:
        return np.nan

# Aplicando validação
df_limpo['cpf'] = df_limpo['cpf'].apply(validar_cpf)

print(f"CPFs válidos após validação: {df_limpo['cpf'].notna().sum()}")
print(f"CPFs removidos: {df_limpo['cpf'].isna().sum()}")
=== VALIDANDO CPFS ===
CPFs válidos após validação: 81
CPFs removidos: 9379

5.5 Formatando Telefones¶

In [18]:
# Formatando telefones
print("=== FORMATANDO TELEFONES ===")

def formatar_telefone(telefone):
    if pd.isna(telefone):
        return telefone
    
    telefone = str(telefone).replace('(', '').replace(')', '').replace('-', '').replace(' ', '')
    
    # Verificar se tem pelo menos 10 dígitos
    if len(telefone) < 10:
        return np.nan
    
    # Se não tem DDD, adicionar um padrão
    if len(telefone) == 9:
        telefone = '11' + telefone
    
    # Formatar
    if len(telefone) == 11:
        return f"({telefone[:2]}) {telefone[2:6]}-{telefone[6:]}"
    else:
        return np.nan

# Aplicando formatação
df_limpo['telefone'] = df_limpo['telefone'].apply(formatar_telefone)

print(f"Telefones válidos após formatação: {df_limpo['telefone'].notna().sum()}")
print(f"Telefones removidos: {df_limpo['telefone'].isna().sum()}")
=== FORMATANDO TELEFONES ===
Telefones válidos após formatação: 7293
Telefones removidos: 2167

5.6 Padronizando Datas¶

In [19]:
# Padronizando datas
print("=== PADRONIZANDO DATAS ===")

def padronizar_data(data):
    if pd.isna(data) or data == '':
        return np.nan
    
    data = str(data).strip()
    
    # Tentar diferentes formatos
    formatos = ['%d/%m/%Y', '%Y-%m-%d', '%d-%m-%Y', '%Y/%m/%d']
    
    for formato in formatos:
        try:
            data_obj = datetime.strptime(data, formato)
            # Verificar se a data é razoável
            if 1900 <= data_obj.year <= 2024:
                return data_obj.strftime('%d/%m/%Y')
        except:
            continue
    
    return np.nan

# Aplicando padronização
df_limpo['data_cadastro'] = df_limpo['data_cadastro'].apply(padronizar_data)

print(f"Datas válidas após padronização: {df_limpo['data_cadastro'].notna().sum()}")
print(f"Datas removidas: {df_limpo['data_cadastro'].isna().sum()}")
=== PADRONIZANDO DATAS ===
Datas válidas após padronização: 7684
Datas removidas: 1776

5.7 Limpando Valores Numéricos¶

In [20]:
# Limpando valores numéricos
print("=== LIMPANDO VALORES NUMÉRICOS ===")

# Idade
df_limpo['idade'] = pd.to_numeric(df_limpo['idade'], errors='coerce')
df_limpo.loc[(df_limpo['idade'] < 0) | (df_limpo['idade'] > 120), 'idade'] = np.nan

# Renda
df_limpo['renda'] = pd.to_numeric(df_limpo['renda'], errors='coerce')
df_limpo.loc[df_limpo['renda'] < 0, 'renda'] = np.nan

# Valor de compra
df_limpo['valor_compra'] = pd.to_numeric(df_limpo['valor_compra'], errors='coerce')
df_limpo.loc[df_limpo['valor_compra'] < 0, 'valor_compra'] = np.nan

print(f"Idades válidas: {df_limpo['idade'].notna().sum()}")
print(f"Rendas válidas: {df_limpo['renda'].notna().sum()}")
print(f"Valores de compra válidos: {df_limpo['valor_compra'].notna().sum()}")
=== LIMPANDO VALORES NUMÉRICOS ===
Idades válidas: 8762
Rendas válidas: 8925
Valores de compra válidos: 9115

5.8 Padronizando Estados e Cidades¶

In [21]:
# Padronizando estados e cidades
print("=== PADRONIZANDO ESTADOS E CIDADES ===")

def padronizar_texto(texto):
    if pd.isna(texto) or texto == '':
        return np.nan
    
    texto = str(texto).strip().title()
    
    # Mapeamento de variações
    mapeamento = {
        'SP': 'São Paulo',
        'RJ': 'Rio de Janeiro',
        'MG': 'Minas Gerais',
        'BA': 'Bahia',
        'PR': 'Paraná'
    }
    
    return mapeamento.get(texto, texto)

# Aplicando padronização
df_limpo['estado'] = df_limpo['estado'].apply(padronizar_texto)
df_limpo['cidade'] = df_limpo['cidade'].apply(padronizar_texto)
df_limpo['status'] = df_limpo['status'].apply(padronizar_texto)

print("Padronização concluída!")
=== PADRONIZANDO ESTADOS E CIDADES ===
Padronização concluída!

6. Comparação Antes e Depois¶

In [22]:
# Comparando antes e depois
print("=== COMPARAÇÃO ANTES E DEPOIS ===")

comparacao = {
    'Métrica': [
        'Total de registros',
        'Registros duplicados',
        'Emails válidos',
        'CPFs válidos',
        'Telefones válidos',
        'Datas válidas',
        'Idades válidas',
        'Rendas válidas',
        'Valores de compra válidos'
    ],
    'Antes': [
        len(df),
        df.duplicated().sum(),
        df['email'].str.contains('@', na=False).sum(),
        df['cpf'].str.len().eq(11).sum(),
        df['telefone'].str.contains(r'\(', na=False).sum(),
        df['data_cadastro'].notna().sum(),
        df[(df['idade'] >= 0) & (df['idade'] <= 120)]['idade'].notna().sum(),
        df[df['renda'] >= 0]['renda'].notna().sum(),
        df[df['valor_compra'] >= 0]['valor_compra'].notna().sum()
    ],
    'Depois': [
        len(df_limpo),
        df_limpo.duplicated().sum(),
        df_limpo['email'].notna().sum(),
        df_limpo['cpf'].notna().sum(),
        df_limpo['telefone'].notna().sum(),
        df_limpo['data_cadastro'].notna().sum(),
        df_limpo['idade'].notna().sum(),
        df_limpo['renda'].notna().sum(),
        df_limpo['valor_compra'].notna().sum()
    ]
}

df_comparacao = pd.DataFrame(comparacao)
df_comparacao['Melhoria'] = df_comparacao['Depois'] - df_comparacao['Antes']
df_comparacao['Melhoria %'] = (df_comparacao['Melhoria'] / df_comparacao['Antes'] * 100).round(2)

print(df_comparacao)
=== COMPARAÇÃO ANTES E DEPOIS ===
                     Métrica  Antes  Depois  Melhoria  Melhoria %
0         Total de registros  10800    9460     -1340      -12.41
1       Registros duplicados      0       0         0         NaN
2             Emails válidos   9387    8436      -951      -10.13
3               CPFs válidos   9428      81     -9347      -99.14
4          Telefones válidos   7779    7293      -486       -6.25
5              Datas válidas  10485    7684     -2801      -26.71
6             Idades válidas   9572    8762      -810       -8.46
7             Rendas válidas   9687    8925      -762       -7.87
8  Valores de compra válidos   9718    9115      -603       -6.20

7. Visualizando as Melhorias¶

In [23]:
# Visualizando as melhorias
fig, axes = plt.subplots(2, 2, figsize=(15, 12))
fig.suptitle('Comparação Antes e Depois do Tratamento', fontsize=16, fontweight='bold')

# 1. Distribuição de idades
axes[0,0].hist(df['idade'].dropna(), bins=30, alpha=0.7, label='Antes', color='red')
axes[0,0].hist(df_limpo['idade'].dropna(), bins=30, alpha=0.7, label='Depois', color='green')
axes[0,0].set_title('Distribuição de Idades')
axes[0,0].set_xlabel('Idade')
axes[0,0].legend()

# 2. Distribuição de rendas
axes[0,1].hist(df['renda'].dropna(), bins=30, alpha=0.7, label='Antes', color='red')
axes[0,1].hist(df_limpo['renda'].dropna(), bins=30, alpha=0.7, label='Depois', color='green')
axes[0,1].set_title('Distribuição de Rendas')
axes[0,1].set_xlabel('Renda')
axes[0,1].legend()

# 3. Estados padronizados
estados_antes = df['estado'].value_counts().head(5)
estados_depois = df_limpo['estado'].value_counts().head(5)

x = np.arange(len(estados_antes))
width = 0.35

axes[1,0].bar(x - width/2, estados_antes.values, width, label='Antes', color='red', alpha=0.7)
axes[1,0].bar(x + width/2, estados_depois.values, width, label='Depois', color='green', alpha=0.7)
axes[1,0].set_title('Top 5 Estados')
axes[1,0].set_xticks(x)
axes[1,0].set_xticklabels(estados_antes.index, rotation=45)
axes[1,0].legend()

# 4. Melhorias percentuais
metricas = ['Emails', 'CPFs', 'Telefones', 'Datas', 'Idades', 'Rendas']
melhorias = [
    df_comparacao[df_comparacao['Métrica'] == 'Emails válidos']['Melhoria %'].iloc[0],
    df_comparacao[df_comparacao['Métrica'] == 'CPFs válidos']['Melhoria %'].iloc[0],
    df_comparacao[df_comparacao['Métrica'] == 'Telefones válidos']['Melhoria %'].iloc[0],
    df_comparacao[df_comparacao['Métrica'] == 'Datas válidas']['Melhoria %'].iloc[0],
    df_comparacao[df_comparacao['Métrica'] == 'Idades válidas']['Melhoria %'].iloc[0],
    df_comparacao[df_comparacao['Métrica'] == 'Rendas válidas']['Melhoria %'].iloc[0]
]

axes[1,1].bar(metricas, melhorias, color='blue', alpha=0.7)
axes[1,1].set_title('Melhorias Percentuais')
axes[1,1].set_ylabel('Melhoria (%)')
axes[1,1].tick_params(axis='x', rotation=45)

plt.tight_layout()
plt.show()
No description has been provided for this image

8. Salvando a Base Limpa¶

In [25]:
# Salvando a base limpa
df_limpo.to_csv('base_dados_sintetica_limpa.csv', index=False)

print("=== RESUMO FINAL ===")
print(f"Registros originais: {len(df)}")
print(f"Registros após limpeza: {len(df_limpo)}")
print(f"Registros removidos: {len(df) - len(df_limpo)}")
print(f"Taxa de retenção: {(len(df_limpo) / len(df) * 100):.1f}%")

print("\nBase limpa salva como 'base_dados_sintetica_limpa.csv'")
print("\nTratamento de dados concluído com sucesso!")

# Mostrar algumas linhas da base limpa
print("\nExemplos da base limpa:")
print(df_limpo.head())
=== RESUMO FINAL ===
Registros originais: 10800
Registros após limpeza: 9460
Registros removidos: 1340
Taxa de retenção: 87.6%

Base limpa salva como 'base_dados_sintetica_limpa.csv'

Tratamento de dados concluído com sucesso!

Exemplos da base limpa:
   id                nome                         email  cpf         telefone  \
0   1        Brenda Alves  samuel32@example.net.invalid  NaN  (91) 9116-68732   
1   2         Yago Borges                           NaN  NaN  (61) 9461-64955   
2   3  Igor Montenegro Jr      novaismiguel@example.net  NaN              NaN   
3   4  Nome Não Informado     manuelapastor@example.com  NaN  (71) 9862-82117   
4   5     Emanuel Sampaio        rfernandes@example.org  NaN  (11) 9913-06093   

  data_cadastro  idade      renda estado          cidade    status  \
0           NaN   56.0     1434.0     Ce          Recife     Ativo   
1           NaN   49.0  7067228.0     Rs       São Paulo  Pendente   
2           NaN   64.0     5010.0     Ba    Porto Alegre  Pendente   
3           NaN   65.0     8019.0     Ba  Belo Horizonte     Ativo   
4    22/09/2021   67.0        NaN     Pr  Belo Horizonte     Ativo   

   valor_compra  
0         376.0  
1        1801.0  
2           NaN  
3           NaN  
4         661.0