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) --- @st.cache_data def carregar_dados(): # Tenta carregar o arquivo localmente try: df = pd.read_csv('credit_risk_dataset.csv') return df except FileNotFoundError: return None @st.cache_data 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 @st.cache_resource 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.")