Tratamento de Dados e Limpeza
Objetivos de Aprendizagem
Ao final deste notebook, você será capaz de:
- ✅ Tratar valores ausentes adequadamente
- ✅ Identificar e tratar outliers
- ✅ Limpar dados problemáticos
- ✅ Preparar dados para modelagem
Pré-requisitos
- Notebook 02: EDA completo
Nota: Execute as células de carregamento primeiro!
# Carregar dados
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import warnings
warnings.filterwarnings("ignore")
try:
df
print("✅ Dataset já carregado")
except NameError:
df = pd.read_csv("credit_risk_full_1_200_000.csv")
df["renda"] = pd.to_numeric(df["renda"], errors="coerce")
df["valor_emprestimo"] = pd.to_numeric(df["valor_emprestimo"], errors="coerce")
print(f"📊 Dataset: {df.shape[0]:,} registros × {df.shape[1]} colunas")
📊 Dataset: 1,200,000 registros × 14 colunas
1. Tratamento de Valores Ausentes
📊 Análise Inicial
# Análise de valores ausentes
print("🔍 ANÁLISE DE VALORES AUSENTES")
print("=" * 60)
missing = df.isnull().sum()
missing_pct = (missing / len(df)) * 100
missing_df = pd.DataFrame({
'Coluna': missing.index,
'Ausentes': missing.values,
'Percentual': missing_pct.values.round(2)
})
missing_df = missing_df[missing_df['Ausentes'] > 0].sort_values('Ausentes', ascending=False)
print("\nValores ausentes por coluna:")
print(missing_df.to_string(index=False))
print(f"\n📊 Total de valores ausentes: {missing.sum():,}")
🔍 ANÁLISE DE VALORES AUSENTES
============================================================
Valores ausentes por coluna:
Coluna Ausentes Percentual
renda 141765 11.81
tempo_emprego 120510 10.04
score_credito 29891 2.49
estado_civil 11938 0.99
genero 5848 0.49
valor_emprestimo 4816 0.40
📊 Total de valores ausentes: 314,768
🔧 Estratégia de Imputação
Decisões:
- Eliminação de linhas: Para variáveis com percentual baixo de valores ausentes (< 3%), será feita a eliminação das linhas com valores ausentes:
estado_civil(~1%)genero(~0.5%)valor_emprestimo(~0.4%)score_credito(~2.5%)
- Numéricas (restantes): Mediana (robusta a outliers)
- Categóricas (restantes): Moda (valor mais frequente)
📝 Interpretação do Tratamento de Valores Ausentes
Principais observações:
Renda e Tempo de Emprego: Maior percentual de ausentes (~11-12%), indicando possível problema na coleta dessas variáveis.
Estratégia de imputação: Mediana para numéricas preserva a distribuição e é robusta a outliers. Moda para categóricas mantém o padrão mais comum.
Impacto: A imputação permite manter todos os registros no dataset, mas é importante documentar que esses valores foram imputados para análises futuras.
# Tratamento de valores ausentes
print("🔧 TRATAMENTO DE VALORES AUSENTES")
print("=" * 60)
missing_before = df.isnull().sum().sum()
rows_before_elimination = len(df)
# Eliminação de linhas para variáveis com percentual baixo de ausentes
print("\n📊 Eliminação de Linhas (Percentual baixo de ausentes):")
cols_to_drop = ['estado_civil', 'genero', 'valor_emprestimo', 'score_credito']
for col in cols_to_drop:
if col in df.columns:
missing_count = df[col].isnull().sum()
if missing_count > 0:
rows_before_col = len(df)
df = df.dropna(subset=[col])
rows_after = len(df)
rows_dropped = rows_before_col - rows_after
print(f" ✅ {col:25} → {rows_dropped:>8,} linhas eliminadas ({missing_count:>8,} valores ausentes)")
rows_before = rows_after
rows_eliminated = rows_before_elimination - len(df)
print(f"\n📊 Registros restantes: {len(df):,} (eliminados: {rows_eliminated:,} linhas)")
# Imputação para variáveis restantes
print("\n📊 Imputação de Variáveis Numéricas (Mediana):")
for col in df.select_dtypes(include=[np.number]).columns:
if col not in cols_to_drop and df[col].isnull().sum() > 0:
median_val = df[col].median()
count_before = df[col].isnull().sum()
df[col] = df[col].fillna(median_val)
print(f" ✅ {col:25} → {count_before:>8,} ausentes imputados (mediana = {median_val:>10.2f})")
print("\n📊 Imputação de Variáveis Categóricas (Moda):")
for col in df.select_dtypes(include=['object']).columns:
if col not in cols_to_drop and df[col].isnull().sum() > 0:
mode_val = df[col].mode()[0] if len(df[col].mode()) > 0 else "unknown"
count_before = df[col].isnull().sum()
df[col] = df[col].fillna(mode_val)
print(f" ✅ {col:25} → {count_before:>8,} ausentes imputados (moda = {mode_val})")
missing_after = df.isnull().sum().sum()
print(f"\n✅ Valores ausentes: {missing_before:,} → {missing_after:,}")
🔧 TRATAMENTO DE VALORES AUSENTES ============================================================ 📊 Eliminação de Linhas (Percentual baixo de ausentes): ✅ estado_civil → 11,938 linhas eliminadas ( 11,938 valores ausentes) ✅ genero → 5,803 linhas eliminadas ( 5,803 valores ausentes) ✅ valor_emprestimo → 4,737 linhas eliminadas ( 4,737 valores ausentes) ✅ score_credito → 29,324 linhas eliminadas ( 29,324 valores ausentes) 📊 Registros restantes: 1,148,198 (eliminados: 51,802 linhas) 📊 Imputação de Variáveis Numéricas (Mediana): ✅ renda → 131,464 ausentes imputados (mediana = 2980.65) ✅ tempo_emprego → 115,392 ausentes imputados (mediana = 16.65) 📊 Imputação de Variáveis Categóricas (Moda): ✅ Valores ausentes: 314,768 → 0
2. Tratamento de Outliers
📊 Identificação de Outliers (Método IQR)
O que é IQR?
IQR (Intervalo Interquartil) é uma medida estatística que representa a dispersão dos dados. Ele é calculado como a diferença entre o 3º quartil (Q3) e o 1º quartil (Q1) dos dados. Valores que estão muito distantes dos quartis são considerados outliers (valores extremos).
Método de Capping (Limitação):
Em vez de remover os outliers, limitamos seus valores aos limites aceitáveis:
- Capping IQR: Valores abaixo de Q1 - 1.5×IQR são elevados para esse limite, e valores acima de Q3 + 1.5×IQR são reduzidos para esse limite.
- Capping Percentis: Proteção adicional limitando valores entre o percentil 1% e 99%. Isso garante que mesmo valores muito extremos sejam tratados adequadamente.
# Função para identificar outliers
def count_outliers_iqr(series):
"""Conta outliers usando método IQR"""
Q1 = series.quantile(0.25)
Q3 = series.quantile(0.75)
IQR = Q3 - Q1
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR
outliers_count = ((series < lower_bound) | (series > upper_bound)).sum()
return outliers_count, lower_bound, upper_bound
# Análise de outliers antes do tratamento
print("🔍 IDENTIFICAÇÃO DE OUTLIERS (Antes do Tratamento)")
print("=" * 60)
numeric_cols = ['renda', 'valor_emprestimo', 'score_credito', 'tempo_emprego']
outliers_info = []
for col in numeric_cols:
if col in df.columns:
outliers_count, lower_bound, upper_bound = count_outliers_iqr(df[col])
outliers_pct = (outliers_count / len(df)) * 100
outliers_info.append({
'Coluna': col,
'Outliers': outliers_count,
'Percentual': outliers_pct,
'Limite Inferior': lower_bound,
'Limite Superior': upper_bound
})
print(f" {col:25} → {outliers_count:>8,} outliers ({outliers_pct:>5.2f}%)")
print("\n### 🔧 Tratamento de Outliers (Capping)")
# Tratamento de outliers
print("\n🔧 TRATAMENTO DE OUTLIERS")
print("=" * 60)
outliers_treated = []
for col in numeric_cols:
if col in df.columns:
outliers_before, lower_bound, upper_bound = count_outliers_iqr(df[col])
df[col] = df[col].clip(lower=lower_bound, upper=upper_bound)
df[col] = df[col].clip(lower=df[col].quantile(0.01), upper=df[col].quantile(0.99))
outliers_after, _, _ = count_outliers_iqr(df[col])
outliers_treated.append({
'Coluna': col,
'Antes': outliers_before,
'Depois': outliers_after,
'Tratados': outliers_before - outliers_after
})
print(f" ✅ {col:25} → {outliers_before:>8,} outliers tratados (capping)")
# Resumo
if outliers_treated:
print("\n📊 Resumo do Tratamento de Outliers:")
outliers_summary = pd.DataFrame(outliers_treated)
print(outliers_summary.to_string(index=False))
print(f"\n✅ Total de outliers tratados: {outliers_summary['Tratados'].sum():,}")
🔍 IDENTIFICAÇÃO DE OUTLIERS (Antes do Tratamento)
============================================================
renda → 67,893 outliers ( 5.91%)
valor_emprestimo → 58,202 outliers ( 5.07%)
score_credito → 2,318 outliers ( 0.20%)
tempo_emprego → 68,906 outliers ( 6.00%)
### 🔧 Tratamento de Outliers (Capping)
🔧 TRATAMENTO DE OUTLIERS
============================================================
✅ renda → 67,893 outliers tratados (capping)
✅ valor_emprestimo → 58,202 outliers tratados (capping)
✅ score_credito → 2,318 outliers tratados (capping)
✅ tempo_emprego → 68,906 outliers tratados (capping)
📊 Resumo do Tratamento de Outliers:
Coluna Antes Depois Tratados
renda 67893 0 67893
valor_emprestimo 58202 0 58202
score_credito 2318 0 2318
tempo_emprego 68906 0 68906
✅ Total de outliers tratados: 197,319
📝 Interpretação do Tratamento de Outliers
Principais observações:
Capping duplo: Aplicação de IQR seguida de percentis 1%-99% garante tratamento robusto sem perder informação crítica.
Variáveis mais afetadas: Renda e tempo de emprego apresentaram maior quantidade de outliers, o que é esperado em dados financeiros.
Impacto: O tratamento reduz distorções extremas que poderiam afetar o modelo, mantendo a maioria dos dados intactos.
3. Limpeza de Valores Problemáticos
3.1 🔍 Tratamento de Valores Inválidos
Valores problemáticos são aqueles que não fazem sentido do ponto de vista lógico ou de negócio. Por exemplo:
- Idade negativa ou zero: Não é possível ter idade negativa ou zero em um contexto de crédito
- Valores negativos em variáveis que não podem ser negativas: Como valores de empréstimo, renda, etc.
A estratégia é identificar esses valores e corrigi-los, geralmente substituindo por valores válidos (como a mediana) ou removendo os registros, dependendo do caso.
# Identificação de valores inválidos
print("🔍 IDENTIFICAÇÃO DE VALORES INVÁLIDOS")
print("=" * 60)
# Verificar idade: negativos e zeros
idade_negativos = (df["idade"] < 0).sum()
idade_zeros = (df["idade"] == 0).sum()
idade_problematicos = idade_negativos + idade_zeros
print(f"\n📊 Análise da variável 'idade':")
print(f" - Valores negativos: {idade_negativos:,}")
print(f" - Valores zero: {idade_zeros:,}")
print(f" - Total de valores problemáticos: {idade_problematicos:,}")
if idade_problematicos > 0:
print(f"\n ⚠️ Exemplos de valores problemáticos:")
problematic_idade = df[(df["idade"] < 0) | (df["idade"] == 0)]["idade"].head(10)
for idx, val in enumerate(problematic_idade, 1):
print(f" {idx}. {val}")
# Verificar outros valores problemáticos em colunas numéricas
print("\n📊 Verificação de valores negativos em outras colunas numéricas:")
problematic_cols = []
for col in df.select_dtypes(include=[np.number]).columns:
if col not in ["default", "idade"]:
negatives = (df[col] < 0).sum()
if negatives > 0:
problematic_cols.append({
'Coluna': col,
'Valores Negativos': negatives,
'Percentual': f"{(negatives / len(df)) * 100:.2f}%"
})
if problematic_cols:
problematic_df = pd.DataFrame(problematic_cols)
print(problematic_df.to_string(index=False))
else:
print(" ✅ Nenhum valor negativo encontrado nas outras colunas numéricas")
print(f"\n📊 Resumo:")
print(f" - Total de registros analisados: {len(df):,}")
total_problematicos = idade_problematicos + sum([p['Valores Negativos'] for p in problematic_cols])
print(f" - Valores problemáticos identificados: {total_problematicos:,}")
🔍 IDENTIFICAÇÃO DE VALORES INVÁLIDOS
============================================================
📊 Análise da variável 'idade':
- Valores negativos: 2
- Valores zero: 1,141
- Total de valores problemáticos: 1,143
⚠️ Exemplos de valores problemáticos:
1. 0.0
2. 0.0
3. 0.0
4. 0.0
5. 0.0
6. 0.0
7. 0.0
8. 0.0
9. 0.0
10. 0.0
📊 Verificação de valores negativos em outras colunas numéricas:
Coluna Valores Negativos Percentual
prazo_emprestimo 1163 0.10%
📊 Resumo:
- Total de registros analisados: 1,148,198
- Valores problemáticos identificados: 2,306
📝 Interpretação da Identificação de Valores Inválidos
Principais observações:
Idade: Foram identificados valores inválidos (negativos ou zero) que não fazem sentido do ponto de vista lógico. Uma idade negativa ou zero é impossível em um contexto de análise de crédito.
Outras variáveis: A verificação em outras colunas numéricas revela se há valores negativos onde não deveriam existir (como em valores de empréstimo, renda, etc.).
Impacto dos valores inválidos: Esses valores podem distorcer análises estatísticas e fazer com que o modelo aprenda padrões incorretos. É essencial tratá-los antes de prosseguir com a modelagem.
Próximo passo: Vamos corrigir esses valores inválidos através de imputação com a mediana (para idade) ou outras estratégias apropriadas.
# Tratamento de valores inválidos
print("🔧 TRATAMENTO DE VALORES INVÁLIDOS")
print("=" * 60)
# Tratar idade: substituir negativos e zeros pela mediana
idade_problematicos = ((df["idade"] < 0) | (df["idade"] == 0)).sum()
if idade_problematicos > 0:
print(f"\n📊 Tratamento da variável 'idade':")
print(f" - Valores problemáticos encontrados: {idade_problematicos:,}")
# Substituir valores inválidos por NaN
df.loc[df["idade"] < 0, "idade"] = np.nan
df.loc[df["idade"] == 0, "idade"] = np.nan
# Calcular mediana (excluindo NaN)
median_idade = df["idade"].median()
print(f" - Mediana calculada: {median_idade:.2f}")
# Imputar com mediana
df["idade"] = df["idade"].fillna(median_idade)
# Verificar se foi corrigido
idade_apos = ((df["idade"] < 0) | (df["idade"] == 0)).sum()
print(f" - Valores problemáticos após tratamento: {idade_apos:,}")
print(f" ✅ {idade_problematicos:,} valores corrigidos com sucesso!")
else:
print("\n✅ idade: Nenhum valor problemático encontrado - não foi necessário tratamento")
# Tratar outros valores negativos (se houver)
print("\n📊 Tratamento de outras colunas com valores negativos:")
colunas_tratadas = 0
for col in df.select_dtypes(include=[np.number]).columns:
if col not in ["default", "idade"]:
negatives = (df[col] < 0).sum()
if negatives > 0:
print(f"\n ⚠️ {col}: {negatives:,} valores negativos encontrados")
print(f" (Avaliar se valores negativos fazem sentido para esta variável)")
# Nota: Não tratamos automaticamente, pois pode ser válido (ex: saldo negativo)
colunas_tratadas += 1
if colunas_tratadas == 0:
print(" ✅ Nenhuma outra coluna com valores negativos problemáticos")
print(f"\n📊 Resumo Final:")
print(f" - Dataset: {len(df):,} registros, {df.shape[1]} colunas")
print(f" - Valores ausentes restantes: {df.isnull().sum().sum():,}")
print(f" - Valores problemáticos tratados: {idade_problematicos:,}")
🔧 TRATAMENTO DE VALORES INVÁLIDOS
============================================================
📊 Tratamento da variável 'idade':
- Valores problemáticos encontrados: 1,143
- Mediana calculada: 35.01
- Valores problemáticos após tratamento: 0
✅ 1,143 valores corrigidos com sucesso!
📊 Tratamento de outras colunas com valores negativos:
⚠️ prazo_emprestimo: 1,163 valores negativos encontrados
(Avaliar se valores negativos fazem sentido para esta variável)
📊 Resumo Final:
- Dataset: 1,148,198 registros, 14 colunas
- Valores ausentes restantes: 0
- Valores problemáticos tratados: 1,143
📝 Interpretação do Tratamento de Valores Inválidos
Principais observações:
Idade: Os valores inválidos (negativos ou zero) foram corrigidos através da imputação com a mediana. Esta estratégia preserva a distribuição dos dados e é robusta a outliers, garantindo que os valores imputados sejam representativos da população.
Estratégia de imputação: A escolha da mediana em vez da média é importante porque a mediana é menos sensível a valores extremos, resultando em uma imputação mais robusta.
Validação: Após o tratamento, verificamos que não restam valores problemáticos na variável idade, confirmando que a correção foi bem-sucedida.
Impacto: Dados limpos e consistentes são fundamentais para a qualidade do modelo final. A correção de valores problemáticos evita que o modelo aprenda padrões incorretos baseados em dados inválidos, melhorando a confiabilidade das predições.
Outras variáveis: Para outras colunas com valores negativos, é importante avaliar caso a caso se esses valores fazem sentido do ponto de vista de negócio antes de tratá-los.
3.2 📊 Padronização de Variáveis Categóricas
⚠️ Problema identificado no EDA: Variáveis categóricas com múltiplas variações do mesmo valor (ex: "solteiro", "SOLTEIRO", "solxeiro").
Estratégia de Padronização:
- Converter todas as strings para minúsculas
- Remover espaços em branco extras
- Mapear variações comuns (erros de digitação)
- Usar matching por palavras-chave para valores similares
📊 Análise Antes da Padronização
# Análise de variáveis categóricas antes da padronização
print("🔍 ANÁLISE DE VARIÁVEIS CATEGÓRICAS (Antes da Padronização)")
print("=" * 60)
categorical_vars = ["genero", "estado_civil", "tipo_residencia"]
for var in categorical_vars:
if var in df.columns:
print(f"\n📊 {var.replace('_', ' ').title()}:")
unique_values = df[var].value_counts()
print(f" Total de valores únicos: {len(unique_values)}")
print(f" Top 10 valores mais frequentes:")
for idx, (val, count) in enumerate(unique_values.head(10).items(), 1):
pct = (count / len(df)) * 100
print(f" {idx:2}. {str(val):<20} → {count:>8,} ({pct:>5.2f}%)")
# Para estado_civil, mostrar análise detalhada
if var == 'estado_civil':
estado_civil_lower = df[var].astype(str).str.lower().str.strip()
unique_lower = estado_civil_lower.value_counts()
if len(unique_lower) > 4:
print(f"\n ⚠️ Valores únicos (após lower): {len(unique_lower)}")
print(f" ⚠️ Possíveis variações a serem padronizadas: {len(unique_lower) - 4}")
# Mapeamento completo baseado nos valores esperados
corrections = {
'genero': {
'outrx': 'outro', 'outxo': 'outro', 'ouxro': 'outro', 'oxtro': 'outro',
'nan': 'outro', 'NAN': 'outro', 'none': 'outro', 'null': 'outro'
},
'estado_civil': {
# Variações de "solteiro"
'solxeiro': 'solteiro', 'soltxiro': 'solteiro', 'solteixo': 'solteiro',
'sxlteiro': 'solteiro', 'solteir': 'solteiro', 'solteirx': 'solteiro',
'solteirxo': 'solteiro', 'soxteiro': 'solteiro', 'soltexro': 'solteiro',
# Variações de "casado"
'casxdo': 'casado', 'casad': 'casado', 'casadx': 'casado', 'casadxo': 'casado',
'casaxo': 'casado', 'cxsado': 'casado', 'caxado': 'casado',
# Variações de "divorciado"
'divorcxado': 'divorciado', 'divorcixdo': 'divorciado', 'divorciad': 'divorciado',
'divorciadx': 'divorciado', 'divorciadxo': 'divorciado', 'divorciaxo': 'divorciado',
'dxvorciado': 'divorciado', 'divoxciado': 'divorciado', 'divorxiado': 'divorciado',
'divxrciado': 'divorciado', 'dixorciado': 'divorciado',
# Variações de "viuvo"
'viuvx': 'viuvo', 'viuv': 'viuvo', 'viuvxo': 'viuvo', 'vxuvo': 'viuvo',
'viuxo': 'viuvo', 'vixvo': 'viuvo',
# Valores ausentes ou inválidos
'nan': 'casado', 'NAN': 'casado', 'none': 'casado', 'null': 'casado',
'': 'casado', 'unknown': 'casado',
# "outro" mapeado para casado (valor mais comum)
'outro': 'casado'
}
}
categorical_vars = ["genero", "estado_civil", "tipo_residencia"]
for var in categorical_vars:
if var in df.columns:
unique_before = df[var].nunique()
# 1. Converter para string
df[var] = df[var].astype(str)
# 2. Converter para minúsculas
df[var] = df[var].str.lower()
# 3. Remover espaços extras
df[var] = df[var].str.strip()
# 4. Aplicar correções específicas
if var in corrections:
for wrong, correct in corrections[var].items():
df[var] = df[var].replace(wrong, correct)
# 5. Para estado_civil, usar matching mais robusto
if var == 'estado_civil':
# Valores esperados válidos
valid_values = ['solteiro', 'casado', 'divorciado', 'viuvo']
# Mapeamento de palavras-chave para valores válidos (mais abrangente)
keyword_mapping = {
'solteiro': ['solt', 'solteir', 'soxteir', 'soltex'],
'casado': ['casad', 'cas', 'casx', 'cxs'],
'divorciado': ['divorci', 'divorc', 'divxrci', 'dixorci'],
'viuvo': ['viuv', 'viu', 'vxuv', 'vixv']
}
# Para cada valor único restante, verificar se contém palavras-chave
unique_current = df[var].unique()
for current_val in unique_current:
if current_val not in valid_values and pd.notna(current_val) and current_val != '':
best_match = None
best_score = 0
# Verificar similaridade com cada valor válido
for valid_val, keywords in keyword_mapping.items():
# Verificar se começa com ou contém alguma palavra-chave
for keyword in keywords:
if current_val.startswith(keyword) or keyword in current_val:
# Calcular score de similaridade
common_chars = len(set(current_val) & set(valid_val))
total_chars = max(len(current_val), len(valid_val))
score = common_chars / total_chars if total_chars > 0 else 0
if score > best_score and score >= 0.4: # Threshold mais baixo
best_score = score
best_match = valid_val
# Se encontrou match razoável, substituir
if best_match:
df[var] = df[var].replace(current_val, best_match)
else:
# Se não encontrou match, atribuir ao valor mais comum (casado)
df[var] = df[var].replace(current_val, 'casado')
unique_after = df[var].nunique()
print(f" ✅ {var:25} → {unique_before:>3} → {unique_after:>3} valores únicos (redução de {unique_before - unique_after})")
# Verificar valores únicos finais de estado_civil
if 'estado_civil' in df.columns:
print(f"\n📊 Valores únicos finais de estado_civil:")
final_values = df['estado_civil'].value_counts()
for val, count in final_values.items():
pct = (count / len(df)) * 100
print(f" - '{val}': {count:>8,} ({pct:>5.2f}%)")
# Verificar valores únicos finais de genero
if 'genero' in df.columns:
print(f"\n📊 Valores únicos finais de genero:")
final_values = df['genero'].value_counts()
for val, count in final_values.items():
pct = (count / len(df)) * 100
print(f" - '{val}': {count:>8,} ({pct:>5.2f}%)")
print("\n✅ Padronização concluída!")
🔍 ANÁLISE DE VARIÁVEIS CATEGÓRICAS (Antes da Padronização)
============================================================
📊 Genero:
Total de valores únicos: 14
Top 10 valores mais frequentes:
1. F → 585,676 (51.01%)
2. M → 550,062 (47.91%)
3. Outro → 11,563 ( 1.01%)
4. outro → 415 ( 0.04%)
5. F → 236 ( 0.02%)
6. M → 218 ( 0.02%)
7. Outro → 9 ( 0.00%)
8. OUTRO → 8 ( 0.00%)
9. Outxo → 2 ( 0.00%)
10. Outrx → 2 ( 0.00%)
📊 Estado Civil:
Total de valores únicos: 40
Top 10 valores mais frequentes:
1. casado → 515,649 (44.91%)
2. solteiro → 401,844 (35.00%)
3. divorciado → 171,649 (14.95%)
4. viuvo → 57,311 ( 4.99%)
5. outro → 453 ( 0.04%)
6. casado → 192 ( 0.02%)
7. CASADO → 188 ( 0.02%)
8. SOLTEIRO → 164 ( 0.01%)
9. solteiro → 138 ( 0.01%)
10. DIVORCIADO → 78 ( 0.01%)
⚠️ Valores únicos (após lower): 31
⚠️ Possíveis variações a serem padronizadas: 27
✅ genero → 14 → 3 valores únicos (redução de 11)
✅ estado_civil → 40 → 4 valores únicos (redução de 36)
📊 Valores únicos finais de estado_civil:
- 'casado': 516,682 (45.00%)
- 'solteiro': 402,292 (35.04%)
- 'divorciado': 171,856 (14.97%)
- 'viuvo': 57,368 ( 5.00%)
📊 Valores únicos finais de genero:
- 'f': 585,912 (51.03%)
- 'm': 550,280 (47.93%)
- 'outro': 12,006 ( 1.05%)
✅ Padronização concluída!
📝 Interpretação da Padronização
Resultados:
- Gênero: Mantido com 3 valores únicos (F, M, outro) - já estava padronizado
- Estado Civil: Reduzido de múltiplas variações para 4 valores válidos (solteiro, casado, divorciado, viuvo)
- Tipo Residência: Mantido padronizado
Benefícios:
- Redução de fragmentação artificial nas categorias
- Melhora na qualidade das features para modelagem
- Facilita análise e interpretação dos resultados
5. Resumo Final do Pré-processamento
# Resumo final do pré-processamento
print("📊 RESUMO DO PRÉ-PROCESSAMENTO")
print("=" * 60)
print(f"\n✅ Dataset final:")
print(f" - Registros: {len(df):,}")
print(f" - Colunas: {df.shape[1]}")
print(f" - Valores ausentes: {df.isnull().sum().sum():,}")
print(f"\n✅ Variáveis numéricas: {len(df.select_dtypes(include=[np.number]).columns)}")
print(f"✅ Variáveis categóricas: {len(df.select_dtypes(include=['object']).columns)}")
if 'default' in df.columns:
default_rate = df['default'].mean()
print(f"\n✅ Variável target (default):")
print(f" - Taxa de default: {default_rate:.2%}")
print(f" - Total de defaults: {df['default'].sum():,}")
print(f" - Total de não-defaults: {(df['default'] == 0).sum():,}")
print("\n✅ Dataset pronto para Feature Engineering!")
📊 RESUMO DO PRÉ-PROCESSAMENTO ============================================================ ✅ Dataset final: - Registros: 1,148,198 - Colunas: 14 - Valores ausentes: 0 ✅ Variáveis numéricas: 9 ✅ Variáveis categóricas: 5 ✅ Variável target (default): - Taxa de default: 2.96% - Total de defaults: 33,959 - Total de não-defaults: 1,114,239 ✅ Dataset pronto para Feature Engineering!
# Salvar dataset limpo para uso no próximo notebook
print("💾 SALVANDO DATASET LIMPO")
print("=" * 60)
# Nome do arquivo de saída
output_filename = "credit_risk_preprocessed.csv"
# Salvar o dataset
df.to_csv(output_filename, index=False)
print(f"\n✅ Dataset salvo com sucesso!")
print(f" - Arquivo: {output_filename}")
print(f" - Registros: {len(df):,}")
print(f" - Colunas: {df.shape[1]}")
print(f"\n📝 O dataset limpo está pronto para ser usado no próximo notebook de Feature Engineering.")
💾 SALVANDO DATASET LIMPO ============================================================ ✅ Dataset salvo com sucesso! - Arquivo: credit_risk_preprocessed.csv - Registros: 1,148,198 - Colunas: 14 📝 O dataset limpo está pronto para ser usado no próximo notebook de Feature Engineering.
💼 Implicações de Negócio
O pré-processamento é crítico porque dados limpos resultam em modelos melhores. Decisões sobre imputação e tratamento de outliers devem ser documentadas.
Lição Chave: "Garbage in, garbage out" - a qualidade dos dados de entrada determina diretamente a qualidade do modelo final. Invista tempo no pré-processamento, pois é mais fácil corrigir problemas aqui do que depois de semanas de modelagem.