brunaaaz commited on
Commit
2cf2075
·
verified ·
1 Parent(s): 18939a9

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +55 -137
app.py CHANGED
@@ -9,7 +9,6 @@ from sklearn.linear_model import LogisticRegression
9
  from sklearn.neighbors import KNeighborsClassifier
10
  from sklearn.svm import SVC
11
  from sklearn.metrics import roc_curve, auc, classification_report, confusion_matrix, precision_recall_curve
12
- from imblearn.over_sampling import SMOTE
13
  import os
14
  import warnings
15
  warnings.filterwarnings('ignore')
@@ -32,131 +31,53 @@ permitindo ações preventivas como overbooking controlado e ofertas promocionai
32
  @st.cache_data
33
  def load_data():
34
  """
35
- Tenta carregar dataset real do Kaggle, fallback para dados sintéticos
36
- """
37
- try:
38
- # Verifica se o arquivo já existe
39
- if os.path.exists('hotel_bookings.csv'):
40
- df = pd.read_csv('hotel_bookings.csv')
41
- st.success("✅ Dataset real 'hotel_bookings.csv' carregado com sucesso!")
42
- return df
43
-
44
- # Tenta baixar via kagglehub
45
- try:
46
- import kagglehub
47
- st.info("📥 Baixando dataset real do Kaggle...")
48
- path = kagglehub.dataset_download("jessemostipak/hotel-booking-demand")
49
- csv_path = os.path.join(path, "hotel_bookings.csv")
50
- df = pd.read_csv(csv_path)
51
- st.success("✅ Dataset real baixado do Kaggle com sucesso!")
52
- return df
53
- except ImportError:
54
- st.warning("📦 Biblioteca kagglehub não disponível. Tentando download alternativo...")
55
-
56
- # Fallback: download direto (se disponível)
57
- try:
58
- url = "https://raw.githubusercontent.com/rfordatascience/tidytuesday/master/data/2020/2020-02-11/hotels.csv"
59
- df = pd.read_csv(url)
60
- st.success("✅ Dataset alternativo carregado!")
61
- return df
62
- except:
63
- st.warning("❌ Não foi possível baixar dados reais.")
64
-
65
- except Exception as e:
66
- st.warning(f"⚠️ Erro ao carregar dataset real: {str(e)}")
67
-
68
- # Fallback para dados sintéticos
69
- st.info("📊 Gerando dados sintéticos para demonstração...")
70
- return generate_synthetic_data()
71
-
72
- def generate_synthetic_data():
73
- """
74
- Gera dados sintéticos quando o dataset real não está disponível
75
  """
76
  np.random.seed(42)
77
- n_samples = 5000
78
 
 
79
  data = {
80
- 'hotel': np.random.choice(['Resort Hotel', 'City Hotel'], n_samples),
81
- 'is_canceled': np.random.choice([0, 1], n_samples, p=[0.7, 0.3]),
82
- 'lead_time': np.random.randint(0, 400, n_samples),
83
- 'arrival_date_year': np.random.choice([2015, 2016, 2017], n_samples),
84
- 'arrival_date_month': np.random.choice(['January', 'February', 'March', 'April', 'May', 'June',
85
- 'July', 'August', 'September', 'October', 'November', 'December'], n_samples),
86
- 'stays_in_weekend_nights': np.random.randint(0, 5, n_samples),
87
- 'stays_in_week_nights': np.random.randint(1, 15, n_samples),
88
- 'adults': np.random.randint(1, 4, n_samples),
89
- 'children': np.random.randint(0, 3, n_samples),
90
- 'babies': np.random.randint(0, 2, n_samples),
91
- 'meal': np.random.choice(['BB', 'HB', 'FB', 'SC'], n_samples),
92
- 'country': np.random.choice(['PRT', 'GBR', 'FRA', 'ESP', 'DEU', 'ITA'], n_samples),
93
- 'market_segment': np.random.choice(['Direct', 'Corporate', 'Online TA', 'Offline TA/TO', 'Complementary'], n_samples),
94
- 'distribution_channel': np.random.choice(['Direct', 'Corporate', 'TA/TO'], n_samples),
95
- 'is_repeated_guest': np.random.choice([0, 1], n_samples, p=[0.9, 0.1]),
96
- 'previous_cancellations': np.random.randint(0, 5, n_samples),
97
- 'previous_bookings_not_canceled': np.random.randint(0, 10, n_samples),
98
- 'reserved_room_type': np.random.choice(['A', 'B', 'C', 'D', 'E', 'F', 'G'], n_samples),
99
- 'assigned_room_type': np.random.choice(['A', 'B', 'C', 'D', 'E', 'F', 'G'], n_samples),
100
- 'booking_changes': np.random.randint(0, 5, n_samples),
101
- 'deposit_type': np.random.choice(['No Deposit', 'Non Refund', 'Refundable'], n_samples),
102
- 'agent': np.random.choice([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], n_samples),
103
- 'company': np.random.choice([0, 1, 2, 3, 4], n_samples),
104
- 'days_in_waiting_list': np.random.randint(0, 50, n_samples),
105
- 'customer_type': np.random.choice(['Transient', 'Contract', 'Transient-Party', 'Group'], n_samples),
106
- 'adr': np.random.uniform(50, 300, n_samples),
107
- 'required_car_parking_spaces': np.random.randint(0, 2, n_samples),
108
- 'total_of_special_requests': np.random.randint(0, 4, n_samples),
 
109
  }
