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="💰") | |
| # REMOVIDA A LINHA QUE CAUSAVA O ERRO: | |
| # st.set_option('deprecation.showPyplotGlobalUse', False) | |
| 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) --- | |
| def carregar_dados(uploaded_file): | |
| if uploaded_file is not None: | |
| df = pd.read_csv(uploaded_file) | |
| else: | |
| # Tenta carregar localmente se nenhum arquivo for enviado | |
| try: | |
| df = pd.read_csv('credit_risk_dataset.csv') | |
| except: | |
| return None | |
| return df | |
| 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 e Encoding | |
| # 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 | |
| def treinar_modelo(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 | |
| smote = SMOTE(random_state=42) | |
| X_train_bal, y_train_bal = smote.fit_resample(X_train, y_train) | |
| # Scaling (necessário para Clusters, opcional para XGBoost mas bom para padronizar) | |
| scaler = StandardScaler() | |
| X_train_scaled = scaler.fit_transform(X_train_bal) | |
| X_test_scaled = scaler.transform(X_test) | |
| # Recuperar nomes das colunas para interpretabilidade | |
| 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 | |
| model = XGBClassifier(use_label_encoder=False, eval_metric='logloss', random_state=42) | |
| model.fit(X_train_final, y_train_bal) | |
| return model, scaler, X_test_final, y_test, X_train_final, feature_names | |
| # --- SIDEBAR (Upload e Simulador) --- | |
| st.sidebar.header("📂 Configuração") | |
| uploaded_file = st.sidebar.file_uploader("Upload do CSV (credit_risk_dataset.csv)", type="csv") | |
| df_raw = carregar_dados(uploaded_file) | |
| if df_raw is not None: | |
| # Processamento | |
| with st.spinner('Processando dados e treinando modelo...'): | |
| X, y, df_clean = processar_dados(df_raw) | |
| model, scaler, X_test, y_test, X_train, feature_names = treinar_modelo(X, y) | |
| st.sidebar.success("Modelo Treinado!") | |
| # Simulador (Bônus) | |
| 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 (Principais features) | |
| 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"): | |
| # Lógica simplificada de simulação (cria um vetor zerado e preenche o que temos) | |
| input_data = pd.DataFrame(0, index=[0], columns=feature_names) | |
| input_data['person_income'] = sim_income | |
| input_data['person_age'] = sim_age | |
| input_data['loan_amnt'] = sim_loan | |
| input_data['loan_int_rate'] = sim_int_rate | |
| input_data['person_emp_length'] = sim_emp_length | |
| input_data['loan_percent_income'] = sim_loan / sim_income if sim_income > 0 else 0 | |
| # Escalonar | |
| 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") | |
| # --- TABS DO DASHBOARD --- | |
| tab1, tab2, tab3, tab4, tab5 = st.tabs([ | |
| "📊 Diagnóstico", | |
| "🤖 Performance do Modelo", | |
| "🧠 Explicabilidade (SHAP)", | |
| "🧩 Segmentação (Clusters)", | |
| "📝 Relatório Gerencial" | |
| ]) | |
| # 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 de 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 para corrigir a desproporção vista acima.") | |
| # TAB 2: Performance | |
| with tab2: | |
| st.subheader("Avaliação do Modelo Vencedor (XGBoost)") | |
| y_pred = model.predict(X_test) | |
| y_proba = model.predict_proba(X_test)[:, 1] | |
| m1, m2, m3, m4 = st.columns(4) | |
| m1.metric("AUC Score", f"{roc_auc_score(y_test, y_proba):.3f}") | |
| m2.metric("Recall (Segurança)", f"{recall_score(y_test, y_pred):.3f}") | |
| m3.metric("Precisão", f"{precision_score(y_test, y_pred):.3f}") | |
| m4.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) | |
| # TAB 3: SHAP | |
| with tab3: | |
| st.subheader("Por que o modelo toma essas decisões?") | |
| # Calcular SHAP | |
| explainer = shap.TreeExplainer(model) | |
| shap_values = explainer.shap_values(X_test) | |
| st.markdown("**1. Visão Global (Quais variáveis importam mais?)**") | |
| # Correção para exibir o gráfico sem warning: criar figura explícita e passar para st.pyplot | |
| fig_summary, ax = plt.subplots() | |
| shap.summary_plot(shap_values, X_test, show=False) | |
| st.pyplot(plt.gcf()) | |
| plt.clf() # Limpar figura atual | |
| st.markdown("---") | |
| st.markdown("**2. Visão Local (Análise caso a caso)**") | |
| # Seletor de índice | |
| idx = st.number_input("Selecione o ID do Cliente para auditar:", min_value=0, max_value=len(X_test)-1, value=0) | |
| real_val = y_test.iloc[idx] | |
| pred_val = y_pred[idx] | |
| st.write(f"Cliente ID {idx} | Real: {'Bad' if real_val==1 else 'Good'} | Predito: {'Bad' if pred_val==1 else 'Good'}") | |
| # Waterfall Plot | |
| 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) | |
| # TAB 4: Clusters | |
| with tab4: | |
| st.subheader("Segmentação de Clientes (KMeans & PCA)") | |
| with st.spinner("Gerando 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 | |
| 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'}) | |
| # Plot | |
| 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) | |
| st.markdown("#### Detecção de Anomalias (Outliers)") | |
| st.info(f"O algoritmo DBSCAN detectou {sum(outliers == -1)} casos anômalos que requerem revisão manual.") | |
| # TAB 5: Recomendações | |
| with tab5: | |
| st.subheader("📋 Relatório Gerencial e Estratégia") | |
| st.markdown(""" | |
| ### 1. Diagnóstico do Modelo | |
| O modelo **XGBoost** foi selecionado como o mais robusto. Priorizamos o **Recall** para minimizar perdas financeiras (falsos negativos), mantendo uma precisão operacionalmente viável. | |
| ### 2. Fatores Críticos (SHAP) | |
| * **Comprometimento de Renda (`loan_percent_income`):** É o maior preditor de risco. Clientes comprometendo >30% da renda disparam alarmes. | |
| * **Histórico (`cb_person_default_on_file`):** Inadimplência prévia é determinante. | |
| * **Taxa de Juros (`loan_int_rate`):** Juros abusivos correlacionam com maior inadimplência (seleção adversa). | |
| ### 3. Plano de Ação (Recomendações) | |
| | Ação | Descrição | Impacto Esperado | | |
| | :--- | :--- | :--- | | |
| | **Travas de Segurança** | Bloquear empréstimos onde parcela > 30% da renda sem garantia real. | Redução drástica de Default. | | |
| | **Mesa de Crédito** | Clientes do **Cluster de Risco** ou com histórico negativo vão para análise humana. | Melhoria na qualidade da carteira. | | |
| | **Juros Inteligentes** | Evitar taxas predatórias que forçam o calote; focar em crédito sustentável. | Aumento do LTV (Lifetime Value). | | |
| --- | |
| *Relatório gerado automaticamente pelo Sistema CrediFast AI.* | |
| """) | |
| else: | |
| st.info("Aguardando upload do arquivo 'credit_risk_dataset.csv' na barra lateral.") | |
| st.write("Se estiver rodando localmente e o arquivo estiver na pasta, ele será carregado automaticamente.") |