HugoNeres commited on
Commit
d88395f
·
verified ·
1 Parent(s): dca8fd3

Upload 3 files

Browse files
Files changed (3) hide show
  1. app.py +246 -0
  2. dados_credito_clean.csv +0 -0
  3. todos_modelos.pkl +3 -0
app.py ADDED
@@ -0,0 +1,246 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import pandas as pd
3
+ import joblib
4
+ import matplotlib.pyplot as plt
5
+ import seaborn as sns
6
+ import shap
7
+ import numpy as np
8
+
9
+ # --- CONFIGURAÇÃO DA PÁGINA ---
10
+ st.set_page_config(page_title="CrediFast Risk System", layout="wide")
11
+
12
+ st.title("🏦 CrediFast: Sistema Multi-Modelo de Risco")
13
+ st.markdown("""
14
+ Este painel permite comparar a decisão de diferentes famílias de algoritmos de Machine Learning,
15
+ desde métodos clássicos baseados em distância até redes neurais.
16
+ """)
17
+
18
+ # --- CARREGAMENTO DOS DADOS E MODELOS ---
19
+ @st.cache_data
20
+ def load_data():
21
+ return pd.read_csv("dados_credito_clean.csv")
22
+
23
+ @st.cache_resource
24
+ def load_models():
25
+ # Carrega o dicionário salvo no passo anterior
26
+ return joblib.load("todos_modelos.pkl")
27
+
28
+ try:
29
+ df = load_data()
30
+ models_dict = load_models()
31
+ except FileNotFoundError:
32
+ st.error("Erro Crítico: Arquivos 'dados_credito_clean.csv' ou 'todos_modelos.pkl' não encontrados. Por favor, faça o upload no menu 'Files'.")
33
+ st.stop()
34
+
35
+ # --- BARRA LATERAL (CONFIGURAÇÃO HIERÁRQUICA) ---
36
+ st.sidebar.header("⚙️ Configuração da Simulação")
37
+
38
+ # 1. DEFINIÇÃO DA TAXONOMIA (Categorias)
39
+ # Mapeia quais modelos pertencem a qual categoria
40
+ taxonomia = {
41
+ "Métodos de Boosting (SOTA)": ["LightGBM (Campeão)", "XGBoost", "Gradient Boosting", "AdaBoost"],
42
+ "Árvores e Bagging": ["Random Forest", "Decision Tree"],
43
+ "Modelos Baseados em Distância": ["KNN", "SVM"],
44
+ "Redes Neurais": ["MPL (Rede Neural)"]
45
+ }
46
+
47
+ # 2. SELEÇÃO DE CATEGORIA
48
+ categoria_selecionada = st.sidebar.selectbox("1º Escolha a Família do Algoritmo:", list(taxonomia.keys()))
49
+
50
+ # 3. FILTRAGEM DE MODELOS DISPONÍVEIS
51
+ # Verifica quais modelos dessa categoria realmente existem no arquivo .pkl carregado
52
+ modelos_disponiveis = [m for m in taxonomia[categoria_selecionada] if m in models_dict]
53
+
54
+ if not modelos_disponiveis:
55
+ st.sidebar.error(f"Nenhum modelo da categoria '{categoria_selecionada}' foi encontrado no arquivo carregado.")
56
+ st.stop()
57
+
58
+ # 4. SELEÇÃO FINAL DO MODELO
59
+ model_name = st.sidebar.selectbox("2º Escolha o Modelo Específico:", modelos_disponiveis)
60
+ current_model = models_dict[model_name]
61
+
62
+ st.sidebar.markdown(f"**Modelo Ativo:** `{model_name}`")
63
+ st.sidebar.markdown("---")
64
+
65
+ # --- FORMULÁRIO DE INPUT DO UTILIZADOR ---
66
+ st.sidebar.header("📝 Perfil do Cliente")
67
+
68
+ def user_input_features():
69
+ # Inputs numéricos
70
+ income = st.sidebar.number_input("Renda Anual (R$)", min_value=4000, max_value=2000000, value=65000, step=1000)
71
+ age = st.sidebar.number_input("Idade", min_value=18, max_value=80, value=25)
72
+ emp_length = st.sidebar.number_input("Tempo de Emprego (Anos)", min_value=0.0, max_value=60.0, value=2.0)
73
+ loan_amnt = st.sidebar.number_input("Valor do Empréstimo (R$)", min_value=1000, max_value=35000, value=15000, step=500)
74
+ int_rate = st.sidebar.slider("Taxa de Juros (%)", 5.0, 25.0, 11.0, step=0.1)
75
+
76
+ percent_income = loan_amnt / income
77
+
78
+ # Inputs categóricos
79
+ home_ownership = st.sidebar.selectbox("Tipo de Moradia", ["ALUGUEL (RENT)", "PRÓPRIA (OWN)", "FINANCIADA (MORTGAGE)", "OUTROS"])
80
+ loan_intent = st.sidebar.selectbox("Motivo", ["PESSOAL", "EDUCAÇÃO", "MÉDICO", "VENTURE", "REFORMA", "CONSOLIDAÇÃO DÍVIDA"])
81
+ loan_grade = st.sidebar.selectbox("Grade de Risco", ["A", "B", "C", "D", "E", "F", "G"])
82
+ cb_default = st.sidebar.selectbox("Já teve calote?", ["Não (N)", "Sim (Y)"])
83
+
84
+ # Dicionário bruto
85
+ data = {
86
+ 'person_age': age,
87
+ 'person_income': income,
88
+ 'person_emp_length': emp_length,
89
+ 'loan_amnt': loan_amnt,
90
+ 'loan_int_rate': int_rate,
91
+ 'loan_percent_income': percent_income,
92
+ 'cb_person_cred_hist_length': max(2, int(age - 20))
93
+ }
94
+
95
+ # --- PREPARAÇÃO DAS COLUNAS (DUMMIES) ---
96
+ # Tenta descobrir as colunas que o modelo espera
97
+ try:
98
+ if hasattr(current_model, "feature_names_in_"):
99
+ model_cols = current_model.feature_names_in_
100
+ elif hasattr(current_model, "feature_name_"):
101
+ model_cols = current_model.feature_name_
102
+ else:
103
+ # Fallback manual caso o modelo não tenha metadados de colunas (comum em versões antigas do sklearn)
104
+ model_cols = ['person_age', 'person_income', 'person_emp_length', 'loan_amnt',
105
+ 'loan_int_rate', 'loan_percent_income', 'cb_person_cred_hist_length',
106
+ 'person_home_ownership_MORTGAGE', 'person_home_ownership_OTHER',
107
+ 'person_home_ownership_OWN', 'person_home_ownership_RENT',
108
+ 'loan_intent_EDUCATION', 'loan_intent_HOMEIMPROVEMENT', 'loan_intent_MEDICAL',
109
+ 'loan_intent_PERSONAL', 'loan_intent_VENTURE', 'loan_intent_DEBTCONSOLIDATION',
110
+ 'loan_grade_A', 'loan_grade_B', 'loan_grade_C', 'loan_grade_D',
111
+ 'loan_grade_E', 'loan_grade_F', 'loan_grade_G',
112
+ 'cb_person_default_on_file_Y']
113
+ except:
114
+ model_cols = []
115
+
116
+ # Cria DF zerado com as colunas certas
117
+ df_input = pd.DataFrame(0, index=[0], columns=model_cols)
118
+
119
+ # Preenche numéricos
120
+ for col in data:
121
+ if col in df_input.columns: df_input[col] = data[col]
122
+
123
+ # Preenche categóricos (One-Hot Manual)
124
+ # Mapeamento Moradia
125
+ mapping_ownership = {'ALUGUEL': 'RENT', 'PRÓPRIA': 'OWN', 'FINANCIADA': 'MORTGAGE', 'OUTROS': 'OTHER'}
126
+ key_home = f"person_home_ownership_{mapping_ownership.get(home_ownership.split()[0], 'OTHER')}"
127
+ if key_home in df_input.columns: df_input[key_home] = 1
128
+
129
+ # Mapeamento Motivo
130
+ intent_map_en = {"PESSOAL": "PERSONAL", "EDUCAÇÃO": "EDUCATION", "MÉDICO": "MEDICAL",
131
+ "VENTURE": "VENTURE", "REFORMA": "HOMEIMPROVEMENT", "CONSOLIDAÇÃO DÍVIDA": "DEBTCONSOLIDATION"}
132
+ key_intent = f"loan_intent_{intent_map_en.get(loan_intent, 'PERSONAL')}"
133
+ if key_intent in df_input.columns: df_input[key_intent] = 1
134
+
135
+ # Mapeamento Grade
136
+ key_grade = f"loan_grade_{loan_grade}"
137
+ if key_grade in df_input.columns: df_input[key_grade] = 1
138
+
139
+ # Mapeamento Histórico
140
+ if "Sim" in cb_default and "cb_person_default_on_file_Y" in df_input.columns:
141
+ df_input["cb_person_default_on_file_Y"] = 1
142
+
143
+ return df_input
144
+
145
+ input_df = user_input_features()
146
+
147
+ # --- TABS DE RESULTADO ---
148
+ tab1, tab2, tab3 = st.tabs(["📊 Carteira & Dados", "🧩 Clusterização", "🔮 Predição (IA)"])
149
+
150
+ with tab1:
151
+ st.header("Visão da Base de Dados")
152
+ col1, col2, col3 = st.columns(3)
153
+ col1.metric("Total Clientes", f"{len(df):,}")
154
+ col2.metric("Inadimplência Média", f"{df['loan_status'].mean():.1%}")
155
+ col3.metric("Ticket Médio", f"R$ {df['loan_amnt'].mean():.2f}")
156
+
157
+ st.subheader("Distribuição de Risco por Grau (Grade)")
158
+ fig = plt.figure(figsize=(10, 4))
159
+ sns.countplot(data=df, x='loan_grade', hue='loan_status', palette='viridis', order=sorted(df['loan_grade'].unique()))
160
+ st.pyplot(fig)
161
+
162
+ with tab2:
163
+ st.header("Segmentação de Clientes (K-Means)")
164
+ if 'Cluster' in df.columns:
165
+ st.markdown("Médias por Cluster:")
166
+ cols_cluster_view = ['person_age', 'person_income', 'loan_amnt', 'loan_status']
167
+ st.dataframe(df.groupby('Cluster')[cols_cluster_view].mean().style.format({'person_income': 'R$ {:,.2f}', 'loan_status': '{:.1%}'}))
168
+
169
+ st.subheader("Visualização: Renda vs Empréstimo")
170
+ fig2 = plt.figure(figsize=(10, 6))
171
+ sns.scatterplot(data=df, x='person_income', y='loan_amnt', hue='Cluster', palette='deep', alpha=0.7)
172
+ plt.xlim(0, 200000)
173
+ plt.title("Clusters de Perfil Financeiro")
174
+ st.pyplot(fig2)
175
+ else:
176
+ st.info("A coluna 'Cluster' não foi encontrada no dataset exportado.")
177
+
178
+ with tab3:
179
+ st.header(f"Análise de Risco com: {model_name}")
180
+ st.caption(f"Categoria: {categoria_selecionada}")
181
+
182
+ if st.button("Calcular Risco de Calote"):
183
+ try:
184
+ # 1. PREDIÇÃO
185
+ # O predict_proba retorna [Prob_Classe0, Prob_Classe1]
186
+ proba = current_model.predict_proba(input_df)[0][1]
187
+
188
+ # Threshold visual de 50%
189
+ status = "REPROVADO" if proba > 0.5 else "APROVADO"
190
+
191
+ # Exibição
192
+ c1, c2 = st.columns(2)
193
+ if status == "APROVADO":
194
+ c1.success(f"## {status}")
195
+ c1.markdown(f"**Cliente Seguro**")
196
+ else:
197
+ c1.error(f"## {status}")
198
+ c1.markdown(f"**Alto Risco**")
199
+
200
+ c2.metric("Probabilidade de Calote", f"{proba:.1%}")
201
+
202
+ st.divider()
203
+
204
+ # 2. EXPLICABILIDADE (SHAP) - Lógica Condicional
205
+ st.subheader("Por que o modelo tomou essa decisão?")
206
+
207
+ # Lista de modelos compatíveis com o TreeExplainer (Rápido e Visual)
208
+ modelos_arvore = ['LightGBM', 'XGBoost', 'Random Forest', 'Decision Tree', 'Gradient Boosting', 'AdaBoost']
209
+ eh_modelo_arvore = any(nome in model_name for nome in modelos_arvore)
210
+
211
+ if eh_modelo_arvore:
212
+ try:
213
+ # Configura o explicador de árvore
214
+ explainer = shap.TreeExplainer(current_model)
215
+
216
+ # Calcula valores SHAP
217
+ shap_values = explainer(input_df)
218
+
219
+ # Tratamento para Random Forest (que retorna lista para cada classe)
220
+ # Se tiver 3 dimensões (amostra, features, classes), pegamos a classe 1
221
+ if len(shap_values.shape) == 3:
222
+ shap_values = shap_values[:, :, 1]
223
+
224
+ # Plota o Waterfall
225
+ fig_shap = plt.figure(figsize=(10, 5))
226
+ shap.plots.waterfall(shap_values[0], show=False, max_display=10)
227
+ st.pyplot(fig_shap)
228
+
229
+ st.info("💡 **Interpretação:** Barras Vermelhas empurram o risco para cima. Barras Azuis protegem o cliente.")
230
+
231
+ except Exception as e:
232
+ st.warning(f"Erro ao gerar SHAP para este modelo de árvore: {e}")
233
+
234
+ else:
235
+ # Caso seja KNN, SVM ou MLP (Rede Neural)
236
+ st.warning(f"⚠️ A visualização SHAP (Waterfall) não está disponível para modelos baseados em **{categoria_selecionada}** neste ambiente Web.")
237
+ st.markdown("""
238
+ **Motivo:** Modelos como SVM, KNN e Redes Neurais exigem o uso do `KernelExplainer`, que é computacionalmente muito pesado
239
+ para rodar em tempo real na nuvem gratuita. Para ver a explicabilidade destes modelos, consulte o relatório estático HTML.
240
+ """)
241
+
242
+ except Exception as e:
243
+ st.error(f"Erro na execução do modelo. Detalhes técnicos: {e}")
244
+
245
+ st.markdown("---")
246
+ st.caption("Sistema desenvolvido para a Prova Final de Sistemas de Informação - Engenharia de Produção")
dados_credito_clean.csv ADDED
The diff for this file is too large to render. See raw diff
 
todos_modelos.pkl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:5ce3b68735b2a9f35b6b455f99af687bbb293864a895837887b7597826bdff56
3
+ size 101442357