110
 
111
  df = pd.DataFrame(data)
112
- return df
113
-
114
- # Carrega os dados
115
- df = load_data()
116
-
117
-
118
- # Título principal
119
- st.title("🏨 Dashboard de Previsão de Cancelamentos em Reservas Hoteleiras")
120
- st.markdown("""
121
- **Objetivo**: Desenvolver e comparar modelos preditivos para identificar reservas com maior probabilidade de cancelamento,
122
- permitindo ações preventivas como overbooking controlado e ofertas promocionais direcionadas.
123
- """)
124
-
125
- # Carregar dados
126
- @st.cache_data
127
- def load_data():
128
- np.random.seed(42)
129
- n_samples = 3000 # Reduzido para melhor performance no Spaces
130
 
131
- data = {
132
- 'hotel': np.random.choice(['Resort Hotel', 'City Hotel'], n_samples),
133
- 'is_canceled': np.random.choice([0, 1], n_samples, p=[0.7, 0.3]),
134
- 'lead_time': np.random.randint(0, 400, n_samples),
135
- 'arrival_date_month': np.random.choice(['January', 'February', 'March', 'April', 'May', 'June',
136
- 'July', 'August', 'September', 'October', 'November', 'December'], n_samples),
137
- 'stays_in_weekend_nights': np.random.randint(0, 5, n_samples),
138
- 'stays_in_week_nights': np.random.randint(1, 15, n_samples),
139
- 'adults': np.random.randint(1, 4, n_samples),
140
- 'children': np.random.randint(0, 3, n_samples),
141
- 'babies': np.random.randint(0, 2, n_samples),
142
- 'meal': np.random.choice(['BB', 'HB', 'FB', 'SC'], n_samples),
143
- 'country': np.random.choice(['PRT', 'GBR', 'FRA', 'ESP', 'DEU', 'ITA'], n_samples),
144
- 'market_segment': np.random.choice(['Direct', 'Corporate', 'Online TA', 'Offline TA/TO', 'Complementary'], n_samples),
145
- 'distribution_channel': np.random.choice(['Direct', 'Corporate', 'TA/TO'], n_samples),
146
- 'is_repeated_guest': np.random.choice([0, 1], n_samples, p=[0.9, 0.1]),
147
- 'previous_cancellations': np.random.randint(0, 5, n_samples),
148
- 'previous_bookings_not_canceled': np.random.randint(0, 10, n_samples),
149
- 'reserved_room_type': np.random.choice(['A', 'B', 'C', 'D', 'E', 'F', 'G'], n_samples),
150
- 'assigned_room_type': np.random.choice(['A', 'B', 'C', 'D', 'E', 'F', 'G'], n_samples),
151
- 'booking_changes': np.random.randint(0, 5, n_samples),
152
- 'deposit_type': np.random.choice(['No Deposit', 'Non Refund', 'Refundable'], n_samples),
153
- 'customer_type': np.random.choice(['Transient', 'Contract', 'Transient-Party', 'Group'], n_samples),
154
- 'adr': np.random.uniform(50, 300, n_samples),
155
- 'required_car_parking_spaces': np.random.randint(0, 2, n_samples),
156
- 'total_of_special_requests': np.random.randint(0, 4, n_samples),
157
- }
158
 
159
- df = pd.DataFrame(data)
160
  return df
161
 
162
  df = load_data()
@@ -192,7 +113,6 @@ elif algorithm == "Support Vector Machine":
192
  # Configurações gerais
193
  st.sidebar.subheader("Configurações Gerais")
194
  test_size = st.sidebar.slider("Tamanho do conjunto de teste", 0.1, 0.5, 0.2, 0.05)
195
- apply_smote = st.sidebar.checkbox("Aplicar SMOTE para balanceamento", value=True)
196
  cross_validation = st.sidebar.slider("Número de folds para validação cruzada", 2, 5, 3)
197
 
198
  # Análise exploratória
