File size: 17,295 Bytes
c51ee9f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
# 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()