Spaces:
Sleeping
Sleeping
| # app.py - Dashboard Interativo de Cancelamento de Reservas Hoteleiras | |
| import streamlit as st | |
| import pandas as pd | |
| import numpy as np | |
| import matplotlib.pyplot as plt | |
| import seaborn as sns | |
| import joblib | |
| from sklearn.metrics import (accuracy_score, precision_score, recall_score, f1_score, | |
| roc_auc_score, roc_curve, confusion_matrix) | |
| from sklearn.linear_model import LogisticRegression | |
| from sklearn.neighbors import KNeighborsClassifier | |
| from sklearn.svm import SVC | |
| from sklearn.model_selection import cross_val_score | |
| from sklearn.preprocessing import StandardScaler | |
| import plotly.graph_objects as go | |
| import plotly.express as px | |
| import time | |
| import warnings | |
| warnings.filterwarnings('ignore') | |
| # Configuração da página | |
| st.set_page_config( | |
| page_title="Dashboard - Cancelamento de Reservas", | |
| page_icon="🏨", | |
| layout="wide", | |
| initial_sidebar_state="expanded" | |
| ) | |
| # CSS customizado | |
| st.markdown(""" | |
| <style> | |
| .main-header { | |
| font-size: 2.5rem; | |
| color: #1f77b4; | |
| text-align: center; | |
| margin-bottom: 2rem; | |
| } | |
| .metric-card { | |
| background-color: #f0f2f6; | |
| padding: 1rem; | |
| border-radius: 10px; | |
| border-left: 4px solid #1f77b4; | |
| margin: 0.5rem 0; | |
| } | |
| .best-model { | |
| background-color: #d4edda; | |
| border-left: 4px solid #28a745; | |
| padding: 1rem; | |
| border-radius: 10px; | |
| margin: 1rem 0; | |
| } | |
| .parameter-section { | |
| background-color: #e8f4f8; | |
| padding: 1rem; | |
| border-radius: 10px; | |
| margin: 1rem 0; | |
| } | |
| </style> | |
| """, unsafe_allow_html=True) | |
| class HotelBookingDashboard: | |
| def __init__(self): | |
| self.models = {} | |
| self.results = {} | |
| self.X_train = None | |
| self.X_test = None | |
| self.y_train = None | |
| self.y_test = None | |
| def load_data(self): | |
| """Carrega dados pré-processados ou cria dados de demonstração""" | |
| try: | |
| # Tentar carregar dados salvos | |
| saved_data = joblib.load('modelos_treinados.pkl') | |
| self.models = saved_data['models'] | |
| self.X_train = saved_data['X_train'] | |
| self.X_test = saved_data['X_test'] | |
| self.y_train = saved_data['y_train'] | |
| self.y_test = saved_data['y_test'] | |
| return True | |
| except: | |
| # Criar dados de demonstração | |
| st.info("📝 Criando dados de demonstração...") | |
| return self._create_demo_data() | |
| def _create_demo_data(self): | |
| """Cria dados de demonstração realísticos""" | |
| np.random.seed(42) | |
| n_samples = 2000 | |
| # Features baseadas no dataset real de hotéis | |
| features = { | |
| 'lead_time': np.random.gamma(2, 50, n_samples), | |
| 'adr': np.random.normal(100, 30, n_samples), | |
| 'adults': np.random.poisson(2, n_samples), | |
| 'children': np.random.poisson(0.3, n_samples), | |
| 'previous_cancellations': np.random.poisson(0.1, n_samples), | |
| 'is_repeated_guest': np.random.binomial(1, 0.1, n_samples), | |
| 'required_car_parking_spaces': np.random.binomial(1, 0.2, n_samples), | |
| 'total_of_special_requests': np.random.poisson(0.5, n_samples), | |
| 'booking_changes': np.random.poisson(0.3, n_samples), | |
| } | |
| X = pd.DataFrame(features) | |
| # Criar target com relação realística | |
| cancellation_prob = 1 / (1 + np.exp(-( | |
| X['lead_time'] * 0.01 + | |
| X['adr'] * 0.005 - | |
| X['is_repeated_guest'] * 0.8 - | |
| X['required_car_parking_spaces'] * 0.3 + | |
| X['total_of_special_requests'] * -0.4 + | |
| np.random.normal(0, 0.5, n_samples) | |
| ))) | |
| y = (cancellation_prob > 0.5).astype(int) | |
| # Split dos dados | |
| from sklearn.model_selection import train_test_split | |
| self.X_train, self.X_test, self.y_train, self.y_test = train_test_split( | |
| X, y, test_size=0.3, random_state=42, stratify=y | |
| ) | |
| # Normalizar | |
| scaler = StandardScaler() | |
| self.X_train = scaler.fit_transform(self.X_train) | |
| self.X_test = scaler.transform(self.X_test) | |
| # Treinar modelos de demonstração | |
| self._train_demo_models() | |
| return True | |
| def _train_demo_models(self): | |
| """Treina modelos de demonstração""" | |
| # Regressão Logística | |
| lr = LogisticRegression(random_state=42, max_iter=1000) | |
| lr.fit(self.X_train, self.y_train) | |
| self.models['RL_Padrao'] = lr | |
| # KNN | |
| knn = KNeighborsClassifier(n_neighbors=5) | |
| knn.fit(self.X_train, self.y_train) | |
| self.models['KNN_Padrao'] = knn | |
| # SVM | |
| svm = SVC(probability=True, random_state=42) | |
| svm.fit(self.X_train, self.y_train) | |
| self.models['SVM_Padrao'] = svm | |
| # Avaliar modelos demo | |
| for name, model in self.models.items(): | |
| metrics, _, _ = self.evaluate_model(model, name, 0) | |
| self.results[name] = metrics | |
| def train_logistic_regression(self, C=1.0, penalty='l2', solver='lbfgs'): | |
| """Treina Regressão Logística""" | |
| model = LogisticRegression(C=C, penalty=penalty, solver=solver, | |
| max_iter=1000, random_state=42) | |
| start_time = time.time() | |
| model.fit(self.X_train, self.y_train) | |
| training_time = time.time() - start_time | |
| return model, training_time | |
| def train_knn(self, n_neighbors=5, metric='euclidean', weights='uniform'): | |
| """Treina KNN""" | |
| model = KNeighborsClassifier(n_neighbors=n_neighbors, metric=metric, | |
| weights=weights) | |
| start_time = time.time() | |
| model.fit(self.X_train, self.y_train) | |
| training_time = time.time() - start_time | |
| return model, training_time | |
| def train_svm(self, C=1.0, kernel='rbf', gamma='scale'): | |
| """Treina SVM""" | |
| model = SVC(C=C, kernel=kernel, gamma=gamma, probability=True, | |
| random_state=42) | |
| start_time = time.time() | |
| model.fit(self.X_train, self.y_train) | |
| training_time = time.time() - start_time | |
| return model, training_time | |
| def evaluate_model(self, model, model_name, training_time): | |
| """Avalia modelo e retorna métricas""" | |
| y_pred = model.predict(self.X_test) | |
| y_proba = model.predict_proba(self.X_test)[:, 1] | |
| metrics = { | |
| 'Acurácia': accuracy_score(self.y_test, y_pred), | |
| 'Precisão': precision_score(self.y_test, y_pred, zero_division=0), | |
| 'Recall': recall_score(self.y_test, y_pred, zero_division=0), | |
| 'F1-Score': f1_score(self.y_test, y_pred, zero_division=0), | |
| 'AUC-ROC': roc_auc_score(self.y_test, y_proba), | |
| 'Tempo Treino (s)': training_time | |
| } | |
| # Curva ROC | |
| fpr, tpr, _ = roc_curve(self.y_test, y_proba) | |
| roc_data = {'fpr': fpr, 'tpr': tpr, 'auc': metrics['AUC-ROC']} | |
| # Matriz de confusão | |
| cm = confusion_matrix(self.y_test, y_pred) | |
| return metrics, roc_data, cm | |
| def plot_roc_comparison(self, current_roc, current_model_name): | |
| """Plota comparação de curvas ROC""" | |
| fig = go.Figure() | |
| # Curva do modelo atual | |
| fig.add_trace(go.Scatter( | |
| x=current_roc['fpr'], y=current_roc['tpr'], | |
| mode='lines', name=f'{current_model_name} (AUC = {current_roc["auc"]:.3f})', | |
| line=dict(width=3, color='red') | |
| )) | |
| # Curvas dos outros modelos | |
| colors = ['blue', 'green', 'orange', 'purple'] | |
| for i, (model_name, model) in enumerate(self.models.items()): | |
| if model_name != current_model_name: | |
| try: | |
| y_proba = model.predict_proba(self.X_test)[:, 1] | |
| fpr, tpr, _ = roc_curve(self.y_test, y_proba) | |
| auc = roc_auc_score(self.y_test, y_proba) | |
| fig.add_trace(go.Scatter( | |
| x=fpr, y=tpr, mode='lines', | |
| name=f'{model_name} (AUC = {auc:.3f})', | |
| line=dict(width=2, color=colors[i % len(colors)], dash='dash') | |
| )) | |
| except: | |
| continue | |
| # Linha de referência | |
| fig.add_trace(go.Scatter( | |
| x=[0, 1], y=[0, 1], mode='lines', | |
| name='Classificador Aleatório', line=dict(dash='dash', color='grey') | |
| )) | |
| fig.update_layout( | |
| title='Comparação das Curvas ROC', | |
| xaxis_title='Taxa de Falsos Positivos', | |
| yaxis_title='Taxa de Verdadeiros Positivos', | |
| width=600, height=500 | |
| ) | |
| return fig | |
| def main(): | |
| # Inicializar dashboard | |
| dashboard = HotelBookingDashboard() | |
| # Carregar dados | |
| if not dashboard.load_data(): | |
| st.error("❌ Erro ao carregar dados") | |
| return | |
| # Header | |
| st.markdown('<h1 class="main-header">🏨 Dashboard Interativo - Cancelamento de Reservas</h1>', | |
| unsafe_allow_html=True) | |
| # Sidebar | |
| st.sidebar.header("⚙️ Configurações do Modelo") | |
| # Seleção do algoritmo | |
| algorithm = st.sidebar.selectbox( | |
| "Escolha o algoritmo:", | |
| ["Regressão Logística", "KNN", "SVM"], | |
| index=0 | |
| ) | |
| # Parâmetros específicos | |
| st.sidebar.subheader("📊 Parâmetros do Modelo") | |
| if algorithm == "Regressão Logística": | |
| st.sidebar.markdown('<div class="parameter-section">', unsafe_allow_html=True) | |
| C_lr = st.sidebar.slider("Parâmetro C (Regularização)", 0.01, 10.0, 1.0, 0.01) | |
| penalty = st.sidebar.selectbox("Tipo de Penalidade", ["l2", "l1"]) | |
| solver = st.sidebar.selectbox("Algoritmo", ["lbfgs", "liblinear", "saga"]) | |
| st.sidebar.markdown('</div>', unsafe_allow_html=True) | |
| elif algorithm == "KNN": | |
| st.sidebar.markdown('<div class="parameter-section">', unsafe_allow_html=True) | |
| n_neighbors = st.sidebar.slider("Número de Vizinhos (k)", 1, 50, 5) | |
| metric = st.sidebar.selectbox("Métrica de Distância", | |
| ["euclidean", "manhattan", "minkowski"]) | |
| weights = st.sidebar.selectbox("Pesos", ["uniform", "distance"]) | |
| st.sidebar.markdown('</div>', unsafe_allow_html=True) | |
| else: # SVM | |
| st.sidebar.markdown('<div class="parameter-section">', unsafe_allow_html=True) | |
| C_svm = st.sidebar.slider("Parâmetro C", 0.01, 10.0, 1.0, 0.01) | |
| kernel = st.sidebar.selectbox("Kernel", ["rbf", "linear", "poly", "sigmoid"]) | |
| gamma = st.sidebar.selectbox("Gamma", ["scale", "auto"]) | |
| st.sidebar.markdown('</div>', unsafe_allow_html=True) | |
| # Botão de treinamento | |
| train_button = st.sidebar.button("🚀 Treinar Modelo", type="primary") | |
| # Informações | |
| st.sidebar.markdown("---") | |
| st.sidebar.info(""" | |
| **Instruções:** | |
| 1. Escolha o algoritmo | |
| 2. Ajuste os parâmetros | |
| 3. Clique em 'Treinar Modelo' | |
| 4. Analise os resultados | |
| """) | |
| # Conteúdo principal | |
| if train_button: | |
| with st.spinner(f"Treinando modelo {algorithm}..."): | |
| # Treinar modelo | |
| if algorithm == "Regressão Logística": | |
| model, training_time = dashboard.train_logistic_regression( | |
| C=C_lr, penalty=penalty, solver=solver | |
| ) | |
| model_name = f"RL_C={C_lr}" | |
| elif algorithm == "KNN": | |
| model, training_time = dashboard.train_knn( | |
| n_neighbors=n_neighbors, metric=metric, weights=weights | |
| ) | |
| model_name = f"KNN_k={n_neighbors}_{metric}" | |
| else: # SVM | |
| model, training_time = dashboard.train_svm( | |
| C=C_svm, kernel=kernel, gamma=gamma | |
| ) | |
| model_name = f"SVM_{kernel}_C={C_svm}" | |
| # Avaliar | |
| metrics, roc_data, cm = dashboard.evaluate_model(model, model_name, training_time) | |
| # Salvar modelo | |
| dashboard.models[model_name] = model | |
| dashboard.results[model_name] = metrics | |
| # Resultados | |
| st.success(f"✅ Modelo {algorithm} treinado com sucesso!") | |
| # Métricas | |
| st.subheader("📈 Métricas de Desempenho") | |
| col1, col2, col3, col4, col5 = st.columns(5) | |
| with col1: st.metric("Acurácia", f"{metrics['Acurácia']:.4f}") | |
| with col2: st.metric("Precisão", f"{metrics['Precisão']:.4f}") | |
| with col3: st.metric("Recall", f"{metrics['Recall']:.4f}") | |
| with col4: st.metric("F1-Score", f"{metrics['F1-Score']:.4f}") | |
| with col5: st.metric("AUC-ROC", f"{metrics['AUC-ROC']:.4f}") | |
| # Visualizações | |
| st.subheader("📊 Visualizações") | |
| col1, col2 = st.columns(2) | |
| with col1: | |
| # Curva ROC | |
| roc_fig = dashboard.plot_roc_comparison(roc_data, model_name) | |
| st.plotly_chart(roc_fig, use_container_width=True) | |
| with col2: | |
| # Matriz de confusão | |
| fig_cm, ax = plt.subplots(figsize=(6, 4)) | |
| sns.heatmap(cm, annot=True, fmt='d', cmap='Blues', ax=ax) | |
| ax.set_xlabel('Predito') | |
| ax.set_ylabel('Verdadeiro') | |
| ax.set_title('Matriz de Confusão') | |
| st.pyplot(fig_cm) | |
| # Análise | |
| st.subheader("🔬 Análise do Modelo") | |
| col1, col2 = st.columns(2) | |
| with col1: | |
| st.markdown("### 📋 Interpretação") | |
| if metrics['F1-Score'] >= 0.7: | |
| st.success("**Excelente desempenho!** Modelo bem balanceado.") | |
| elif metrics['F1-Score'] >= 0.5: | |
| st.info("**Bom desempenho!** Resultados satisfatórios.") | |
| else: | |
| st.warning("**Desempenho moderado.** Considere ajustar parâmetros.") | |
| if metrics['AUC-ROC'] >= 0.8: | |
| st.success("**Ótima discriminação!** Separa bem as classes.") | |
| elif metrics['AUC-ROC'] >= 0.7: | |
| st.info("**Boa discriminação!** Separação adequada.") | |
| else: | |
| st.warning("**Discriminação moderada.** Espaço para melhorias.") | |
| with col2: | |
| st.markdown("### 💡 Recomendações") | |
| recommendations = [] | |
| if metrics['Precisão'] < 0.6: | |
| recommendations.append("Aumente o threshold para reduzir falsos positivos") | |
| if metrics['Recall'] < 0.6: | |
| recommendations.append("Diminua o threshold para capturar mais positivos") | |
| if algorithm == "KNN" and n_neighbors < 5: | |
| recommendations.append("Considere aumentar k para reduzir overfitting") | |
| if algorithm == "SVM" and training_time > 5: | |
| recommendations.append("Para dados grandes, use kernel linear") | |
| for rec in recommendations: | |
| st.write(f"• {rec}") | |
| if not recommendations: | |
| st.success("Parâmetros bem ajustados!") | |
| # Ranking | |
| st.subheader("🏆 Ranking dos Modelos") | |
| if dashboard.results: | |
| results_df = pd.DataFrame(dashboard.results).T | |
| results_df = results_df.sort_values('F1-Score', ascending=False) | |
| st.dataframe(results_df.style.format("{:.4f}").background_gradient(cmap='Blues')) | |
| best_model = results_df.index[0] | |
| st.markdown(f'<div class="best-model">🎉 **Melhor Modelo:** {best_model} (F1: {results_df.loc[best_model, "F1-Score"]:.4f})</div>', | |
| unsafe_allow_html=True) | |
| else: | |
| # Tela inicial | |
| st.info("👈 **Configure o modelo na sidebar e clique em 'Treinar Modelo' para começar**") | |
| # Mostrar modelos pré-treinados | |
| if dashboard.models: | |
| st.subheader("📋 Modelos Pré-treinados Disponíveis") | |
| results_df = pd.DataFrame(dashboard.results).T | |
| st.dataframe(results_df.style.format("{:.4f}").background_gradient(cmap='Blues')) | |
| # Gráfico de comparação inicial | |
| st.subheader("📊 Comparação Inicial") | |
| fig = go.Figure() | |
| for model_name, metrics in dashboard.results.items(): | |
| fig.add_trace(go.Bar( | |
| name=model_name, | |
| x=list(metrics.keys())[:5], # Métricas principais | |
| y=[metrics[k] for k in list(metrics.keys())[:5]] | |
| )) | |
| fig.update_layout( | |
| barmode='group', | |
| title='Comparação de Métricas - Modelos Pré-treinados', | |
| xaxis_title='Métricas', | |
| yaxis_title='Valor' | |
| ) | |
| st.plotly_chart(fig, use_container_width=True) | |
| if __name__ == "__main__": | |
| main() |