@@ -210,16 +130,20 @@ with col1:
210
  st.pyplot(fig)
211
 
212
  with col2:
213
- st.subheader("Top 10 Correlações")
214
- numeric_cols = df.select_dtypes(include=[np.number]).columns
215
- correlation_with_target = df[numeric_cols].corr()['is_canceled'].sort_values(ascending=False)
216
-
217
  fig, ax = plt.subplots(figsize=(6, 4))
218
- correlation_with_target.drop('is_canceled').head(10).plot(kind='barh', ax=ax, color='lightgreen')
219
- ax.set_title('Top 10 Correlações com Cancelamentos')
220
- ax.set_xlabel('Correlação')
 
221
  st.pyplot(fig)
222
 
 
 
 
 
 
 
223
  # Pré-processamento dos dados
224
  st.header("🔧 Pré-processamento dos Dados")
225
 
@@ -235,12 +159,6 @@ X_train, X_test, y_train, y_test = train_test_split(
235
  X_encoded, y, test_size=test_size, random_state=42, stratify=y
236
  )
237
 
238
- # Aplicar SMOTE se selecionado
239
- if apply_smote:
240
- smote = SMOTE(random_state=42)
241
- X_train, y_train = smote.fit_resample(X_train, y_train)
242
- st.success("✅ SMOTE aplicado para balanceamento dos dados")
243
-
244
  # Normalizar dados
245
  scaler = StandardScaler()
246
  X_train_scaled = scaler.fit_transform(X_train)
