dcga commited on
Commit
632cea5
·
verified ·
1 Parent(s): 4de014c

Upload 3 files

Browse files
Files changed (3) hide show
  1. README (2).md +83 -0
  2. app (3).py +1579 -0
  3. requirements (3).txt +11 -0
README (2).md ADDED
@@ -0,0 +1,83 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 💳 CrediFast - Sistema de Análise de Risco de Crédito
2
+
3
+ ## Prova Final - Sistemas de Informação em Engenharia de Produção (SIEP)
4
+
5
+ **Universidade de Brasília - UnB**
6
+ **Faculdade de Tecnologia - FT**
7
+ **Departamento de Engenharia de Produção - EPR**
8
+
9
+ ---
10
+
11
+ ### 📋 Informações do Projeto
12
+
13
+ - **Aluno:** Daniel
14
+ - **Matrícula:** 200033638
15
+ - **Professor:** João Gabriel de Moraes Souza
16
+ - **Data de Entrega:** 04/12/2025
17
+
18
+ ---
19
+
20
+ ### 🎯 Objetivo
21
+
22
+ Dashboard interativo para análise de risco de crédito da fintech CrediFast, incluindo:
23
+
24
+ 1. **Diagnóstico Inicial** - Análise de desbalanceamento e aplicação de SMOTE
25
+ 2. **Modelagem Supervisionada** - 9 algoritmos de ML comparados
26
+ 3. **Explicabilidade (SHAP)** - Interpretação global e local do modelo
27
+ 4. **Recomendações Gerenciais** - Orientações estratégicas baseadas em dados
28
+ 5. **Clusterização e Outliers** - KMeans e DBSCAN para segmentação
29
+ 6. **Classificador Interativo** - Simulação de análise de crédito em tempo real
30
+
31
+ ---
32
+
33
+ ### 🤖 Modelos Implementados
34
+
35
+ - **Baseados em distância:** KNN, SVM
36
+ - **Árvores e bagging:** Decision Tree, Random Forest
37
+ - **Boosting:** AdaBoost, Gradient Boosting, XGBoost, LightGBM
38
+ - **Redes Neurais:** MLPClassifier
39
+
40
+ ---
41
+
42
+ ### 📊 Dataset
43
+
44
+ Credit Risk Dataset (Kaggle) com as seguintes características:
45
+ - Variável-alvo: `loan_status` (0 = Good, 1 = Bad)
46
+ - Features numéricas e categóricas relacionadas a perfil do cliente e do empréstimo
47
+
48
+ ---
49
+
50
+ ### 🚀 Como Executar
51
+
52
+ ```bash
53
+ # Instalar dependências
54
+ pip install -r requirements.txt
55
+
56
+ # Executar o dashboard
57
+ streamlit run app.py
58
+ ```
59
+
60
+ ---
61
+
62
+ ### 📁 Estrutura do Projeto
63
+
64
+ ```
65
+ credit_risk_app/
66
+ ├── app.py # Aplicação principal Streamlit
67
+ ├── requirements.txt # Dependências do projeto
68
+ └── README.md # Este arquivo
69
+ ```
70
+
71
+ ---
72
+
73
+ ### 🔗 Links Úteis
74
+
75
+ - [Dataset no GitHub](https://raw.githubusercontent.com/danielcoservalor/credit_data/refs/heads/main/credit_risk_dataset.csv)
76
+ - [Documentação SHAP](https://shap.readthedocs.io/)
77
+ - [Streamlit Documentation](https://docs.streamlit.io/)
78
+
79
+ ---
80
+
81
+ ### 📝 Licença
82
+
83
+ Este projeto foi desenvolvido para fins acadêmicos como parte da Prova Final da disciplina SIEP - UnB.
app (3).py ADDED
@@ -0,0 +1,1579 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ ============================================================================
3
+ PROVA FINAL - ANÁLISE DE RISCO DE CRÉDITO - CREDIFAST
4
+ ============================================================================
5
+ Aluno: Daniel
6
+ Matrícula: 200033638
7
+ Disciplina: Sistemas de Informação em Engenharia de Produção (SIEP)
8
+ Professor: João Gabriel de Moraes Souza
9
+ Universidade de Brasília - UnB
10
+ ============================================================================
11
+ """
12
+
13
+ import streamlit as st
14
+ import pandas as pd
15
+ import numpy as np
16
+ import matplotlib.pyplot as plt
17
+ import seaborn as sns
18
+ import plotly.express as px
19
+ import plotly.graph_objects as go
20
+ from plotly.subplots import make_subplots
21
+ import warnings
22
+ warnings.filterwarnings('ignore')
23
+
24
+ # Machine Learning
25
+ from sklearn.model_selection import train_test_split, cross_val_score
26
+ from sklearn.preprocessing import StandardScaler, LabelEncoder
27
+ from sklearn.metrics import (accuracy_score, precision_score, recall_score,
28
+ f1_score, roc_auc_score, roc_curve,
29
+ confusion_matrix, classification_report)
30
+
31
+ # Modelos
32
+ from sklearn.neighbors import KNeighborsClassifier
33
+ from sklearn.svm import SVC
34
+ from sklearn.tree import DecisionTreeClassifier
35
+ from sklearn.ensemble import (RandomForestClassifier, AdaBoostClassifier,
36
+ GradientBoostingClassifier)
37
+ from sklearn.neural_network import MLPClassifier
38
+ from xgboost import XGBClassifier
39
+ from lightgbm import LGBMClassifier
40
+
41
+ # Balanceamento
42
+ from imblearn.over_sampling import SMOTE
43
+
44
+ # Clusterização
45
+ from sklearn.cluster import KMeans, DBSCAN
46
+ from sklearn.decomposition import PCA
47
+
48
+ # Explicabilidade
49
+ import shap
50
+
51
+ # Configuração da página
52
+ st.set_page_config(
53
+ page_title="CrediFast - Análise de Risco de Crédito",
54
+ page_icon="💳",
55
+ layout="wide",
56
+ initial_sidebar_state="expanded"
57
+ )
58
+
59
+ # CSS customizado
60
+ st.markdown("""
61
+ <style>
62
+ .main-header {
63
+ font-size: 2.5rem;
64
+ font-weight: bold;
65
+ color: #1E3A8A;
66
+ text-align: center;
67
+ margin-bottom: 0.5rem;
68
+ }
69
+ .sub-header {
70
+ font-size: 1.2rem;
71
+ color: #6B7280;
72
+ text-align: center;
73
+ margin-bottom: 2rem;
74
+ }
75
+ .metric-card {
76
+ background-color: #F3F4F6;
77
+ border-radius: 10px;
78
+ padding: 20px;
79
+ text-align: center;
80
+ }
81
+ .section-header {
82
+ font-size: 1.8rem;
83
+ font-weight: bold;
84
+ color: #1E3A8A;
85
+ border-bottom: 3px solid #3B82F6;
86
+ padding-bottom: 10px;
87
+ margin-top: 2rem;
88
+ }
89
+ .info-box {
90
+ background-color: #EFF6FF;
91
+ border-left: 4px solid #3B82F6;
92
+ padding: 15px;
93
+ margin: 10px 0;
94
+ border-radius: 0 8px 8px 0;
95
+ }
96
+ .warning-box {
97
+ background-color: #FEF3C7;
98
+ border-left: 4px solid #F59E0B;
99
+ padding: 15px;
100
+ margin: 10px 0;
101
+ border-radius: 0 8px 8px 0;
102
+ }
103
+ .success-box {
104
+ background-color: #D1FAE5;
105
+ border-left: 4px solid #10B981;
106
+ padding: 15px;
107
+ margin: 10px 0;
108
+ border-radius: 0 8px 8px 0;
109
+ }
110
+ </style>
111
+ """, unsafe_allow_html=True)
112
+
113
+
114
+ # =============================================================================
115
+ # FUNÇÕES DE CARREGAMENTO E PROCESSAMENTO DE DADOS
116
+ # =============================================================================
117
+
118
+ @st.cache_data
119
+ def load_data():
120
+ """Carrega e prepara os dados do dataset de risco de crédito."""
121
+ url = "https://raw.githubusercontent.com/danielcoservalor/credit_data/refs/heads/main/credit_risk_dataset.csv"
122
+ df = pd.read_csv(url)
123
+ return df
124
+
125
+
126
+ @st.cache_data
127
+ def preprocess_data(df):
128
+ """Preprocessa os dados para modelagem."""
129
+ df_processed = df.copy()
130
+
131
+ # Tratamento de valores ausentes
132
+ # Preencher valores numéricos com a mediana
133
+ numeric_cols = df_processed.select_dtypes(include=[np.number]).columns
134
+ for col in numeric_cols:
135
+ if df_processed[col].isnull().sum() > 0:
136
+ df_processed[col].fillna(df_processed[col].median(), inplace=True)
137
+
138
+ # Tratamento de outliers extremos em person_age (valores > 100)
139
+ df_processed = df_processed[df_processed['person_age'] <= 100]
140
+
141
+ # Tratamento de outliers em person_emp_length (valores > 60)
142
+ df_processed = df_processed[df_processed['person_emp_length'] <= 60]
143
+
144
+ return df_processed
145
+
146
+
147
+ @st.cache_data
148
+ def encode_features(df):
149
+ """Codifica variáveis categóricas."""
150
+ df_encoded = df.copy()
151
+
152
+ # Label encoding para variáveis categóricas
153
+ categorical_cols = ['person_home_ownership', 'loan_intent', 'loan_grade', 'cb_person_default_on_file']
154
+
155
+ label_encoders = {}
156
+ for col in categorical_cols:
157
+ le = LabelEncoder()
158
+ df_encoded[col] = le.fit_transform(df_encoded[col].astype(str))
159
+ label_encoders[col] = le
160
+
161
+ return df_encoded, label_encoders
162
+
163
+
164
+ @st.cache_data
165
+ def prepare_model_data(df_encoded):
166
+ """Prepara dados para modelagem."""
167
+ # Separar features e target
168
+ X = df_encoded.drop('loan_status', axis=1)
169
+ y = df_encoded['loan_status']
170
+
171
+ # Split treino/teste
172
+ X_train, X_test, y_train, y_test = train_test_split(
173
+ X, y, test_size=0.2, random_state=42, stratify=y
174
+ )
175
+
176
+ # Escalonamento
177
+ scaler = StandardScaler()
178
+ X_train_scaled = scaler.fit_transform(X_train)
179
+ X_test_scaled = scaler.transform(X_test)
180
+
181
+ return X_train, X_test, y_train, y_test, X_train_scaled, X_test_scaled, scaler, X.columns.tolist()
182
+
183
+
184
+ @st.cache_data
185
+ def apply_smote(X_train_scaled, y_train):
186
+ """Aplica SMOTE para balanceamento."""
187
+ smote = SMOTE(random_state=42)
188
+ X_train_balanced, y_train_balanced = smote.fit_resample(X_train_scaled, y_train)
189
+ return X_train_balanced, y_train_balanced
190
+
191
+
192
+ # =============================================================================
193
+ # FUNÇÕES DE MODELAGEM
194
+ # =============================================================================
195
+
196
+ def train_models(X_train, y_train, X_test, y_test, feature_names):
197
+ """Treina todos os modelos solicitados."""
198
+
199
+ models = {
200
+ 'KNN': KNeighborsClassifier(n_neighbors=5),
201
+ 'SVM': SVC(probability=True, random_state=42, kernel='rbf', C=1.0),
202
+ 'Decision Tree': DecisionTreeClassifier(random_state=42, max_depth=10),
203
+ 'Random Forest': RandomForestClassifier(n_estimators=100, random_state=42, n_jobs=-1),
204
+ 'AdaBoost': AdaBoostClassifier(n_estimators=100, random_state=42),
205
+ 'Gradient Boosting': GradientBoostingClassifier(n_estimators=100, random_state=42),
206
+ 'XGBoost': XGBClassifier(n_estimators=100, random_state=42, use_label_encoder=False,
207
+ eval_metric='logloss', verbosity=0),
208
+ 'LightGBM': LGBMClassifier(n_estimators=100, random_state=42, verbose=-1),
209
+ 'MLP': MLPClassifier(hidden_layer_sizes=(100, 50), max_iter=500, random_state=42)
210
+ }
211
+
212
+ results = {}
213
+ trained_models = {}
214
+
215
+ progress_bar = st.progress(0)
216
+ status_text = st.empty()
217
+
218
+ for i, (name, model) in enumerate(models.items()):
219
+ status_text.text(f"Treinando {name}...")
220
+
221
+ # Treinar modelo
222
+ model.fit(X_train, y_train)
223
+ trained_models[name] = model
224
+
225
+ # Predições
226
+ y_pred = model.predict(X_test)
227
+ y_prob = model.predict_proba(X_test)[:, 1] if hasattr(model, 'predict_proba') else None
228
+
229
+ # Métricas
230
+ results[name] = {
231
+ 'accuracy': accuracy_score(y_test, y_pred),
232
+ 'precision': precision_score(y_test, y_pred),
233
+ 'recall': recall_score(y_test, y_pred),
234
+ 'f1': f1_score(y_test, y_pred),
235
+ 'auc': roc_auc_score(y_test, y_prob) if y_prob is not None else None,
236
+ 'y_pred': y_pred,
237
+ 'y_prob': y_prob,
238
+ 'confusion_matrix': confusion_matrix(y_test, y_pred)
239
+ }
240
+
241
+ progress_bar.progress((i + 1) / len(models))
242
+
243
+ status_text.text("Treinamento concluído!")
244
+
245
+ return results, trained_models
246
+
247
+
248
+ def get_best_model(results):
249
+ """Identifica o melhor modelo baseado no AUC."""
250
+ best_name = max(results, key=lambda x: results[x]['auc'] if results[x]['auc'] else 0)
251
+ return best_name
252
+
253
+
254
+ # =============================================================================
255
+ # FUNÇÕES DE VISUALIZAÇÃO
256
+ # =============================================================================
257
+
258
+ def plot_class_distribution(y, title="Distribuição das Classes"):
259
+ """Plota distribuição das classes."""
260
+ fig = px.pie(
261
+ values=y.value_counts().values,
262
+ names=['Good (0)', 'Bad (1)'],
263
+ title=title,
264
+ color_discrete_sequence=['#10B981', '#EF4444'],
265
+ hole=0.4
266
+ )
267
+ fig.update_traces(textposition='inside', textinfo='percent+label+value')
268
+ return fig
269
+
270
+
271
+ def plot_class_comparison(y_original, y_balanced):
272
+ """Compara distribuição antes e depois do SMOTE."""
273
+ fig = make_subplots(rows=1, cols=2, specs=[[{'type':'pie'}, {'type':'pie'}]],
274
+ subplot_titles=['Antes do SMOTE', 'Após SMOTE'])
275
+
276
+ # Antes
277
+ fig.add_trace(go.Pie(
278
+ labels=['Good (0)', 'Bad (1)'],
279
+ values=y_original.value_counts().sort_index().values,
280
+ marker_colors=['#10B981', '#EF4444'],
281
+ hole=0.4,
282
+ name='Original'
283
+ ), row=1, col=1)
284
+
285
+ # Depois
286
+ unique, counts = np.unique(y_balanced, return_counts=True)
287
+ fig.add_trace(go.Pie(
288
+ labels=['Good (0)', 'Bad (1)'],
289
+ values=counts,
290
+ marker_colors=['#10B981', '#EF4444'],
291
+ hole=0.4,
292
+ name='SMOTE'
293
+ ), row=1, col=2)
294
+
295
+ fig.update_layout(title_text="Impacto do SMOTE no Balanceamento das Classes")
296
+ return fig
297
+
298
+
299
+ def plot_metrics_comparison(results):
300
+ """Plota comparação de métricas entre modelos."""
301
+ df_results = pd.DataFrame({
302
+ 'Modelo': list(results.keys()),
303
+ 'Accuracy': [r['accuracy'] for r in results.values()],
304
+ 'Precision': [r['precision'] for r in results.values()],
305
+ 'Recall': [r['recall'] for r in results.values()],
306
+ 'F1-Score': [r['f1'] for r in results.values()],
307
+ 'AUC': [r['auc'] if r['auc'] else 0 for r in results.values()]
308
+ })
309
+
310
+ df_melted = df_results.melt(id_vars='Modelo', var_name='Métrica', value_name='Valor')
311
+
312
+ fig = px.bar(df_melted, x='Modelo', y='Valor', color='Métrica',
313
+ barmode='group', title='Comparação de Métricas por Modelo',
314
+ color_discrete_sequence=px.colors.qualitative.Set2)
315
+ fig.update_layout(xaxis_tickangle=-45)
316
+ return fig
317
+
318
+
319
+ def plot_roc_curves(results, y_test):
320
+ """Plota curvas ROC de todos os modelos."""
321
+ fig = go.Figure()
322
+
323
+ colors = px.colors.qualitative.Set1
324
+
325
+ for i, (name, res) in enumerate(results.items()):
326
+ if res['y_prob'] is not None:
327
+ fpr, tpr, _ = roc_curve(y_test, res['y_prob'])
328
+ fig.add_trace(go.Scatter(
329
+ x=fpr, y=tpr,
330
+ mode='lines',
331
+ name=f"{name} (AUC={res['auc']:.3f})",
332
+ line=dict(color=colors[i % len(colors)])
333
+ ))
334
+
335
+ # Linha diagonal
336
+ fig.add_trace(go.Scatter(
337
+ x=[0, 1], y=[0, 1],
338
+ mode='lines',
339
+ name='Random',
340
+ line=dict(color='gray', dash='dash')
341
+ ))
342
+
343
+ fig.update_layout(
344
+ title='Curvas ROC - Comparação de Modelos',
345
+ xaxis_title='Taxa de Falsos Positivos (FPR)',
346
+ yaxis_title='Taxa de Verdadeiros Positivos (TPR)',
347
+ legend=dict(x=1.02, y=0.5)
348
+ )
349
+
350
+ return fig
351
+
352
+
353
+ def plot_confusion_matrix(cm, model_name):
354
+ """Plota matriz de confusão."""
355
+ fig = px.imshow(
356
+ cm,
357
+ labels=dict(x="Predito", y="Real", color="Contagem"),
358
+ x=['Good (0)', 'Bad (1)'],
359
+ y=['Good (0)', 'Bad (1)'],
360
+ text_auto=True,
361
+ color_continuous_scale='Blues',
362
+ title=f'Matriz de Confusão - {model_name}'
363
+ )
364
+ return fig
365
+
366
+
367
+ def plot_feature_importance(model, feature_names, model_name):
368
+ """Plota importância das features."""
369
+ if hasattr(model, 'feature_importances_'):
370
+ importances = model.feature_importances_
371
+ elif hasattr(model, 'coef_'):
372
+ importances = np.abs(model.coef_[0])
373
+ else:
374
+ return None
375
+
376
+ df_imp = pd.DataFrame({
377
+ 'Feature': feature_names,
378
+ 'Importance': importances
379
+ }).sort_values('Importance', ascending=True)
380
+
381
+ fig = px.bar(df_imp, x='Importance', y='Feature', orientation='h',
382
+ title=f'Importância das Features - {model_name}',
383
+ color='Importance', color_continuous_scale='Blues')
384
+
385
+ return fig
386
+
387
+
388
+ # =============================================================================
389
+ # FUNÇÕES DE CLUSTERIZAÇÃO
390
+ # =============================================================================
391
+
392
+ def perform_clustering(X_scaled, n_clusters=4):
393
+ """Realiza clustering com KMeans."""
394
+ kmeans = KMeans(n_clusters=n_clusters, random_state=42, n_init=10)
395
+ clusters = kmeans.fit_predict(X_scaled)
396
+ return clusters, kmeans
397
+
398
+
399
+ def perform_dbscan(X_scaled, eps=0.5, min_samples=5):
400
+ """Realiza DBSCAN para detecção de outliers."""
401
+ dbscan = DBSCAN(eps=eps, min_samples=min_samples)
402
+ labels = dbscan.fit_predict(X_scaled)
403
+ return labels, dbscan
404
+
405
+
406
+ def perform_pca(X_scaled, n_components=2):
407
+ """Reduz dimensionalidade com PCA."""
408
+ pca = PCA(n_components=n_components)
409
+ X_pca = pca.fit_transform(X_scaled)
410
+ return X_pca, pca
411
+
412
+
413
+ def plot_clusters_pca(X_pca, clusters, title="Clusters Visualizados com PCA"):
414
+ """Visualiza clusters em 2D usando PCA."""
415
+ df_pca = pd.DataFrame({
416
+ 'PC1': X_pca[:, 0],
417
+ 'PC2': X_pca[:, 1],
418
+ 'Cluster': clusters.astype(str)
419
+ })
420
+
421
+ fig = px.scatter(df_pca, x='PC1', y='PC2', color='Cluster',
422
+ title=title,
423
+ color_discrete_sequence=px.colors.qualitative.Set1)
424
+
425
+ return fig
426
+
427
+
428
+ def plot_dbscan_outliers(X_pca, labels, title="Outliers Detectados pelo DBSCAN"):
429
+ """Visualiza outliers detectados pelo DBSCAN."""
430
+ df_pca = pd.DataFrame({
431
+ 'PC1': X_pca[:, 0],
432
+ 'PC2': X_pca[:, 1],
433
+ 'Tipo': ['Outlier' if l == -1 else 'Normal' for l in labels]
434
+ })
435
+
436
+ fig = px.scatter(df_pca, x='PC1', y='PC2', color='Tipo',
437
+ title=title,
438
+ color_discrete_map={'Outlier': '#EF4444', 'Normal': '#3B82F6'})
439
+
440
+ return fig
441
+
442
+
443
+ # =============================================================================
444
+ # FUNÇÕES SHAP
445
+ # =============================================================================
446
+
447
+ def compute_shap_values(model, X_test, feature_names, model_name):
448
+ """Computa SHAP values para o modelo."""
449
+ try:
450
+ if model_name in ['Random Forest', 'XGBoost', 'LightGBM', 'Decision Tree',
451
+ 'AdaBoost', 'Gradient Boosting']:
452
+ explainer = shap.TreeExplainer(model)
453
+ else:
454
+ # Para outros modelos, usar KernelExplainer com amostra
455
+ background = shap.sample(X_test, min(100, len(X_test)))
456
+ explainer = shap.KernelExplainer(model.predict_proba, background)
457
+
458
+ # Limitar amostras para performance
459
+ X_sample = X_test[:min(500, len(X_test))]
460
+ shap_values = explainer.shap_values(X_sample)
461
+
462
+ return explainer, shap_values, X_sample
463
+ except Exception as e:
464
+ st.warning(f"Não foi possível calcular SHAP values: {str(e)}")
465
+ return None, None, None
466
+
467
+
468
+ # =============================================================================
469
+ # INTERFACE PRINCIPAL
470
+ # =============================================================================
471
+
472
+ def main():
473
+ # Header
474
+ st.markdown('<h1 class="main-header">💳 CrediFast - Sistema de Análise de Risco de Crédito</h1>',
475
+ unsafe_allow_html=True)
476
+ st.markdown('''<p class="sub-header">
477
+ Dashboard Interativo para Predição de Inadimplência |
478
+ Prova Final - SIEP | UnB | Prof. João Gabriel de Moraes Souza
479
+ </p>''', unsafe_allow_html=True)
480
+
481
+ # Sidebar
482
+ st.sidebar.image("https://upload.wikimedia.org/wikipedia/commons/thumb/c/c3/Webysther_20160322_-_Logo_UnB_%28sem_texto%29.svg/1200px-Webysther_20160322_-_Logo_UnB_%28sem_texto%29.svg.png", width=100)
483
+ st.sidebar.markdown("### 📊 Navegação")
484
+
485
+ page = st.sidebar.radio(
486
+ "Selecione a seção:",
487
+ ["🏠 Visão Geral",
488
+ "📊 I. Diagnóstico Inicial",
489
+ "🤖 II. Modelagem Supervisionada",
490
+ "🔍 III. Explicabilidade (SHAP)",
491
+ "📋 IV. Recomendações Gerenciais",
492
+ "🎯 V. Clusterização e Outliers",
493
+ "⚡ VI. Classificador Interativo"]
494
+ )
495
+
496
+ # Carregar dados
497
+ with st.spinner("Carregando dados..."):
498
+ df_raw = load_data()
499
+ df = preprocess_data(df_raw)
500
+ df_encoded, label_encoders = encode_features(df)
501
+
502
+ # Preparar dados para modelagem
503
+ (X_train, X_test, y_train, y_test,
504
+ X_train_scaled, X_test_scaled, scaler, feature_names) = prepare_model_data(df_encoded)
505
+
506
+ # Aplicar SMOTE
507
+ X_train_balanced, y_train_balanced = apply_smote(X_train_scaled, y_train)
508
+
509
+ # ==========================================================================
510
+ # PÁGINA: VISÃO GERAL
511
+ # ==========================================================================
512
+ if page == "🏠 Visão Geral":
513
+ st.markdown('<h2 class="section-header">Visão Geral do Projeto</h2>', unsafe_allow_html=True)
514
+
515
+ st.markdown("""
516
+ <div class="info-box">
517
+ <h4>📋 Contexto do Negócio</h4>
518
+ <p>A <strong>CrediFast</strong> é uma fintech especializada em empréstimos pessoais no modelo P2P (Peer-to-Peer),
519
+ conectando investidores a tomadores de crédito de maneira totalmente digital. Como a empresa não opera com
520
+ capital próprio, sua sobrevivência depende da capacidade de prever corretamente o risco de inadimplência.</p>
521
+ </div>
522
+ """, unsafe_allow_html=True)
523
+
524
+ col1, col2, col3, col4 = st.columns(4)
525
+
526
+ with col1:
527
+ st.metric("Total de Registros", f"{len(df):,}")
528
+ with col2:
529
+ st.metric("Features", f"{len(df.columns) - 1}")
530
+ with col3:
531
+ bad_rate = (df['loan_status'].sum() / len(df)) * 100
532
+ st.metric("Taxa de Inadimplência", f"{bad_rate:.1f}%")
533
+ with col4:
534
+ st.metric("Período de Análise", "2024-2025")
535
+
536
+ st.markdown("### 📁 Amostra dos Dados")
537
+ st.dataframe(df.head(10), use_container_width=True)
538
+
539
+ st.markdown("### 📊 Estatísticas Descritivas")
540
+ st.dataframe(df.describe(), use_container_width=True)
541
+
542
+ st.markdown("### 📋 Dicionário de Variáveis")
543
+ var_dict = pd.DataFrame({
544
+ 'Variável': ['person_age', 'person_income', 'person_home_ownership', 'person_emp_length',
545
+ 'loan_intent', 'loan_grade', 'loan_amnt', 'loan_int_rate',
546
+ 'loan_status', 'loan_percent_income', 'cb_person_default_on_file',
547
+ 'cb_person_cred_hist_length'],
548
+ 'Descrição': [
549
+ 'Idade do solicitante',
550
+ 'Renda anual do solicitante',
551
+ 'Tipo de residência (RENT, OWN, MORTGAGE, OTHER)',
552
+ 'Tempo de emprego em anos',
553
+ 'Finalidade do empréstimo',
554
+ 'Classificação de risco do empréstimo (A-G)',
555
+ 'Valor do empréstimo solicitado',
556
+ 'Taxa de juros do empréstimo',
557
+ 'Status do empréstimo (0=Bom, 1=Inadimplente) - TARGET',
558
+ 'Percentual do empréstimo em relação à renda',
559
+ 'Histórico de inadimplência (Y/N)',
560
+ 'Tempo de histórico de crédito em anos'
561
+ ],
562
+ 'Tipo': ['Numérica', 'Numérica', 'Categórica', 'Numérica',
563
+ 'Categórica', 'Categórica', 'Numérica', 'Numérica',
564
+ 'Target (Binária)', 'Numérica', 'Categórica', 'Numérica']
565
+ })
566
+ st.dataframe(var_dict, use_container_width=True)
567
+
568
+ # ==========================================================================
569
+ # PÁGINA: DIAGNÓSTICO INICIAL
570
+ # ==========================================================================
571
+ elif page == "📊 I. Diagnóstico Inicial":
572
+ st.markdown('<h2 class="section-header">I. Diagnóstico Inicial e Variável-Alvo</h2>',
573
+ unsafe_allow_html=True)
574
+
575
+ st.markdown("""
576
+ <div class="info-box">
577
+ <h4>🎯 Declaração da Variável-Alvo</h4>
578
+ <p>A coluna <code>loan_status</code> é declarada como variável-alvo (target/class), onde:
579
+ <ul>
580
+ <li><strong>0 = Good</strong>: Cliente pagou o empréstimo integralmente (Fully Paid)</li>
581
+ <li><strong>1 = Bad</strong>: Cliente inadimplente (Default ou Charge Off)</li>
582
+ </ul>
583
+ </p>
584
+ </div>
585
+ """, unsafe_allow_html=True)
586
+
587
+ # Análise de proporção
588
+ st.markdown("### 📊 Proporção das Classes")
589
+
590
+ col1, col2 = st.columns(2)
591
+
592
+ with col1:
593
+ good_count = (df['loan_status'] == 0).sum()
594
+ bad_count = (df['loan_status'] == 1).sum()
595
+ total = len(df)
596
+
597
+ st.metric("Clientes Good (0)", f"{good_count:,} ({good_count/total*100:.1f}%)")
598
+ st.metric("Clientes Bad (1)", f"{bad_count:,} ({bad_count/total*100:.1f}%)")
599
+
600
+ ratio = good_count / bad_count
601
+ st.metric("Razão Good/Bad", f"{ratio:.2f}:1")
602
+
603
+ with col2:
604
+ fig = plot_class_distribution(df['loan_status'], "Distribuição Original das Classes")
605
+ st.plotly_chart(fig, use_container_width=True)
606
+
607
+ # Discussão sobre desbalanceamento
608
+ st.markdown("### ⚠️ Análise do Desbalanceamento")
609
+
610
+ st.markdown("""
611
+ <div class="warning-box">
612
+ <h4>Por que o Desbalanceamento é Problemático?</h4>
613
+ <p>O desbalanceamento entre as classes pode prejudicar significativamente os modelos de classificação,
614
+ especialmente em contextos de risco de crédito:</p>
615
+
616
+ <p><strong>🔴 Falsos Negativos (FN) - Maior Custo:</strong><br>
617
+ Classificar um cliente bad como good significa aprovar um empréstimo que provavelmente não será pago.
618
+ Para uma fintech P2P como a CrediFast, isso representa:</p>
619
+ <ul>
620
+ <li>Perda direta do capital emprestado</li>
621
+ <li>Perda de confiança dos investidores</li>
622
+ <li>Impacto na liquidez da plataforma</li>
623
+ <li>Custos de cobrança e recuperação</li>
624
+ </ul>
625
+
626
+ <p><strong>🟡 Falsos Positivos (FP) - Custo Moderado:</strong><br>
627
+ Negar crédito a um bom pagador representa:</p>
628
+ <ul>
629
+ <li>Perda de receita potencial</li>
630
+ <li>Redução da base de clientes</li>
631
+ <li>Oportunidade perdida de fidelização</li>
632
+ </ul>
633
+
634
+ <p><strong>⚡ Conclusão:</strong> Em risco de crédito, prioriza-se o <strong>Recall</strong> (capturar
635
+ o máximo de inadimplentes) mesmo que isso aumente falsos positivos, pois o custo do FN é muito maior.</p>
636
+ </div>
637
+ """, unsafe_allow_html=True)
638
+
639
+ # SMOTE
640
+ st.markdown("### 🔄 Aplicação do SMOTE (Synthetic Minority Over-sampling Technique)")
641
+
642
+ st.markdown("""
643
+ <div class="success-box">
644
+ <h4>Técnica de Balanceamento Escolhida: SMOTE</h4>
645
+ <p>O SMOTE foi selecionado por:</p>
646
+ <ul>
647
+ <li><strong>Criação de amostras sintéticas:</strong> Gera novos exemplos da classe minoritária
648
+ através de interpolação entre exemplos existentes</li>
649
+ <li><strong>Preservação da distribuição:</strong> Mantém as características estatísticas da classe minoritária</li>
650
+ <li><strong>Redução de overfitting:</strong> Diferente do oversampling simples, não replica exemplos idênticos</li>
651
+ <li><strong>Aplicação apenas no treino:</strong> Evita data leakage ao não modificar o conjunto de teste</li>
652
+ </ul>
653
+ </div>
654
+ """, unsafe_allow_html=True)
655
+
656
+ col1, col2 = st.columns(2)
657
+
658
+ with col1:
659
+ st.markdown("**Antes do SMOTE:**")
660
+ st.write(f"- Good: {(y_train == 0).sum():,}")
661
+ st.write(f"- Bad: {(y_train == 1).sum():,}")
662
+
663
+ with col2:
664
+ unique, counts = np.unique(y_train_balanced, return_counts=True)
665
+ st.markdown("**Após SMOTE:**")
666
+ st.write(f"- Good: {counts[0]:,}")
667
+ st.write(f"- Bad: {counts[1]:,}")
668
+
669
+ fig = plot_class_comparison(y_train, y_train_balanced)
670
+ st.plotly_chart(fig, use_container_width=True)
671
+
672
+ # Análise exploratória adicional
673
+ st.markdown("### 📈 Análise Exploratória das Variáveis")
674
+
675
+ tab1, tab2, tab3 = st.tabs(["Distribuições Numéricas", "Variáveis Categóricas", "Correlações"])
676
+
677
+ with tab1:
678
+ numeric_cols = ['person_age', 'person_income', 'loan_amnt', 'loan_int_rate',
679
+ 'loan_percent_income', 'cb_person_cred_hist_length']
680
+
681
+ selected_var = st.selectbox("Selecione a variável:", numeric_cols)
682
+
683
+ fig = px.histogram(df, x=selected_var, color='loan_status',
684
+ barmode='overlay',
685
+ title=f'Distribuição de {selected_var} por Status',
686
+ color_discrete_map={0: '#10B981', 1: '#EF4444'},
687
+ labels={'loan_status': 'Status'})
688
+ st.plotly_chart(fig, use_container_width=True)
689
+
690
+ with tab2:
691
+ cat_cols = ['person_home_ownership', 'loan_intent', 'loan_grade', 'cb_person_default_on_file']
692
+ selected_cat = st.selectbox("Selecione a variável categórica:", cat_cols)
693
+
694
+ cross_tab = pd.crosstab(df[selected_cat], df['loan_status'], normalize='index') * 100
695
+ cross_tab.columns = ['Good (%)', 'Bad (%)']
696
+
697
+ fig = px.bar(cross_tab.reset_index(), x=selected_cat, y=['Good (%)', 'Bad (%)'],
698
+ barmode='group', title=f'Taxa de Inadimplência por {selected_cat}',
699
+ color_discrete_sequence=['#10B981', '#EF4444'])
700
+ st.plotly_chart(fig, use_container_width=True)
701
+
702
+ with tab3:
703
+ numeric_df = df.select_dtypes(include=[np.number])
704
+ corr_matrix = numeric_df.corr()
705
+
706
+ fig = px.imshow(corr_matrix,
707
+ labels=dict(color="Correlação"),
708
+ x=corr_matrix.columns,
709
+ y=corr_matrix.columns,
710
+ color_continuous_scale='RdBu_r',
711
+ title='Matriz de Correlação')
712
+ st.plotly_chart(fig, use_container_width=True)
713
+
714
+ # ==========================================================================
715
+ # PÁGINA: MODELAGEM SUPERVISIONADA
716
+ # ==========================================================================
717
+ elif page == "🤖 II. Modelagem Supervisionada":
718
+ st.markdown('<h2 class="section-header">II. Construção e Avaliação dos Modelos Supervisionados</h2>',
719
+ unsafe_allow_html=True)
720
+
721
+ st.markdown("""
722
+ <div class="info-box">
723
+ <h4>🤖 Modelos Treinados</h4>
724
+ <p>Conforme solicitado, os seguintes algoritmos foram implementados:</p>
725
+ <ul>
726
+ <li><strong>Modelos baseados em distância:</strong> KNN e SVM</li>
727
+ <li><strong>Modelos de árvores e bagging:</strong> Decision Tree e Random Forest</li>
728
+ <li><strong>Métodos de boosting:</strong> AdaBoost, Gradient Boosting, XGBoost e LightGBM</li>
729
+ <li><strong>Modelo neural:</strong> MLPClassifier</li>
730
+ </ul>
731
+ </div>
732
+ """, unsafe_allow_html=True)
733
+
734
+ # Treinar modelos
735
+ if st.button("🚀 Treinar Todos os Modelos", type="primary"):
736
+ with st.spinner("Treinando modelos... Isso pode levar alguns minutos."):
737
+ results, trained_models = train_models(
738
+ X_train_balanced, y_train_balanced,
739
+ X_test_scaled, y_test, feature_names
740
+ )
741
+
742
+ # Salvar em session state
743
+ st.session_state['results'] = results
744
+ st.session_state['trained_models'] = trained_models
745
+ st.session_state['X_test_scaled'] = X_test_scaled
746
+ st.session_state['y_test'] = y_test
747
+ st.session_state['feature_names'] = feature_names
748
+
749
+ st.success("✅ Todos os modelos foram treinados com sucesso!")
750
+
751
+ # Verificar se já temos resultados
752
+ if 'results' in st.session_state:
753
+ results = st.session_state['results']
754
+ trained_models = st.session_state['trained_models']
755
+
756
+ # Tabela de resultados
757
+ st.markdown("### 📊 Comparação de Métricas")
758
+
759
+ df_results = pd.DataFrame({
760
+ 'Modelo': list(results.keys()),
761
+ 'Accuracy': [f"{r['accuracy']:.4f}" for r in results.values()],
762
+ 'Precision': [f"{r['precision']:.4f}" for r in results.values()],
763
+ 'Recall': [f"{r['recall']:.4f}" for r in results.values()],
764
+ 'F1-Score': [f"{r['f1']:.4f}" for r in results.values()],
765
+ 'AUC': [f"{r['auc']:.4f}" if r['auc'] else "N/A" for r in results.values()]
766
+ })
767
+
768
+ st.dataframe(df_results, use_container_width=True)
769
+
770
+ # Gráfico de comparação
771
+ fig = plot_metrics_comparison(results)
772
+ st.plotly_chart(fig, use_container_width=True)
773
+
774
+ # Curvas ROC
775
+ st.markdown("### 📈 Curvas ROC")
776
+ fig_roc = plot_roc_curves(results, y_test)
777
+ st.plotly_chart(fig_roc, use_container_width=True)
778
+
779
+ # Matrizes de confusão
780
+ st.markdown("### 🎯 Matrizes de Confusão")
781
+
782
+ selected_model = st.selectbox("Selecione o modelo:", list(results.keys()))
783
+
784
+ col1, col2 = st.columns(2)
785
+
786
+ with col1:
787
+ fig_cm = plot_confusion_matrix(results[selected_model]['confusion_matrix'], selected_model)
788
+ st.plotly_chart(fig_cm, use_container_width=True)
789
+
790
+ with col2:
791
+ cm = results[selected_model]['confusion_matrix']
792
+ tn, fp, fn, tp = cm.ravel()
793
+
794
+ st.markdown(f"""
795
+ **Interpretação da Matriz de Confusão - {selected_model}:**
796
+
797
+ - **Verdadeiros Negativos (TN):** {tn:,} - Clientes bons corretamente identificados
798
+ - **Falsos Positivos (FP):** {fp:,} - Clientes bons incorretamente classificados como ruins
799
+ - **Falsos Negativos (FN):** {fn:,} - Clientes ruins incorretamente classificados como bons ⚠️
800
+ - **Verdadeiros Positivos (TP):** {tp:,} - Clientes ruins corretamente identificados
801
+
802
+ **Análise de Custos:**
803
+ - FN ({fn}) representa o maior risco financeiro: empréstimos aprovados que resultarão em inadimplência
804
+ - FP ({fp}) representa perda de receita potencial: bons clientes que foram rejeitados
805
+ """)
806
+
807
+ # Melhor modelo
808
+ best_model = get_best_model(results)
809
+
810
+ st.markdown(f"""
811
+ <div class="success-box">
812
+ <h4>🏆 Modelo de Melhor Desempenho: {best_model}</h4>
813
+ <p><strong>Justificativa Técnica:</strong></p>
814
+ <ul>
815
+ <li><strong>AUC = {results[best_model]['auc']:.4f}:</strong> Maior capacidade discriminativa entre classes</li>
816
+ <li><strong>Recall = {results[best_model]['recall']:.4f}:</strong> Alta taxa de detecção de inadimplentes</li>
817
+ <li><strong>F1-Score = {results[best_model]['f1']:.4f}:</strong> Bom equilíbrio entre precisão e recall</li>
818
+ </ul>
819
+ <p>Para o contexto da CrediFast, o {best_model} é recomendado por maximizar a detecção de
820
+ clientes de risco (recall) mantendo um bom equilíbrio com a precisão, minimizando assim
821
+ os custosos falsos negativos.</p>
822
+ </div>
823
+ """, unsafe_allow_html=True)
824
+
825
+ # Interpretação das métricas
826
+ st.markdown("### 📚 Interpretação das Métricas para o Negócio")
827
+
828
+ st.markdown("""
829
+ | Métrica | Significado no Contexto de Crédito | Importância para CrediFast |
830
+ |---------|-----------------------------------|---------------------------|
831
+ | **AUC** | Capacidade geral do modelo de distinguir bons e maus pagadores | Métrica principal para comparação de modelos |
832
+ | **Recall** | % de inadimplentes corretamente identificados | Crítico - alto recall = menos fraudes aprovadas |
833
+ | **Precision** | % de previsões de inadimplência que estão corretas | Importante - evita rejeitar bons clientes |
834
+ | **F1-Score** | Média harmônica entre precision e recall | Equilíbrio geral do modelo |
835
+ | **Accuracy** | % de previsões corretas totais | Menos relevante em dados desbalanceados |
836
+ """)
837
+
838
+ else:
839
+ st.info("👆 Clique no botão acima para treinar os modelos e visualizar os resultados.")
840
+
841
+ # ==========================================================================
842
+ # PÁGINA: EXPLICABILIDADE (SHAP)
843
+ # ==========================================================================
844
+ elif page == "🔍 III. Explicabilidade (SHAP)":
845
+ st.markdown('<h2 class="section-header">III. Explicabilidade com SHAP</h2>',
846
+ unsafe_allow_html=True)
847
+
848
+ if 'trained_models' not in st.session_state:
849
+ st.warning("⚠️ Por favor, treine os modelos primeiro na seção 'II. Modelagem Supervisionada'")
850
+ else:
851
+ results = st.session_state['results']
852
+ trained_models = st.session_state['trained_models']
853
+ best_model_name = get_best_model(results)
854
+
855
+ st.markdown(f"""
856
+ <div class="info-box">
857
+ <h4>🔍 Análise de Explicabilidade do Modelo: {best_model_name}</h4>
858
+ <p>SHAP (SHapley Additive exPlanations) permite entender como cada variável contribui
859
+ para as predições do modelo, tanto de forma global quanto individual.</p>
860
+ </div>
861
+ """, unsafe_allow_html=True)
862
+
863
+ # Selecionar modelo para análise SHAP
864
+ model_for_shap = st.selectbox(
865
+ "Selecione o modelo para análise SHAP:",
866
+ ['LightGBM', 'XGBoost', 'Random Forest', 'Gradient Boosting'],
867
+ index=0
868
+ )
869
+
870
+ if st.button("🔬 Calcular SHAP Values", type="primary"):
871
+ with st.spinner("Calculando SHAP values... Isso pode levar alguns minutos."):
872
+ model = trained_models[model_for_shap]
873
+
874
+ # Usar TreeExplainer para modelos de árvore
875
+ try:
876
+ explainer = shap.TreeExplainer(model)
877
+ X_sample = X_test_scaled[:500]
878
+ shap_values = explainer.shap_values(X_sample)
879
+
880
+ # Para modelos de classificação binária
881
+ if isinstance(shap_values, list):
882
+ shap_values = shap_values[1] # Classe positiva (bad)
883
+
884
+ st.session_state['shap_explainer'] = explainer
885
+ st.session_state['shap_values'] = shap_values
886
+ st.session_state['X_sample_shap'] = X_sample
887
+ st.session_state['shap_model'] = model_for_shap
888
+
889
+ st.success("✅ SHAP values calculados com sucesso!")
890
+ except Exception as e:
891
+ st.error(f"Erro ao calcular SHAP values: {str(e)}")
892
+
893
+ if 'shap_values' in st.session_state:
894
+ shap_values = st.session_state['shap_values']
895
+ X_sample = st.session_state['X_sample_shap']
896
+
897
+ st.markdown("### 📊 Summary Plot - Visão Global")
898
+
899
+ st.markdown("""
900
+ <div class="info-box">
901
+ <p>O <strong>Summary Plot</strong> mostra a importância global de cada variável e como
902
+ seus valores afetam as predições:</p>
903
+ <ul>
904
+ <li>Features ordenadas por importância (de cima para baixo)</li>
905
+ <li>Cores indicam valores das features (vermelho = alto, azul = baixo)</li>
906
+ <li>Posição horizontal indica impacto na predição (direita = aumenta risco)</li>
907
+ </ul>
908
+ </div>
909
+ """, unsafe_allow_html=True)
910
+
911
+ # Summary plot com matplotlib
912
+ fig_summary, ax = plt.subplots(figsize=(10, 8))
913
+ shap.summary_plot(shap_values, X_sample, feature_names=feature_names,
914
+ plot_type="dot", show=False)
915
+ st.pyplot(fig_summary)
916
+ plt.clf()
917
+
918
+ # Análise das principais variáveis
919
+ st.markdown("### 📈 Análise das Variáveis Mais Importantes")
920
+
921
+ # Calcular importância média
922
+ shap_importance = np.abs(shap_values).mean(0)
923
+ importance_df = pd.DataFrame({
924
+ 'Feature': feature_names,
925
+ 'Importância SHAP': shap_importance
926
+ }).sort_values('Importância SHAP', ascending=False)
927
+
928
+ col1, col2 = st.columns([1, 2])
929
+
930
+ with col1:
931
+ st.dataframe(importance_df, use_container_width=True)
932
+
933
+ with col2:
934
+ fig_bar = px.bar(importance_df.head(10), x='Importância SHAP', y='Feature',
935
+ orientation='h', title='Top 10 Variáveis Mais Importantes',
936
+ color='Importância SHAP', color_continuous_scale='Blues')
937
+ fig_bar.update_layout(yaxis={'categoryorder': 'total ascending'})
938
+ st.plotly_chart(fig_bar, use_container_width=True)
939
+
940
+ # Interpretação detalhada
941
+ st.markdown("""
942
+ <div class="success-box">
943
+ <h4>🔎 Interpretação das Principais Variáveis</h4>
944
+
945
+ <p><strong>1. loan_percent_income (% do empréstimo em relação à renda):</strong><br>
946
+ Valores ALTOS (vermelho à direita) → AUMENTAM o risco de inadimplência.<br>
947
+ <em>Interpretação:</em> Clientes que comprometem grande parte da renda com o empréstimo
948
+ têm maior probabilidade de default.</p>
949
+
950
+ <p><strong>2. loan_int_rate (Taxa de juros):</strong><br>
951
+ Valores ALTOS → AUMENTAM significativamente o risco.<br>
952
+ <em>Interpretação:</em> Taxas elevadas geralmente são atribuídas a clientes de maior risco,
953
+ criando um ciclo de dificuldade de pagamento.</p>
954
+
955
+ <p><strong>3. loan_grade (Classificação do empréstimo):</strong><br>
956
+ Valores ALTOS (grades piores: E, F, G) → AUMENTAM o risco.<br>
957
+ <em>Interpretação:</em> A classificação prévia do empréstimo é um forte preditor de inadimplência.</p>
958
+
959
+ <p><strong>4. person_income (Renda):</strong><br>
960
+ Valores BAIXOS (azul à direita) → AUMENTAM o risco.<br>
961
+ <em>Interpretação:</em> Menor renda implica menor capacidade de pagamento.</p>
962
+
963
+ <p><strong>5. cb_person_default_on_file (Histórico de inadimplência):</strong><br>
964
+ Valor = 1 (Sim) → AUMENTA significativamente o risco.<br>
965
+ <em>Interpretação:</em> Histórico negativo é forte preditor de comportamento futuro.</p>
966
+ </div>
967
+ """, unsafe_allow_html=True)
968
+
969
+ # Análise individual (Force/Waterfall plots)
970
+ st.markdown("### 🎯 Análise Individual - Force Plots")
971
+
972
+ # Encontrar exemplos good e bad
973
+ y_test_array = np.array(y_test)
974
+
975
+ # Encontrar índices de exemplos good e bad na amostra
976
+ good_indices = np.where(y_test_array[:500] == 0)[0]
977
+ bad_indices = np.where(y_test_array[:500] == 1)[0]
978
+
979
+ if len(good_indices) > 0 and len(bad_indices) > 0:
980
+ tab1, tab2 = st.tabs(["Cliente GOOD (Bom Pagador)", "Cliente BAD (Inadimplente)"])
981
+
982
+ with tab1:
983
+ st.markdown("#### Análise de um Cliente Classificado como GOOD")
984
+ idx_good = good_indices[0]
985
+
986
+ # Waterfall plot
987
+ fig_wf, ax = plt.subplots(figsize=(10, 6))
988
+ shap.waterfall_plot(shap.Explanation(
989
+ values=shap_values[idx_good],
990
+ base_values=st.session_state['shap_explainer'].expected_value[1]
991
+ if isinstance(st.session_state['shap_explainer'].expected_value, np.ndarray)
992
+ else st.session_state['shap_explainer'].expected_value,
993
+ data=X_sample[idx_good],
994
+ feature_names=feature_names
995
+ ), show=False)
996
+ st.pyplot(fig_wf)
997
+ plt.clf()
998
+
999
+ st.markdown("""
1000
+ **Interpretação:** Este cliente foi classificado como bom pagador porque:
1001
+ - Variáveis que REDUZEM o risco (barras azuis apontando para esquerda) dominam
1002
+ - Baixo comprometimento de renda com o empréstimo
1003
+ - Boa classificação de crédito (loan_grade baixo)
1004
+ - Sem histórico de inadimplência
1005
+ """)
1006
+
1007
+ with tab2:
1008
+ st.markdown("#### Análise de um Cliente Classificado como BAD")
1009
+ idx_bad = bad_indices[0]
1010
+
1011
+ # Waterfall plot
1012
+ fig_wf2, ax = plt.subplots(figsize=(10, 6))
1013
+ shap.waterfall_plot(shap.Explanation(
1014
+ values=shap_values[idx_bad],
1015
+ base_values=st.session_state['shap_explainer'].expected_value[1]
1016
+ if isinstance(st.session_state['shap_explainer'].expected_value, np.ndarray)
1017
+ else st.session_state['shap_explainer'].expected_value,
1018
+ data=X_sample[idx_bad],
1019
+ feature_names=feature_names
1020
+ ), show=False)
1021
+ st.pyplot(fig_wf2)
1022
+ plt.clf()
1023
+
1024
+ st.markdown("""
1025
+ **Interpretação:** Este cliente foi classificado como inadimplente porque:
1026
+ - Variáveis que AUMENTAM o risco (barras vermelhas apontando para direita) dominam
1027
+ - Alto comprometimento da renda (loan_percent_income elevado)
1028
+ - Taxa de juros alta (indicando risco prévio identificado)
1029
+ - Possível histórico de inadimplência anterior
1030
+ """)
1031
+
1032
+ # ==========================================================================
1033
+ # PÁGINA: RECOMENDAÇÕES GERENCIAIS
1034
+ # ==========================================================================
1035
+ elif page == "📋 IV. Recomendações Gerenciais":
1036
+ st.markdown('<h2 class="section-header">IV. Recomendações Gerenciais Baseadas nos Resultados</h2>',
1037
+ unsafe_allow_html=True)
1038
+
1039
+ st.markdown("""
1040
+ <div class="info-box">
1041
+ <h4>📋 Síntese das Descobertas para a Diretoria da CrediFast</h4>
1042
+ <p>Com base nas análises de modelagem supervisionada e explicabilidade SHAP,
1043
+ apresentamos as seguintes recomendações estratégicas para redução da inadimplência
1044
+ e melhoria da eficiência operacional.</p>
1045
+ </div>
1046
+ """, unsafe_allow_html=True)
1047
+
1048
+ # Recomendação 1
1049
+ st.markdown("### 🎯 1. Revisão de Limites de Crédito")
1050
+
1051
+ col1, col2 = st.columns([2, 1])
1052
+
1053
+ with col1:
1054
+ st.markdown("""
1055
+ **Evidência:** A variável `loan_percent_income` (% do empréstimo em relação à renda)
1056
+ é o principal preditor de inadimplência.
1057
+
1058
+ **Recomendação:**
1059
+ - Implementar limite máximo de comprometimento de renda de **35%** para novos empréstimos
1060
+ - Para clientes com histórico positivo, permitir até **45%** com aprovação especial
1061
+ - Criar alertas automáticos quando solicitações excedem **30%** da renda
1062
+
1063
+ **Impacto Esperado:** Redução de 15-20% na taxa de inadimplência em novos empréstimos.
1064
+ """)
1065
+
1066
+ with col2:
1067
+ fig = go.Figure(go.Indicator(
1068
+ mode="gauge+number",
1069
+ value=35,
1070
+ title={'text': "Limite Recomendado (%)"},
1071
+ gauge={'axis': {'range': [0, 100]},
1072
+ 'bar': {'color': "#3B82F6"},
1073
+ 'steps': [
1074
+ {'range': [0, 35], 'color': "#D1FAE5"},
1075
+ {'range': [35, 50], 'color': "#FEF3C7"},
1076
+ {'range': [50, 100], 'color': "#FEE2E2"}
1077
+ ]}
1078
+ ))
1079
+ st.plotly_chart(fig, use_container_width=True)
1080
+
1081
+ # Recomendação 2
1082
+ st.markdown("### 📊 2. Criação de Categorias de Risco Refinadas")
1083
+
1084
+ st.markdown("""
1085
+ **Evidência:** As variáveis `loan_grade`, `loan_int_rate` e `cb_person_default_on_file`
1086
+ apresentam forte poder preditivo.
1087
+
1088
+ **Nova Matriz de Risco Proposta:**
1089
+ """)
1090
+
1091
+ risk_matrix = pd.DataFrame({
1092
+ 'Categoria': ['Ultra Baixo', 'Baixo', 'Moderado', 'Alto', 'Muito Alto', 'Crítico'],
1093
+ 'Score': ['0-10', '11-25', '26-45', '46-65', '66-85', '86-100'],
1094
+ 'Características': [
1095
+ 'Grade A, sem histórico negativo, income > 100k',
1096
+ 'Grade A-B, loan_percent_income < 20%',
1097
+ 'Grade B-C, sem histórico negativo',
1098
+ 'Grade C-D ou histórico negativo anterior',
1099
+ 'Grade D-E, alto comprometimento de renda',
1100
+ 'Grade F-G, múltiplos fatores de risco'
1101
+ ],
1102
+ 'Taxa Sugerida': ['Base', 'Base + 1%', 'Base + 3%', 'Base + 5%', 'Base + 8%', 'Análise especial'],
1103
+ 'Ação': ['Aprovação automática', 'Aprovação rápida', 'Análise padrão',
1104
+ 'Verificação adicional', 'Comitê de crédito', 'Possível recusa']
1105
+ })
1106
+
1107
+ st.dataframe(risk_matrix, use_container_width=True)
1108
+
1109
+ # Recomendação 3
1110
+ st.markdown("### 🔍 3. Verificações Complementares por Perfil")
1111
+
1112
+ col1, col2 = st.columns(2)
1113
+
1114
+ with col1:
1115
+ st.markdown("""
1116
+ **Perfis que Exigem Verificação Adicional:**
1117
+
1118
+ 1. **Clientes com histórico de inadimplência (cb_person_default_on_file = Y)**
1119
+ - Exigir comprovante de quitação de dívidas anteriores
1120
+ - Solicitar fiador ou garantia adicional
1121
+ - Limite inicial reduzido em 50%
1122
+
1123
+ 2. **Empréstimos > 40% da renda**
1124
+ - Análise detalhada de despesas fixas
1125
+ - Verificação de outras dívidas ativas
1126
+ - Aprovação por comitê
1127
+
1128
+ 3. **Clientes jovens (< 25 anos) com pouco histórico**
1129
+ - Score de crédito alternativo (redes sociais, utilities)
1130
+ - Limite progressivo baseado em comportamento
1131
+ """)
1132
+
1133
+ with col2:
1134
+ st.markdown("""
1135
+ **Perfis com Aprovação Facilitada:**
1136
+
1137
+ 1. **Funcionários estáveis (emp_length > 5 anos)**
1138
+ - Processo simplificado
1139
+ - Taxas preferenciais
1140
+
1141
+ 2. **Proprietários de imóvel (home_ownership = OWN/MORTGAGE)**
1142
+ - Menor risco comprovado nos dados
1143
+ - Limites maiores disponíveis
1144
+
1145
+ 3. **Histórico de crédito longo (> 5 anos) sem ocorrências**
1146
+ - Pré-aprovação automática
1147
+ - Programa de fidelidade
1148
+ """)
1149
+
1150
+ # Recomendação 4
1151
+ st.markdown("### 📈 4. Monitoramento e Acompanhamento")
1152
+
1153
+ st.markdown("""
1154
+ **Sistema de Early Warning (Alerta Antecipado):**
1155
+
1156
+ Com base nos SHAP values, implementar monitoramento contínuo de:
1157
+
1158
+ | Indicador | Threshold de Alerta | Ação |
1159
+ |-----------|---------------------|------|
1160
+ | Atraso no pagamento | > 5 dias | SMS/Email automático |
1161
+ | Score de risco aumentou | > 15 pontos | Contato proativo |
1162
+ | Múltiplas consultas de crédito | > 3/mês | Análise de comportamento |
1163
+ | Solicitação de aumento de limite | Em período de risco | Bloqueio temporário |
1164
+ """)
1165
+
1166
+ # Recomendação 5
1167
+ st.markdown("### 📚 5. Políticas de Educação Financeira")
1168
+
1169
+ st.markdown("""
1170
+ <div class="success-box">
1171
+ <h4>Programa "CrediFast Consciente"</h4>
1172
+
1173
+ <p><strong>Público-alvo:</strong> Clientes nas categorias de risco "Alto" e "Muito Alto"</p>
1174
+
1175
+ <p><strong>Componentes:</strong></p>
1176
+ <ul>
1177
+ <li>Curso online obrigatório antes da liberação do empréstimo (2 horas)</li>
1178
+ <li>Calculadora de capacidade de pagamento integrada ao app</li>
1179
+ <li>Alertas personalizados sobre comprometimento de renda</li>
1180
+ <li>Desconto na taxa de juros para quem completar o programa (+0.5%)</li>
1181
+ </ul>
1182
+
1183
+ <p><strong>Impacto esperado:</strong> Redução de 10% na inadimplência do grupo de alto risco</p>
1184
+ </div>
1185
+ """, unsafe_allow_html=True)
1186
+
1187
+ # Síntese Final
1188
+ st.markdown("### 🎯 Síntese: Impacto Esperado das Recomendações")
1189
+
1190
+ impact_data = pd.DataFrame({
1191
+ 'Iniciativa': ['Limites de crédito', 'Categorias de risco', 'Verificações complementares',
1192
+ 'Monitoramento proativo', 'Educação financeira'],
1193
+ 'Redução Inadimplência (%)': [18, 12, 15, 8, 10],
1194
+ 'Custo Implementação': ['Baixo', 'Médio', 'Médio', 'Alto', 'Baixo'],
1195
+ 'Prazo (meses)': [1, 3, 2, 6, 4]
1196
+ })
1197
+
1198
+ fig = px.bar(impact_data, x='Iniciativa', y='Redução Inadimplência (%)',
1199
+ color='Custo Implementação',
1200
+ title='Impacto Esperado por Iniciativa',
1201
+ color_discrete_map={'Baixo': '#10B981', 'Médio': '#F59E0B', 'Alto': '#EF4444'})
1202
+ st.plotly_chart(fig, use_container_width=True)
1203
+
1204
+ st.markdown("""
1205
+ <div class="info-box">
1206
+ <h4>📌 Conclusão Executiva</h4>
1207
+ <p>A implementação conjunta das recomendações acima pode resultar em uma <strong>redução
1208
+ de até 40% na taxa de inadimplência</strong> da CrediFast em 12 meses, mantendo o
1209
+ crescimento saudável da base de clientes através de políticas de crédito mais inteligentes
1210
+ e baseadas em dados.</p>
1211
+
1212
+ <p>O modelo de machine learning desenvolvido (LightGBM/XGBoost) deve ser integrado ao
1213
+ sistema de decisão de crédito para scoring automático, com revisão trimestral dos
1214
+ parâmetros baseada no desempenho real da carteira.</p>
1215
+ </div>
1216
+ """, unsafe_allow_html=True)
1217
+
1218
+ # ==========================================================================
1219
+ # PÁGINA: CLUSTERIZAÇÃO E OUTLIERS
1220
+ # ==========================================================================
1221
+ elif page == "🎯 V. Clusterização e Outliers":
1222
+ st.markdown('<h2 class="section-header">V. Clusterização e Outliers</h2>',
1223
+ unsafe_allow_html=True)
1224
+
1225
+ st.markdown("""
1226
+ <div class="info-box">
1227
+ <h4>🎯 Objetivo da Análise</h4>
1228
+ <p>Segmentar clientes em grupos homogêneos (sem usar a variável-alvo) e detectar
1229
+ outliers que podem representar riscos adicionais ou oportunidades especiais.</p>
1230
+ </div>
1231
+ """, unsafe_allow_html=True)
1232
+
1233
+ # Preparar dados para clustering (sem a variável alvo)
1234
+ X_cluster = X_test_scaled
1235
+
1236
+ # PCA para visualização
1237
+ X_pca, pca = perform_pca(X_cluster)
1238
+
1239
+ st.markdown(f"""
1240
+ **Variância explicada pelo PCA:**
1241
+ - PC1: {pca.explained_variance_ratio_[0]*100:.1f}%
1242
+ - PC2: {pca.explained_variance_ratio_[1]*100:.1f}%
1243
+ - Total: {sum(pca.explained_variance_ratio_)*100:.1f}%
1244
+ """)
1245
+
1246
+ # KMeans
1247
+ st.markdown("### 🔵 Segmentação com KMeans")
1248
+
1249
+ n_clusters = st.slider("Número de clusters:", 2, 8, 4)
1250
+
1251
+ clusters, kmeans = perform_clustering(X_cluster, n_clusters)
1252
+
1253
+ col1, col2 = st.columns(2)
1254
+
1255
+ with col1:
1256
+ fig_clusters = plot_clusters_pca(X_pca, clusters, f"Clusters KMeans (k={n_clusters})")
1257
+ st.plotly_chart(fig_clusters, use_container_width=True)
1258
+
1259
+ with col2:
1260
+ # Análise de clusters vs inadimplência
1261
+ cluster_analysis = pd.DataFrame({
1262
+ 'Cluster': clusters,
1263
+ 'loan_status': y_test.values
1264
+ })
1265
+
1266
+ cluster_stats = cluster_analysis.groupby('Cluster').agg({
1267
+ 'loan_status': ['count', 'sum', 'mean']
1268
+ }).round(3)
1269
+ cluster_stats.columns = ['Total', 'Inadimplentes', 'Taxa Inadimplência']
1270
+ cluster_stats['Taxa Inadimplência'] = (cluster_stats['Taxa Inadimplência'] * 100).round(1).astype(str) + '%'
1271
+
1272
+ st.markdown("**Análise de Inadimplência por Cluster:**")
1273
+ st.dataframe(cluster_stats, use_container_width=True)
1274
+
1275
+ # Características dos clusters
1276
+ st.markdown("### 📊 Características dos Clusters")
1277
+
1278
+ # Adicionar cluster aos dados originais para análise
1279
+ X_test_df = pd.DataFrame(X_test_scaled, columns=feature_names)
1280
+ X_test_df['Cluster'] = clusters
1281
+
1282
+ # Estatísticas por cluster
1283
+ cluster_profiles = X_test_df.groupby('Cluster').mean()
1284
+
1285
+ fig_heatmap = px.imshow(cluster_profiles.T,
1286
+ labels=dict(x="Cluster", y="Feature", color="Valor Médio (Normalizado)"),
1287
+ title="Perfil Médio dos Clusters",
1288
+ color_continuous_scale='RdBu_r',
1289
+ aspect='auto')
1290
+ st.plotly_chart(fig_heatmap, use_container_width=True)
1291
+
1292
+ # Interpretação dos clusters
1293
+ st.markdown("""
1294
+ <div class="success-box">
1295
+ <h4>🔍 Interpretação dos Clusters</h4>
1296
+ <p>Com base no perfil médio, podemos caracterizar os clusters:</p>
1297
+ <ul>
1298
+ <li><strong>Cluster com menor taxa de inadimplência:</strong> Geralmente apresenta menor
1299
+ comprometimento de renda, renda mais alta e melhor grade de crédito</li>
1300
+ <li><strong>Cluster com maior taxa de inadimplência:</strong> Caracterizado por alto
1301
+ comprometimento de renda, taxas de juros elevadas e possível histórico negativo</li>
1302
+ </ul>
1303
+ <p>Estes clusters podem ser usados para estratégias de marketing e políticas de crédito diferenciadas.</p>
1304
+ </div>
1305
+ """, unsafe_allow_html=True)
1306
+
1307
+ # DBSCAN para outliers
1308
+ st.markdown("### 🔴 Detecção de Outliers com DBSCAN")
1309
+
1310
+ col1, col2 = st.columns(2)
1311
+
1312
+ with col1:
1313
+ eps = st.slider("Parâmetro eps:", 0.1, 2.0, 0.8, 0.1)
1314
+ with col2:
1315
+ min_samples = st.slider("Min samples:", 3, 20, 10)
1316
+
1317
+ labels_dbscan, dbscan = perform_dbscan(X_cluster, eps, min_samples)
1318
+
1319
+ n_outliers = (labels_dbscan == -1).sum()
1320
+ n_normal = (labels_dbscan != -1).sum()
1321
+
1322
+ col1, col2, col3 = st.columns(3)
1323
+
1324
+ with col1:
1325
+ st.metric("Total de Outliers", f"{n_outliers:,}")
1326
+ with col2:
1327
+ st.metric("Pontos Normais", f"{n_normal:,}")
1328
+ with col3:
1329
+ st.metric("% Outliers", f"{n_outliers/len(labels_dbscan)*100:.1f}%")
1330
+
1331
+ # Visualização dos outliers
1332
+ fig_outliers = plot_dbscan_outliers(X_pca, labels_dbscan)
1333
+ st.plotly_chart(fig_outliers, use_container_width=True)
1334
+
1335
+ # Análise dos outliers vs inadimplência
1336
+ st.markdown("### 📈 Outliers e Risco de Inadimplência")
1337
+
1338
+ outlier_analysis = pd.DataFrame({
1339
+ 'Tipo': ['Outlier' if l == -1 else 'Normal' for l in labels_dbscan],
1340
+ 'loan_status': y_test.values
1341
+ })
1342
+
1343
+ outlier_stats = outlier_analysis.groupby('Tipo').agg({
1344
+ 'loan_status': ['count', 'sum', 'mean']
1345
+ })
1346
+ outlier_stats.columns = ['Total', 'Inadimplentes', 'Taxa Inadimplência']
1347
+
1348
+ col1, col2 = st.columns(2)
1349
+
1350
+ with col1:
1351
+ st.dataframe(outlier_stats, use_container_width=True)
1352
+
1353
+ with col2:
1354
+ fig_outlier_bar = px.bar(
1355
+ outlier_stats.reset_index(),
1356
+ x='Tipo',
1357
+ y='Taxa Inadimplência',
1358
+ title='Taxa de Inadimplência: Outliers vs Normais',
1359
+ color='Tipo',
1360
+ color_discrete_map={'Outlier': '#EF4444', 'Normal': '#3B82F6'}
1361
+ )
1362
+ fig_outlier_bar.update_yaxes(tickformat='.1%')
1363
+ st.plotly_chart(fig_outlier_bar, use_container_width=True)
1364
+
1365
+ # Conclusões
1366
+ outlier_bad_rate = outlier_stats.loc['Outlier', 'Taxa Inadimplência'] if 'Outlier' in outlier_stats.index else 0
1367
+ normal_bad_rate = outlier_stats.loc['Normal', 'Taxa Inadimplência'] if 'Normal' in outlier_stats.index else 0
1368
+
1369
+ if outlier_bad_rate > normal_bad_rate:
1370
+ st.markdown(f"""
1371
+ <div class="warning-box">
1372
+ <h4>⚠️ Outliers Apresentam Maior Risco</h4>
1373
+ <p>Os clientes identificados como outliers apresentam taxa de inadimplência de
1374
+ <strong>{outlier_bad_rate*100:.1f}%</strong>, versus <strong>{normal_bad_rate*100:.1f}%</strong>
1375
+ dos clientes normais.</p>
1376
+
1377
+ <p><strong>Recomendações:</strong></p>
1378
+ <ul>
1379
+ <li>Implementar análise manual obrigatória para perfis atípicos</li>
1380
+ <li>Criar flag automática no sistema para outliers detectados</li>
1381
+ <li>Considerar limites de crédito reduzidos para estes perfis</li>
1382
+ <li>Monitoramento mais frequente após aprovação</li>
1383
+ </ul>
1384
+ </div>
1385
+ """, unsafe_allow_html=True)
1386
+ else:
1387
+ st.markdown("""
1388
+ <div class="info-box">
1389
+ <h4>ℹ️ Outliers não representam risco adicional significativo</h4>
1390
+ <p>Nesta análise, os outliers não apresentaram taxa de inadimplência significativamente
1391
+ maior que os clientes normais. No entanto, recomenda-se manter monitoramento especial
1392
+ para perfis atípicos.</p>
1393
+ </div>
1394
+ """, unsafe_allow_html=True)
1395
+
1396
+ # ==========================================================================
1397
+ # PÁGINA: CLASSIFICADOR INTERATIVO
1398
+ # ==========================================================================
1399
+ elif page == "⚡ VI. Classificador Interativo":
1400
+ st.markdown('<h2 class="section-header">VI. Classificador Interativo de Risco</h2>',
1401
+ unsafe_allow_html=True)
1402
+
1403
+ st.markdown("""
1404
+ <div class="info-box">
1405
+ <h4>⚡ Simulação de Análise de Crédito</h4>
1406
+ <p>Utilize esta ferramenta para simular a análise de risco de um novo cliente.
1407
+ Preencha os dados abaixo ou faça upload de um arquivo CSV.</p>
1408
+ </div>
1409
+ """, unsafe_allow_html=True)
1410
+
1411
+ if 'trained_models' not in st.session_state:
1412
+ st.warning("⚠️ Por favor, treine os modelos primeiro na seção 'II. Modelagem Supervisionada'")
1413
+ else:
1414
+ trained_models = st.session_state['trained_models']
1415
+
1416
+ tab1, tab2 = st.tabs(["📝 Entrada Manual", "📁 Upload de Dados"])
1417
+
1418
+ with tab1:
1419
+ st.markdown("### Dados do Solicitante")
1420
+
1421
+ col1, col2, col3 = st.columns(3)
1422
+
1423
+ with col1:
1424
+ age = st.number_input("Idade", min_value=18, max_value=100, value=30)
1425
+ income = st.number_input("Renda Anual (R$)", min_value=0, value=60000)
1426
+ home = st.selectbox("Tipo de Residência",
1427
+ ['RENT', 'OWN', 'MORTGAGE', 'OTHER'])
1428
+ emp_length = st.number_input("Tempo de Emprego (anos)", min_value=0, max_value=50, value=5)
1429
+
1430
+ with col2:
1431
+ intent = st.selectbox("Finalidade do Empréstimo",
1432
+ ['PERSONAL', 'EDUCATION', 'MEDICAL', 'VENTURE',
1433
+ 'HOMEIMPROVEMENT', 'DEBTCONSOLIDATION'])
1434
+ grade = st.selectbox("Grade de Crédito", ['A', 'B', 'C', 'D', 'E', 'F', 'G'])
1435
+ loan_amount = st.number_input("Valor do Empréstimo (R$)", min_value=500, value=10000)
1436
+ int_rate = st.number_input("Taxa de Juros (%)", min_value=5.0, max_value=25.0, value=12.0)
1437
+
1438
+ with col3:
1439
+ percent_income = loan_amount / income if income > 0 else 0
1440
+ st.metric("% Comprometimento Renda", f"{percent_income*100:.1f}%")
1441
+
1442
+ default_history = st.selectbox("Histórico de Inadimplência", ['N', 'Y'])
1443
+ cred_hist_length = st.number_input("Histórico de Crédito (anos)", min_value=0, max_value=30, value=5)
1444
+
1445
+ if st.button("🔮 Analisar Risco", type="primary"):
1446
+ # Preparar dados
1447
+ new_data = pd.DataFrame({
1448
+ 'person_age': [age],
1449
+ 'person_income': [income],
1450
+ 'person_home_ownership': [home],
1451
+ 'person_emp_length': [emp_length],
1452
+ 'loan_intent': [intent],
1453
+ 'loan_grade': [grade],
1454
+ 'loan_amnt': [loan_amount],
1455
+ 'loan_int_rate': [int_rate],
1456
+ 'loan_percent_income': [percent_income],
1457
+ 'cb_person_default_on_file': [default_history],
1458
+ 'cb_person_cred_hist_length': [cred_hist_length]
1459
+ })
1460
+
1461
+ # Codificar
1462
+ for col, le in label_encoders.items():
1463
+ if col in new_data.columns:
1464
+ try:
1465
+ new_data[col] = le.transform(new_data[col])
1466
+ except:
1467
+ # Se o valor não existe no encoder, usar o mais comum
1468
+ new_data[col] = 0
1469
+
1470
+ # Escalar
1471
+ new_data_scaled = scaler.transform(new_data)
1472
+
1473
+ # Predizer com múltiplos modelos
1474
+ st.markdown("### 📊 Resultado da Análise")
1475
+
1476
+ results_pred = {}
1477
+ for name, model in trained_models.items():
1478
+ pred = model.predict(new_data_scaled)[0]
1479
+ prob = model.predict_proba(new_data_scaled)[0] if hasattr(model, 'predict_proba') else [0.5, 0.5]
1480
+ results_pred[name] = {'pred': pred, 'prob_bad': prob[1]}
1481
+
1482
+ # Média das probabilidades
1483
+ avg_prob = np.mean([r['prob_bad'] for r in results_pred.values()])
1484
+
1485
+ col1, col2 = st.columns(2)
1486
+
1487
+ with col1:
1488
+ # Gauge de risco
1489
+ fig_gauge = go.Figure(go.Indicator(
1490
+ mode="gauge+number",
1491
+ value=avg_prob * 100,
1492
+ title={'text': "Probabilidade de Inadimplência"},
1493
+ gauge={
1494
+ 'axis': {'range': [0, 100]},
1495
+ 'bar': {'color': "#3B82F6"},
1496
+ 'steps': [
1497
+ {'range': [0, 30], 'color': "#D1FAE5"},
1498
+ {'range': [30, 60], 'color': "#FEF3C7"},
1499
+ {'range': [60, 100], 'color': "#FEE2E2"}
1500
+ ],
1501
+ 'threshold': {
1502
+ 'line': {'color': "red", 'width': 4},
1503
+ 'thickness': 0.75,
1504
+ 'value': 50
1505
+ }
1506
+ }
1507
+ ))
1508
+ st.plotly_chart(fig_gauge, use_container_width=True)
1509
+
1510
+ with col2:
1511
+ # Decisão
1512
+ if avg_prob < 0.3:
1513
+ st.success("✅ APROVADO - Baixo Risco")
1514
+ st.markdown(f"""
1515
+ **Recomendação:** Aprovar empréstimo
1516
+ - Risco estimado: {avg_prob*100:.1f}%
1517
+ - Categoria: Baixo Risco
1518
+ - Ação: Aprovação automática
1519
+ """)
1520
+ elif avg_prob < 0.6:
1521
+ st.warning("⚠️ ANÁLISE ADICIONAL - Risco Moderado")
1522
+ st.markdown(f"""
1523
+ **Recomendação:** Verificação adicional
1524
+ - Risco estimado: {avg_prob*100:.1f}%
1525
+ - Categoria: Risco Moderado
1526
+ - Ação: Solicitar documentação complementar
1527
+ """)
1528
+ else:
1529
+ st.error("❌ NEGADO - Alto Risco")
1530
+ st.markdown(f"""
1531
+ **Recomendação:** Não aprovar
1532
+ - Risco estimado: {avg_prob*100:.1f}%
1533
+ - Categoria: Alto Risco
1534
+ - Ação: Encaminhar para análise especial ou recusar
1535
+ """)
1536
+
1537
+ # Detalhes por modelo
1538
+ st.markdown("### 📋 Detalhes por Modelo")
1539
+
1540
+ df_pred = pd.DataFrame({
1541
+ 'Modelo': list(results_pred.keys()),
1542
+ 'Predição': ['Bad (Inadimplente)' if r['pred'] == 1 else 'Good (Bom Pagador)'
1543
+ for r in results_pred.values()],
1544
+ 'Prob. Inadimplência': [f"{r['prob_bad']*100:.1f}%" for r in results_pred.values()]
1545
+ })
1546
+
1547
+ st.dataframe(df_pred, use_container_width=True)
1548
+
1549
+ with tab2:
1550
+ st.markdown("### Upload de Arquivo CSV")
1551
+
1552
+ uploaded_file = st.file_uploader("Selecione um arquivo CSV", type=['csv'])
1553
+
1554
+ if uploaded_file is not None:
1555
+ try:
1556
+ df_upload = pd.read_csv(uploaded_file)
1557
+ st.markdown("**Preview dos dados:**")
1558
+ st.dataframe(df_upload.head(), use_container_width=True)
1559
+
1560
+ if st.button("🔮 Analisar Todos", type="primary"):
1561
+ # Processamento similar ao anterior
1562
+ st.info("Funcionalidade de processamento em lote disponível na versão completa.")
1563
+ except Exception as e:
1564
+ st.error(f"Erro ao carregar arquivo: {str(e)}")
1565
+
1566
+ # Footer
1567
+ st.markdown("---")
1568
+ st.markdown("""
1569
+ <div style="text-align: center; color: #6B7280; font-size: 0.9rem;">
1570
+ <p>📚 Prova Final - Sistemas de Informação em Engenharia de Produção (SIEP)</p>
1571
+ <p>👨‍🎓 Daniel | Matrícula: 200033638 | UnB - Universidade de Brasília</p>
1572
+ <p>👨‍🏫 Professor: João Gabriel de Moraes Souza</p>
1573
+ <p>📅 Data de Entrega: 04/12/2025</p>
1574
+ </div>
1575
+ """, unsafe_allow_html=True)
1576
+
1577
+
1578
+ if __name__ == "__main__":
1579
+ main()
requirements (3).txt ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ streamlit==1.29.0
2
+ pandas==2.1.3
3
+ numpy==1.26.2
4
+ scikit-learn==1.3.2
5
+ xgboost==2.0.2
6
+ lightgbm==4.1.0
7
+ shap==0.43.0
8
+ imbalanced-learn==0.11.0
9
+ matplotlib==3.8.2
10
+ seaborn==0.13.0
11
+ plotly==5.18.0