Spaces:
Sleeping
Sleeping
| import streamlit as st | |
| import pandas as pd | |
| import numpy as np | |
| import matplotlib.pyplot as plt | |
| import seaborn as sns | |
| import kagglehub | |
| from sklearn.model_selection import train_test_split | |
| from sklearn.preprocessing import StandardScaler, LabelEncoder | |
| from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier | |
| from sklearn.linear_model import LogisticRegression | |
| from sklearn.tree import DecisionTreeClassifier | |
| from sklearn.cluster import KMeans, DBSCAN | |
| from sklearn.metrics import classification_report, confusion_matrix, roc_auc_score, roc_curve, accuracy_score, precision_score, recall_score, f1_score | |
| import xgboost as xgb | |
| import shap | |
| # Configuração da Página | |
| st.set_page_config(page_title="Dashboard de Risco de Crédito", layout="wide") | |
| # --- FUNÇÕES DE CARREGAMENTO E PREPARAÇÃO DE DADOS --- | |
| def load_data(): | |
| # Baixa o dataset do Kaggle (cacheado pelo Streamlit para não baixar sempre) | |
| try: | |
| path = "dataset" | |
| file_path = f"{path}/credit_customers.csv" | |
| df = pd.read_csv(file_path) | |
| except Exception as e: | |
| st.error(f"Erro ao baixar dados: {e}. Certifique-se de ter acesso à internet.") | |
| return None | |
| # Padronização dos nomes das colunas | |
| df.columns = df.columns.str.lower().str.replace(" ", "_") | |
| return df | |
| def preprocess_data(df): | |
| # Cópia para não alterar o original | |
| df_proc = df.copy() | |
| # Codificar a target 'class' (good=0, bad=1 para focar na inadimplência como classe positiva) | |
| # Nota: No notebook original, a análise pode variar, mas aqui 'bad' é o evento de interesse (1) | |
| target_map = {'good': 0, 'bad': 1} | |
| df_proc['target'] = df_proc['class'].map(target_map) | |
| df_proc = df_proc.drop(columns=['class']) | |
| # Tratamento de variáveis categóricas (One-Hot Encoding) | |
| # Ajuste de nomes de colunas problemáticos para modelos como XGBoost | |
| df_encoded = pd.get_dummies(df_proc, drop_first=True, dtype=int) | |
| # Limpeza de caracteres especiais nos nomes das colunas (para evitar erros no XGBoost/LightGBM) | |
| df_encoded.columns = df_encoded.columns.str.replace("<", "lt_", regex=False) | |
| df_encoded.columns = df_encoded.columns.str.replace(">", "gt_", regex=False) | |
| df_encoded.columns = df_encoded.columns.str.replace("=", "eq_", regex=False) | |
| df_encoded.columns = df_encoded.columns.str.replace("[", "", regex=False) | |
| df_encoded.columns = df_encoded.columns.str.replace("]", "", regex=False) | |
| return df_encoded | |
| # --- CARREGAR DADOS --- | |
| df_raw = load_data() | |
| # Título | |
| st.markdown(""" | |
| \\ | |
| Programa de Pós-Graduação em Computação Aplicada - PPCA\\ | |
| Disciplina: Análise Estatística de Dados e Informações\\ | |
| Ano/Período: 2025/2\\ | |
| Prof. Dr. João Gabriel\\ | |
| Discente: 252107091 - Vinícius dos Santos Moreira | |
| """) | |
| st.title("Classificador de Risco de Crédito para Instituições Financeiras") | |
| if df_raw is not None: | |
| df_model = preprocess_data(df_raw) | |
| # Separação X e y | |
| X = df_model.drop(columns=['target']) | |
| y = df_model['target'] | |
| # Split de treino e teste | |
| X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42, stratify=y) | |
| # --- BARRA LATERAL DE NAVEGAÇÃO --- | |
| st.sidebar.title("Navegação") | |
| page = st.sidebar.radio("Selecione a Seção:", | |
| ["a) Discussão do Problema", | |
| "b) Análise Descritiva", | |
| "c) Modelagem Preditiva", | |
| "d) Explicabilidade (SHAP)", | |
| "e) Análise Não Supervisionada", | |
| "f) Tomada de Decisão"]) | |
| # --- PÁGINA A: DISCUSSÃO --- | |
| if page == "a) Discussão do Problema": | |
| st.title("📉 Contextualização do Risco de Crédito") | |
| st.markdown(""" | |
| ### O Problema de Negócio | |
| O **risco de crédito** é a possibilidade de uma perda financeira resultante do falha de um tomador de empréstimo em pagar sua dívida total ou parcialmente. No setor bancário, gerenciar esse risco é o coração do negócio. | |
| ### Importância Econômica | |
| * **Sustentabilidade Bancária:** A inadimplência elevada corrói o capital dos bancos, limitando sua capacidade de conceder novos empréstimos. | |
| * **Custo do Crédito:** Quando o risco é mal mensurado, os bancos tendem a aumentar as taxas de juros (spread) para todos os clientes para cobrir as perdas, prejudicando bons pagadores e a economia real. | |
| * **Estabilidade Sistêmica:** Uma gestão de risco ineficiente em larga escala pode levar a crises financeiras. | |
| ### Por que prever a inadimplência? | |
| Prever se um cliente será um "mau pagador" (`bad`) ou "bom pagador" (`good`) permite: | |
| 1. **Reduzir Perdas:** Negar crédito ou ajustar limites para perfis de alto risco. | |
| 2. **Melhorar a Gestão:** Alocar capital de reserva (provisão) de forma mais eficiente. | |
| 3. **Estratégias Personalizadas:** Oferecer taxas melhores para bons clientes e exigir garantias para os mais arriscados. | |
| """) | |
| # --- PÁGINA B: ANÁLISE DESCRITIVA --- | |
| elif page == "b) Análise Descritiva": | |
| st.title("📊 Análise Exploratória de Dados (EDA)") | |
| st.subheader("Visão Geral do Dataset") | |
| st.dataframe(df_raw.head()) | |
| col1, col2 = st.columns(2) | |
| with col1: | |
| st.markdown("**Distribuição da Variável Alvo (Class)**") | |
| fig, ax = plt.subplots() | |
| sns.countplot(data=df_raw, x='class', palette='viridis', ax=ax) | |
| st.pyplot(fig) | |
| st.info("Observa-se o desbalanceamento das classes. A classe 'bad' (inadimplente) é geralmente a minoritária.") | |
| with col2: | |
| st.markdown("**Estatísticas Descritivas (Numéricas)**") | |
| st.dataframe(df_raw.describe()) | |
| st.markdown("---") | |
| st.subheader("Relação das Variáveis com a Classe") | |
| # Seleção de variável para plotar | |
| num_cols = df_raw.select_dtypes(include=np.number).columns.tolist() | |
| cat_cols = df_raw.select_dtypes(include='object').columns.tolist() | |
| cat_cols.remove('class') # Remover target da lista | |
| plot_type = st.selectbox("Escolha o tipo de visualização:", ["Numérica (Boxplot)", "Categórica (Barras)"]) | |
| if plot_type == "Numérica (Boxplot)": | |
| var_num = st.selectbox("Selecione a variável numérica:", num_cols) | |
| fig, ax = plt.subplots(figsize=(10, 5)) | |
| sns.boxplot(data=df_raw, x='class', y=var_num, palette='Set2', ax=ax) | |
| st.pyplot(fig, use_container_width=False) | |
| else: | |
| var_cat = st.selectbox("Selecione a variável categórica:", cat_cols) | |
| fig, ax = plt.subplots(figsize=(10, 5)) | |
| sns.countplot(data=df_raw, x=var_cat, hue='class', palette='Set1', ax=ax) | |
| plt.xticks(rotation=45) | |
| st.pyplot(fig, use_container_width=False) | |
| st.markdown("---") | |
| st.subheader("Matriz de Correlação (Numéricas)") | |
| fig, ax = plt.subplots(figsize=(10, 8)) | |
| sns.heatmap(df_raw[num_cols].corr(), annot=True, fmt=".2f", cmap='coolwarm', ax=ax) | |
| st.pyplot(fig, use_container_width=False) | |
| # --- PÁGINA C: MODELAGEM --- | |
| elif page == "c) Modelagem Preditiva": | |
| st.title("🤖 Definição e Seleção dos Modelos") | |
| st.sidebar.header("Configuração do Modelo") | |
| model_choice = st.sidebar.selectbox("Escolha o Algoritmo:", | |
| ["Random Forest", "XGBoost", "Regressão Logística", "Árvore de Decisão"]) | |
| # Instanciação dos modelos | |
| if model_choice == "Random Forest": | |
| model = RandomForestClassifier(n_estimators=100, random_state=42) | |
| elif model_choice == "XGBoost": | |
| model = xgb.XGBClassifier(use_label_encoder=False, eval_metric='logloss', random_state=42) | |
| elif model_choice == "Regressão Logística": | |
| model = LogisticRegression(max_iter=1000, random_state=42) | |
| elif model_choice == "Árvore de Decisão": | |
| model = DecisionTreeClassifier(random_state=42) | |
| # Treinamento | |
| model.fit(X_train, y_train) | |
| y_pred = model.predict(X_test) | |
| y_prob = model.predict_proba(X_test)[:, 1] | |
| # Métricas | |
| acc = accuracy_score(y_test, y_pred) | |
| prec = precision_score(y_test, y_pred) | |
| rec = recall_score(y_test, y_pred) | |
| f1 = f1_score(y_test, y_pred) | |
| auc = roc_auc_score(y_test, y_prob) | |
| st.subheader(f"Performance do Modelo: {model_choice}") | |
| # Cards de Métricas | |
| c1, c2, c3, c4, c5 = st.columns(5) | |
| c1.metric("Acurácia", f"{acc:.2%}") | |
| c2.metric("Precisão (Bad)", f"{prec:.2%}") | |
| c3.metric("Recall (Bad)", f"{rec:.2%}") | |
| c4.metric("F1-Score", f"{f1:.2f}") | |
| c5.metric("AUC-ROC", f"{auc:.2f}") | |
| col1, col2 = st.columns(2) | |
| with col1: | |
| st.markdown("### Matriz de Confusão") | |
| cm = confusion_matrix(y_test, y_pred) | |
| fig, ax = plt.subplots() | |
| sns.heatmap(cm, annot=True, fmt='d', cmap='Blues', xticklabels=['Good', 'Bad'], yticklabels=['Good', 'Bad']) | |
| plt.xlabel('Predito') | |
| plt.ylabel('Real') | |
| st.pyplot(fig) | |
| with col2: | |
| st.markdown("### Curva ROC") | |
| fpr, tpr, _ = roc_curve(y_test, y_prob) | |
| fig, ax = plt.subplots() | |
| plt.plot(fpr, tpr, label=f"AUC = {auc:.2f}") | |
| plt.plot([0, 1], [0, 1], 'k--') | |
| plt.xlabel('Taxa de Falso Positivo (FPR)') | |
| plt.ylabel('Taxa de Verdadeiro Positivo (TPR)') | |
| plt.legend() | |
| st.pyplot(fig) | |
| st.markdown(""" | |
| **Justificativa da Escolha:** | |
| * **Random Forest e XGBoost** geralmente performam melhor em dados tabulares complexos devido à capacidade de lidar com não-linearidades e interações entre variáveis. | |
| * No contexto de crédito, o **Recall** (capturar os maus pagadores) é crucial, pois o custo de um calote é geralmente maior que o custo de oportunidade de negar um bom cliente. | |
| """) | |
| # --- PÁGINA D: SHAP --- | |
| elif page == "d) Explicabilidade (SHAP)": | |
| st.title("🔍 Explicabilidade com SHAP Values") | |
| st.info("Calculando valores SHAP para o modelo XGBoost (padrão para esta análise)...") | |
| # Treinar um modelo fixo para análise SHAP estável | |
| model_shap = xgb.XGBClassifier(use_label_encoder=False, eval_metric='logloss', random_state=42) | |
| model_shap.fit(X_train, y_train) | |
| # Criar Explainer | |
| explainer = shap.TreeExplainer(model_shap) | |
| shap_values = explainer.shap_values(X_test) | |
| st.subheader("Importância Global das Variáveis (Summary Plot)") | |
| st.markdown("Este gráfico mostra quais variáveis mais impactam na decisão do modelo. Pontos à direita aumentam a probabilidade de ser 'Bad' (Inadimplente).") | |
| fig, ax = plt.subplots() | |
| shap.summary_plot(shap_values, X_test, show=False) | |
| st.pyplot(fig, use_container_width=False) | |
| st.markdown(""" | |
| **Interpretação:** | |
| * **checking_status (Saldo da Conta):** Geralmente a variável mais forte. Saldos negativos ou baixos tendem a aumentar o risco (SHAP positivo). | |
| * **duration (Duração):** Empréstimos mais longos costumam ter maior risco acumulado. | |
| * **credit_amount:** Valores muito altos podem indicar maior dificuldade de pagamento. | |
| """) | |
| # --- PÁGINA E: CLUSTERIZAÇÃO --- | |
| elif page == "e) Análise Não Supervisionada": | |
| st.title("🧩 Segmentação de Clientes (Clustering)") | |
| # Preparação dos dados para clusterização (apenas numéricos e normalizados) | |
| features_cluster = df_raw.select_dtypes(include=np.number).columns.tolist() | |
| # Remover target se estiver lá | |
| features_cluster = [f for f in features_cluster if f != 'class'] | |
| df_cluster = df_raw[features_cluster].dropna() | |
| scaler = StandardScaler() | |
| X_scaled = scaler.fit_transform(df_cluster) | |
| tab1, tab2 = st.tabs(["K-Means", "DBSCAN (Outliers)"]) | |
| with tab1: | |
| st.subheader("K-Means Clustering") | |
| k = st.slider("Número de Clusters (K):", 2, 8, 3) | |
| kmeans = KMeans(n_clusters=k, random_state=42, n_init=10) | |
| clusters = kmeans.fit_predict(X_scaled) | |
| df_cluster['Cluster_KMeans'] = clusters | |
| # Plot 2D simples usando duas variáveis principais | |
| x_axis = st.selectbox("Eixo X para plot:", features_cluster, index=0) | |
| y_axis = st.selectbox("Eixo Y para plot:", features_cluster, index=1) | |
| fig, ax = plt.subplots(figsize=(10, 6)) | |
| sns.scatterplot(data=df_cluster, x=x_axis, y=y_axis, hue='Cluster_KMeans', palette='viridis', s=100, ax=ax) | |
| plt.title(f"Segmentação K-Means (k={k})") | |
| st.pyplot(fig, use_container_width=False) | |
| st.markdown("**Interpretação dos Perfis:**") | |
| st.dataframe(df_cluster.groupby('Cluster_KMeans').mean()) | |
| with tab2: | |
| st.subheader("DBSCAN - Detecção de Outliers") | |
| eps = st.slider("Epsilon (eps):", 0.1, 5.0, 2.0, 0.1) | |
| min_samples = st.slider("Min Samples:", 2, 20, 5) | |
| dbscan = DBSCAN(eps=eps, min_samples=min_samples) | |
| clusters_db = dbscan.fit_predict(X_scaled) | |
| df_cluster['Cluster_DBSCAN'] = clusters_db | |
| # O DBSCAN rotula outliers como -1 | |
| outliers_count = (df_cluster['Cluster_DBSCAN'] == -1).sum() | |
| st.metric("Número de Outliers Detectados", outliers_count) | |
| fig, ax = plt.subplots(figsize=(10, 6)) | |
| # Plotar normais | |
| sns.scatterplot(data=df_cluster[df_cluster['Cluster_DBSCAN'] != -1], x='credit_amount', y='age', label='Normal', color='gray', alpha=0.5, ax=ax) | |
| # Plotar outliers | |
| if outliers_count > 0: | |
| sns.scatterplot(data=df_cluster[df_cluster['Cluster_DBSCAN'] == -1], x='credit_amount', y='age', label='Outlier', color='red', s=100, ax=ax) | |
| plt.title("Detecção de Perfis Atípicos (DBSCAN)") | |
| st.pyplot(fig, use_container_width=False) | |
| st.markdown("Os pontos em **vermelho** (-1) são perfis atípicos que podem indicar risco elevado ou fraude, pois não se agrupam com o comportamento padrão dos clientes.") | |
| # --- PÁGINA F: DECISÃO ESTRATÉGICA --- | |
| elif page == "f) Tomada de Decisão": | |
| st.title("🚀 Tomada de Decisão Estratégica") | |
| st.markdown(""" | |
| Com base nos resultados obtidos pelos modelos supervisionados e na análise de clusters, sugerem-se as seguintes ações para a instituição financeira: | |
| ### 1. Políticas de Concessão Diferenciadas | |
| * **Segmento de Alto Risco (Predito 'Bad'):** Implementar políticas mais rígidas. Exigir garantias (fiador, bens) ou limitar o valor do crédito (`credit_amount`) e o prazo (`duration`). | |
| * **Checking Status:** Clientes com saldo de conta corrente negativo ou baixo demonstraram alto risco (via SHAP). Para estes, a aprovação automática deve ser suspensa. | |
| ### 2. Segmentação de Clientes (K-Means) | |
| * Utilizar os clusters para criar produtos específicos. | |
| * *Exemplo:* Se um cluster é formado por jovens com baixo crédito, criar um produto de "primeiro crédito" com limites baixos e foco em educação financeira. | |
| * *Exemplo:* Clientes mais velhos com alto montante podem receber ofertas de crédito consignado ou imobiliário. | |
| ### 3. Prevenção e Monitoramento (DBSCAN) | |
| * Os **outliers** detectados pelo DBSCAN devem passar por uma mesa de crédito manual (mesa de análise). Esses casos fogem do padrão e podem representar tanto fraudes quanto oportunidades de nicho não exploradas. | |
| ### 4. Concessão Responsável | |
| * A análise de dados orienta a não endividar clientes que têm alta probabilidade de default, protegendo a saúde financeira tanto do banco quanto do consumidor. | |
| """) |