@@ -380,13 +298,13 @@ with st.spinner(f"Treinando modelo {algorithm}..."):
380
  st.markdown("""
381
  **Com base na análise realizada, recomenda-se:**
382
 
383
- 1. **Segmentação de Clientes**: Focar em reservas corporativas com lead time superior a 30 dias
384
- 2. **Política de Overbooking**: Aplicar overbooking controlado de 3-5% para reservas de alta probabilidade de cancelamento
385
- 3. **Ações Preventivas**: Oferecer upgrades ou benefícios para reservas identificadas como risco médio-alto
386
- 4. **Comunicação Proativa**: Estabelecer contato com clientes de alto risco 48h antes do check-in
387
 
388
- **Variáveis mais preditivas de cancelamento:**
389
- - Lead time elevado
390
  - Histórico de cancelamentos anteriores
391
  - Tipo de depósito não reembolsável
392
  - Canal de distribuição Online TA
 
9
  from sklearn.neighbors import KNeighborsClassifier
10
  from sklearn.svm import SVC
11
  from sklearn.metrics import roc_curve, auc, classification_report, confusion_matrix, precision_recall_curve
 
12
  import os
13
  import warnings
14
  warnings.filterwarnings('ignore')
 
31
  @st.cache_data
32
  def load_data():
33
  """
34
+ Carrega dados sintéticos (evita problemas de compatibilidade)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35
  """
36
  np.random.seed(42)
37
+ n_samples = 4000
38
 
39
+ # Criar dados mais realistas baseados no dataset real
40
  data = {
41
+ 'hotel': np.random.choice(['Resort Hotel', 'City Hotel'], n_samples, p=[0.6, 0.4]),
42
+ 'is_canceled': np.random.choice([0, 1], n_samples, p=[0.65, 0.35]),
43
+ 'lead_time': np.random.gamma(2, 50, n_samples).astype(int),
44
+ 'arrival_date_year': np.random.choice([2015, 2016, 2017], n_samples, p=[0.3, 0.4, 0.3]),
45
+ 'arrival_date_month': np.random.choice([
46
+ 'January', 'February', 'March', 'April', 'May', 'June',
47
+ 'July', 'August', 'September', 'October', 'November', 'December'
48
+ ], n_samples),
49
+ 'stays_in_weekend_nights': np.random.poisson(1, n_samples),
50
+ 'stays_in_week_nights': np.random.poisson(3, n_samples),
51
+ 'adults': np.random.choice([1, 2, 3, 4], n_samples, p=[0.1, 0.7, 0.15, 0.05]),
52
+ 'children': np.random.choice([0, 1, 2], n_samples, p=[0.8, 0.15, 0.05]),
53
+ 'babies': np.random.choice([0, 1], n_samples, p=[0.95, 0.05]),
54
+ 'meal': np.random.choice(['BB', 'HB', 'FB', 'SC'], n_samples, p=[0.7, 0.2, 0.05, 0.05]),
55
+ 'country': np.random.choice(['PRT', 'GBR', 'FRA', 'ESP', 'DEU', 'ITA', 'IRL', 'BEL'], n_samples),
56
+ 'market_segment': np.random.choice([
57
+ 'Direct', 'Corporate', 'Online TA', 'Offline TA/TO', 'Complementary', 'Groups'
58
+ ], n_samples, p=[0.2, 0.1, 0.5, 0.15, 0.02, 0.03]),
59
+ 'distribution_channel': np.random.choice(['Direct', 'Corporate', 'TA/TO'], n_samples, p=[0.2, 0.1, 0.7]),
60
+ 'is_repeated_guest': np.random.choice([0, 1], n_samples, p=[0.95, 0.05]),
61
+ 'previous_cancellations': np.random.poisson(0.1, n_samples),
62
+ 'previous_bookings_not_canceled': np.random.poisson(0.5, n_samples),
63
+ 'reserved_room_type': np.random.choice(['A', 'B', 'C', 'D', 'E', 'F', 'G'], n_samples, p=[0.4, 0.2, 0.15, 0.1, 0.08, 0.05, 0.02]),
64
+ 'assigned_room_type': np.random.choice(['A', 'B', 'C', 'D', 'E', 'F', 'G'], n_samples, p=[0.4, 0.2, 0.15, 0.1, 0.08, 0.05, 0.02]),
65
+ 'booking_changes': np.random.poisson(0.3, n_samples),
66
+ 'deposit_type': np.random.choice(['No Deposit', 'Non Refund', 'Refundable'], n_samples, p=[0.85, 0.1, 0.05]),
67
+ 'customer_type': np.random.choice(['Transient', 'Contract', 'Transient-Party', 'Group'], n_samples, p=[0.7, 0.1, 0.15, 0.05]),
68
+ 'adr': np.random.gamma(5, 20, n_samples) + 50, # ADR entre 50-250
69
+ 'required_car_parking_spaces': np.random.choice([0, 1], n_samples, p=[0.9, 0.1]),
70
+ 'total_of_special_requests': np.random.poisson(0.5, n_samples),
71
  }
72
 
73
  df = pd.DataFrame(data)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74
 
75
+ # Ajustar lead_time para ser mais realista
76
+ df['lead_time'] = np.clip(df['lead_time'], 0, 400)
77
+
78
+ # Ajustar ADR para ter valores mais realistas
79
+ df['adr'] = np.clip(df['adr'], 50, 300)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
80
 
 
81
  return df
82
 
83
  df = load_data()
 
113
  # Configurações gerais
114
  st.sidebar.subheader("Configurações Gerais")
115
  test_size = st.sidebar.slider("Tamanho do conjunto de teste", 0.1, 0.5, 0.2, 0.05)
 
116
  cross_validation = st.sidebar.slider("Número de folds para validação cruzada", 2, 5, 3)
117
 
118
  # Análise exploratória
 
130
  st.pyplot(fig)
131
 
132
  with col2:
133
+ st.subheader("Lead Time vs Cancelamentos")
 
 
 
134
  fig, ax = plt.subplots(figsize=(6, 4))
135
+ sns.boxplot(data=df, x='is_canceled', y='lead_time', ax=ax)
136
+ ax.set_title('Lead Time por Status de Cancelamento')
137
+ ax.set_xlabel('Cancelado')
138
+ ax.set_ylabel('Lead Time (dias)')
139
  st.pyplot(fig)
140
 
141
+ # Informações do dataset
142
+ st.sidebar.header("📊 Informações do Dataset")
143
+ st.sidebar.write(f"**Registros**: {len(df):,}")
144
+ st.sidebar.write(f"**Cancelamentos**: {df['is_canceled'].sum():,} ({df['is_canceled'].mean():.1%})")
145
+ st.sidebar.write(f"**Variáveis**: {len(df.columns)}")
146
+
147
  # Pré-processamento dos dados
148
  st.header("🔧 Pré-processamento dos Dados")
149
 
 
159
  X_encoded, y, test_size=test_size, random_state=42, stratify=y
160
  )
161
 
 
 
 
 
 
 
162
  # Normalizar dados
163
  scaler = StandardScaler()
164
  X_train_scaled = scaler.fit_transform(X_train)
 
298
  st.markdown("""
299
  **Com base na análise realizada, recomenda-se:**
300
 
301
+ 1. **Segmentação de Clientes**: Focar em reservas com lead time > 100 dias
302
+ 2. **Política de Overbooking**: Aplicar overbooking de 3-5% para reservas de alto risco
303
+ 3. **Ações Preventivas**: Oferecer upgrades para reservas identificadas como risco médio-alto
304
+ 4. **Comunicação Proativa**: Contato com clientes de alto risco 48h antes do check-in
305
 
306
+ **Fatores de risco identificados:**
307
+ - Lead time elevado (> 100 dias)
308
  - Histórico de cancelamentos anteriores
309
  - Tipo de depósito não reembolsável
310
  - Canal de distribuição Online TA