Spaces:
Sleeping
Sleeping
| import streamlit as st | |
| import pandas as pd | |
| import numpy as np | |
| import plotly.express as px | |
| import plotly.graph_objects as go | |
| import matplotlib.pyplot as plt | |
| import shap | |
| from sklearn.model_selection import train_test_split | |
| from sklearn.preprocessing import StandardScaler, OneHotEncoder | |
| from sklearn.impute import SimpleImputer | |
| from sklearn.metrics import confusion_matrix, accuracy_score, recall_score, precision_score, roc_auc_score | |
| from sklearn.cluster import KMeans, DBSCAN | |
| from sklearn.decomposition import PCA | |
| from xgboost import XGBClassifier | |
| from imblearn.over_sampling import SMOTE | |
| import warnings | |
| # Configuração da Página | |
| st.set_page_config(page_title="CrediFast - Risco de Crédito", layout="wide", page_icon="💰") | |
| # Desativar avisos (LINHA PROBLEMÁTICA REMOVIDA AQUI) | |
| warnings.filterwarnings('ignore') | |
| # Título e Cabeçalho | |
| st.title("💰 CrediFast: Sistema Inteligente de Risco de Crédito") | |
| st.markdown("---") | |
| # --- FUNÇÕES DE CACHE (Para performance e estabilidade) --- | |
| def carregar_dados(): | |
| # Tenta carregar o arquivo localmente | |
| try: | |
| df = pd.read_csv('credit_risk_dataset.csv') | |
| return df | |
| except FileNotFoundError: | |
| return None | |
| def processar_dados(df): | |
| # 1. Limpeza | |
| df = df.drop_duplicates() | |
| # 2. Separação | |
| target = 'loan_status' | |
| X = df.drop(columns=[target]) | |
| y = df[target] | |
| # 3. Tratamento de Nulos | |
| # Numéricas | |
| num_cols = X.select_dtypes(include=['number']).columns.tolist() | |
| imputer_num = SimpleImputer(strategy='median') | |
| X[num_cols] = imputer_num.fit_transform(X[num_cols]) | |
| # Categóricas - OneHot Manual para garantir consistência | |
| X = pd.get_dummies(X, drop_first=True) | |
| X = X.fillna(0) | |
| return X, y, df # Retorna df original limpo para visualização | |
| # Usamos v4 no nome para garantir que o cache antigo seja invalidado | |
| def treinar_modelo_v4(X, y): | |
| # Split | |
| X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42, stratify=y) | |
| # SMOTE (Apenas no treino) | |
| smote = SMOTE(random_state=42) | |
| X_train_bal, y_train_bal = smote.fit_resample(X_train, y_train) | |
| # Scaling | |
| scaler = StandardScaler() | |
| X_train_scaled = scaler.fit_transform(X_train_bal) | |
| X_test_scaled = scaler.transform(X_test) | |
| # Recuperar nomes das colunas | |
| feature_names = X.columns.tolist() | |
| X_train_final = pd.DataFrame(X_train_scaled, columns=feature_names) | |
| X_test_final = pd.DataFrame(X_test_scaled, columns=feature_names) | |
| # Treinamento XGBoost | |
| # Definimos base_score=0.5 explicitamente para ajudar na compatibilidade com SHAP | |
| model = XGBClassifier( | |
| use_label_encoder=False, | |
| eval_metric='logloss', | |
| random_state=42, | |
| base_score=0.5 | |
| ) | |
| model.fit(X_train_final, y_train_bal) | |
| return model, scaler, X_test_final, y_test, X_train_final, feature_names | |
| # --- LÓGICA PRINCIPAL DO DASHBOARD --- | |
| # Tenta carregar os dados | |
| df_raw = carregar_dados() | |
| if df_raw is not None: | |
| # Processamento Automático | |
| with st.spinner('Inicializando sistema: Processando dados e treinando IA...'): | |
| X, y, df_clean = processar_dados(df_raw) | |
| model, scaler, X_test, y_test, X_train, feature_names = treinar_modelo_v4(X, y) | |
| # --- BARRA LATERAL (Simulador) --- | |
| st.sidebar.header("📂 Menu") | |
| st.sidebar.success("✅ Modelo Carregado") | |
| st.sidebar.markdown("---") | |
| st.sidebar.subheader("🎲 Simulador de Crédito") | |
| st.sidebar.info("Simule um perfil para ver a probabilidade de calote.") | |
| # Inputs do Simulador | |
| sim_income = st.sidebar.number_input("Renda Anual", value=50000) | |
| sim_age = st.sidebar.number_input("Idade", value=25) | |
| sim_loan = st.sidebar.number_input("Valor do Empréstimo", value=10000) | |
| sim_int_rate = st.sidebar.number_input("Taxa de Juros (%)", value=10.0) | |
| sim_emp_length = st.sidebar.number_input("Anos de Emprego", value=2) | |
| # Botão Simular | |
| if st.sidebar.button("Calcular Risco"): | |
| # Criação do dataframe de input | |
| input_data = pd.DataFrame(0, index=[0], columns=feature_names) | |
| # Preenchendo valores | |
| if 'person_income' in input_data.columns: input_data['person_income'] = sim_income | |
| if 'person_age' in input_data.columns: input_data['person_age'] = sim_age | |
| if 'loan_amnt' in input_data.columns: input_data['loan_amnt'] = sim_loan | |
| if 'loan_int_rate' in input_data.columns: input_data['loan_int_rate'] = sim_int_rate | |
| if 'person_emp_length' in input_data.columns: input_data['person_emp_length'] = sim_emp_length | |
| if 'loan_percent_income' in input_data.columns: | |
| input_data['loan_percent_income'] = sim_loan / sim_income if sim_income > 0 else 0 | |
| # Escalonar e Prever | |
| input_scaled = scaler.transform(input_data) | |
| prob = model.predict_proba(input_scaled)[0][1] | |
| if prob > 0.5: | |
| st.sidebar.error(f"🔴 Risco Alto: {prob:.1%} de chance de Default") | |
| else: | |
| st.sidebar.success(f"🟢 Aprovado: {prob:.1%} de chance de Default") | |
| # --- ABAS --- | |
| tab1, tab2, tab3, tab4, tab5 = st.tabs([ | |
| "📊 Diagnóstico", | |
| "🤖 Performance", | |
| "🧠 Explicabilidade (SHAP)", | |
| "🧩 Segmentação", | |
| "📝 Relatório" | |
| ]) | |
| # TAB 1: Diagnóstico | |
| with tab1: | |
| st.subheader("Análise Exploratória Inicial") | |
| col1, col2 = st.columns(2) | |
| with col1: | |
| st.markdown("**Proporção de Inadimplência (Original)**") | |
| fig_pie = px.pie(names=['Good (0)', 'Bad (1)'], | |
| values=y.value_counts().values, | |
| color_discrete_sequence=['blue', 'red']) | |
| st.plotly_chart(fig_pie, use_container_width=True) | |
| with col2: | |
| st.markdown("**Distribuição: Renda vs Empréstimo**") | |
| fig_scatter = px.scatter(df_clean.head(1000), x='person_income', y='loan_amnt', | |
| color=y.head(1000).astype(str), | |
| color_discrete_map={'0': 'blue', '1': 'red'}, | |
| title="Amostra de 1000 clientes") | |
| st.plotly_chart(fig_scatter, use_container_width=True) | |
| st.warning("Nota: Foi aplicado SMOTE (Balanceamento) nos dados de treino.") | |
| # TAB 2: Performance | |
| with tab2: | |
| st.subheader("Performance do Modelo (XGBoost)") | |
| y_pred = model.predict(X_test) | |
| y_proba = model.predict_proba(X_test)[:, 1] | |
| c1, c2, c3, c4 = st.columns(4) | |
| c1.metric("AUC Score", f"{roc_auc_score(y_test, y_proba):.3f}") | |
| c2.metric("Recall (Segurança)", f"{recall_score(y_test, y_pred):.3f}") | |
| c3.metric("Precisão", f"{precision_score(y_test, y_pred):.3f}") | |
| c4.metric("Acurácia", f"{accuracy_score(y_test, y_pred):.3f}") | |
| st.markdown("### Matriz de Confusão") | |
| cm = confusion_matrix(y_test, y_pred) | |
| fig_cm = px.imshow(cm, text_auto=True, color_continuous_scale='Blues', | |
| labels=dict(x="Predito", y="Real", color="Qtd"), | |
| x=['Good', 'Bad'], y=['Good', 'Bad']) | |
| st.plotly_chart(fig_cm, use_container_width=True) | |
| # TAB 3: SHAP (Com Correção de Erro) | |
| with tab3: | |
| st.subheader("Interpretabilidade do Modelo") | |
| try: | |
| # Tenta criar o explainer padrão | |
| explainer = shap.TreeExplainer(model) | |
| shap_values = explainer.shap_values(X_test) | |
| st.markdown("**1. Impacto Global das Variáveis**") | |
| # Matplotlib Figure explícita para evitar warnings | |
| fig, ax = plt.subplots() | |
| shap.summary_plot(shap_values, X_test, show=False) | |
| st.pyplot(fig) | |
| plt.clf() # Limpar figura | |
| st.markdown("---") | |
| st.markdown("**2. Análise Local (Waterfall)**") | |
| idx = st.number_input("ID do Cliente para auditar:", 0, len(X_test)-1, 0) | |
| real_txt = 'Bad' if y_test.iloc[idx] == 1 else 'Good' | |
| pred_txt = 'Bad' if y_pred[idx] == 1 else 'Good' | |
| st.info(f"Cliente {idx}: Real = {real_txt} | Predito = {pred_txt}") | |
| fig_waterfall = plt.figure() | |
| shap.plots.waterfall(shap.Explanation(values=shap_values[idx], | |
| base_values=explainer.expected_value, | |
| data=X_test.iloc[idx], | |
| feature_names=X_test.columns.tolist()), | |
| max_display=10, show=False) | |
| st.pyplot(fig_waterfall) | |
| plt.clf() | |
| except Exception as e: | |
| # Fallback para o erro de versão XGBoost/SHAP | |
| if "could not convert string to float" in str(e): | |
| st.warning("⚠️ Ajustando visualização SHAP (Modo de Compatibilidade)...") | |
| try: | |
| # Usa o booster interno | |
| explainer = shap.TreeExplainer(model.get_booster()) | |
| shap_values = explainer.shap_values(X_test) | |
| fig, ax = plt.subplots() | |
| shap.summary_plot(shap_values, X_test, show=False) | |
| st.pyplot(fig) | |
| except: | |
| st.error("Não foi possível gerar os gráficos SHAP devido a incompatibilidade de versão.") | |
| else: | |
| st.error(f"Erro ao calcular SHAP: {e}") | |
| # TAB 4: Clusters | |
| with tab4: | |
| st.subheader("Segmentação (KMeans & DBSCAN)") | |
| with st.spinner("Calculando clusters..."): | |
| # KMeans | |
| kmeans = KMeans(n_clusters=3, random_state=42, n_init=10) | |
| clusters = kmeans.fit_predict(X_test) | |
| # DBSCAN | |
| dbscan = DBSCAN(eps=3.0, min_samples=5) | |
| outliers = dbscan.fit_predict(X_test) | |
| # PCA 2D | |
| pca = PCA(n_components=2) | |
| components = pca.fit_transform(X_test) | |
| df_viz = pd.DataFrame(data=components, columns=['PC1', 'PC2']) | |
| df_viz['Cluster'] = clusters.astype(str) | |
| df_viz['Outlier'] = outliers | |
| df_viz['Status Real'] = y_test.values | |
| df_viz['Status Real'] = df_viz['Status Real'].map({0: 'Good', 1: 'Bad'}) | |
| fig_cluster = px.scatter(df_viz, x='PC1', y='PC2', color='Cluster', | |
| symbol='Status Real', | |
| title="Mapa de Segmentação de Risco", | |
| color_discrete_sequence=px.colors.qualitative.Safe) | |
| st.plotly_chart(fig_cluster, use_container_width=True) | |
| num_outliers = sum(outliers == -1) | |
| st.error(f"🚨 **DBSCAN:** Foram detectadas {num_outliers} anomalias (Outliers) que exigem revisão manual.") | |
| # TAB 5: Relatório | |
| with tab5: | |
| st.subheader("📋 Relatório Gerencial") | |
| st.markdown(""" | |
| ### Diagnóstico & Estratégia | |
| 1. **Modelo Selecionado:** XGBoost (Foco em Recall). | |
| - Identifica a maioria dos inadimplentes, protegendo o capital da CrediFast. | |
| 2. **Fatores de Risco (SHAP):** | |
| - **Renda Comprometida:** >30% é crítico. | |
| - **Histórico Negativo:** Maior preditor isolado. | |
| - **Juros:** Taxas muito altas atraem maus pagadores. | |
| 3. **Plano de Ação:** | |
| - [x] Implementar trava automática para parcela > 30% da renda. | |
| - [x] Criar esteira de aprovação manual para o **Cluster de Risco**. | |
| - [x] Revisar política de juros para clientes 'Good' para aumentar retenção. | |
| """) | |
| else: | |
| st.error("🚨 Arquivo `credit_risk_dataset.csv` não encontrado.") | |
| st.info("Por favor, certifique-se de que o arquivo CSV está na mesma pasta (Files) do Hugging Face.") |