Programa de Pós-graduação em Computação Aplicada – PPCA (UnB)
Análise Estatística de Dados e Informações - Prova Final
Professor: João Gabriel de Moraes Souza
Aluno: Angelo Donizete Buso Júnior
import warnings
warnings.filterwarnings("ignore")
# Manipulação de Dados
import pandas as pd
from IPython.display import display
import numpy as np
# Visualização de Dados
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
import seaborn as sns
# Estatística e Testes Estatísticos
import shap
from scipy import stats
from scipy.stats import kurtosis, skew, f_oneway, shapiro, levene, kruskal
import statsmodels.api as sm
from statsmodels.formula.api import ols
from statsmodels.stats.outliers_influence import variance_inflation_factor
# Pré-Processamento de Dados
from sklearn.model_selection import KFold, cross_validate
from sklearn.preprocessing import StandardScaler, LabelEncoder, MinMaxScaler, OneHotEncoder
from imblearn.under_sampling import RandomUnderSampler
from imblearn.over_sampling import SMOTE
from collections import Counter
# Algoritmos de Machine Learning
from sklearn.linear_model import LinearRegression, LogisticRegression
from sklearn.svm import SVC
# Treinamento e Validação de Modelos
from sklearn.model_selection import train_test_split, cross_val_score, GridSearchCV, StratifiedKFold
# Avaliação de Modelos
from sklearn.metrics import (
mean_squared_error, mean_absolute_error, r2_score,
confusion_matrix, accuracy_score, precision_score, recall_score,
classification_report, roc_auc_score, roc_curve, ConfusionMatrixDisplay
)
from scikitplot.metrics import plot_confusion_matrix, plot_roc
# Configurações Opcionais
pd.set_option('display.float_format', '{:.2f}'.format)
sns.set_theme(style="whitegrid")
plt.rc("figure", figsize=(10, 6))
# Verificação de Versões
print("Versão do Seaborn:", sns.__version__)
print("Versão do Pandas:", pd.__version__)
Versão do Seaborn: 0.13.2 Versão do Pandas: 2.2.3
Questão 2¶
1. Compreensão do Problema de Negócio¶
No setor hoteleiro, compreender os fatores que levam ao cancelamento de reservas é essencial para otimizar a gestão de ocupação e maximizar a receita. Cancelamentos inesperados podem resultar em quartos vagos, impactando negativamente a rentabilidade dos hotéis. A aplicação de modelos preditivos permite antecipar o comportamento dos clientes, possibilitando a implementação de estratégias proativas para mitigar os efeitos dos cancelamentos.
Neste estudo, focaremos na previsão de cancelamentos de reservas utilizando o conjunto de dados Hotel Booking Demand, disponível no Kaggle. Este dataset compreende informações sobre reservas em dois tipos de hotéis: um hotel urbano e um resort.
Para prever a probabilidade de cancelamento de uma reserva, utilizaremos a Regressão Logística, uma técnica estatística adequada para modelar desfechos binários, como cancelado ou não cancelado. Além disso, aplicaremos a metodologia CRISP-DM (Cross-Industry Standard Process for Data Mining) para estruturar nossa análise, garantindo uma abordagem sistemática e eficaz.
Este estudo visa não apenas construir um modelo preditivo, mas também identificar e interpretar as variáveis que mais influenciam o cancelamento de reservas. Compreender esses fatores permitirá aos gestores hoteleiros desenvolver estratégias direcionadas para reduzir os cancelamentos e melhorar a eficiência operacional.
1.1 Dicionário de Dados¶
Informações sobre os atributos:¶
| Nome da Coluna | Descrição |
|---|---|
| hotel | Tipo de hotel (Resort Hotel ou City Hotel) |
| is_canceled variável target | Indica se a reserva foi cancelada (1) ou não (0) |
| lead_time | Número de dias entre a data de inserção da reserva no sistema e a data de chegada |
| arrival_date_year | Ano da data de chegada |
| arrival_date_month | Mês da data de chegada |
| arrival_date_week_number | Número da semana do ano referente à data de chegada |
| arrival_date_day_of_month | Dia do mês da data de chegada |
| stays_in_weekend_nights | Número de noites de fim de semana (sábado ou domingo) que o hóspede permaneceu ou reservou para permanecer no hotel |
| stays_in_week_nights | Número de noites de semana (segunda a sexta-feira) que o hóspede permaneceu ou reservou para permanecer no hotel |
| adults | Número de adultos |
| children | Número de crianças |
| babies | Número de bebês |
| meal | Tipo de refeição reservada. Categorias: BB (Bed & Breakfast), HB (Half board - café da manhã e uma outra refeição), FB (Full board - café da manhã, almoço e jantar), SC (Self Catering - sem pacote de refeições) |
| country | País de origem, representado no formato ISO 3155–3:2013 |
| market_segment | Segmento de mercado da reserva. Por exemplo, TA (Travel Agents) ou TO (Tour Operators) |
| distribution_channel | Canal de distribuição da reserva. Por exemplo, TA (Travel Agents) ou TO (Tour Operators) |
| is_repeated_guest | Indica se o hóspede é repetido (1) ou não (0) |
| previous_cancellations | Número de reservas anteriores que foram canceladas pelo cliente antes da reserva atual |
| previous_bookings_not_canceled | Número de reservas anteriores não canceladas pelo cliente antes da reserva atual |
| reserved_room_type | Código do tipo de quarto reservado |
| assigned_room_type | Código do tipo de quarto atribuído à reserva |
| booking_changes | Número de alterações feitas na reserva desde a inserção no sistema até o momento do check-in ou cancelamento |
| deposit_type | Indicação se o cliente fez um depósito para garantir a reserva. Categorias: No Deposit (nenhum depósito), Non Refund (depósito no valor total da estadia), Refundable (depósito com valor inferior ao custo total da estadia) |
| agent | ID da agência de viagens que fez a reserva |
| company | ID da empresa responsável pela reserva ou pagamento |
| days_in_waiting_list | Número de dias que a reserva ficou na lista de espera antes de ser confirmada para o cliente |
| customer_type | Tipo de cliente: Contract (contrato), Group (grupo), Transient (transitório), Transient-party (transitório com outros) |
| adr | Taxa Média Diária, calculada dividindo a soma de todas as transações de hospedagem pelo número total de noites de estadia |
| required_car_parking_spaces | Número de vagas de estacionamento requeridas pelo cliente |
| total_of_special_requests | Número de pedidos especiais feitos pelo cliente (por exemplo, cama de solteiro ou andar alto) |
| reservation_status | Status final da reserva: Canceled (cancelada), Check-Out (cliente fez check-in e já saiu), No-Show (cliente não fez check-in e não informou o motivo) |
| reservation_status_date | Data em que o último status foi definido |
2. Coleta de Dados¶
Para este projeto de machine learning, utilizaremos o conjunto de dados Hotel Booking Demand, disponível no Kaggle.
Este dataset abrange informações detalhadas sobre reservas em dois tipos de hotéis: City Hotel e Resort Hotel. As variáveis presentes podem ser classificadas nas seguintes categorias:
Informações sobre a reserva:
is_canceled: Indica se a reserva foi cancelada ou não.lead_time: Número de dias entre a data da reserva e a data de chegada.arrival_date_year,arrival_date_month,arrival_date_week_number,arrival_date_day_of_month: Detalhes sobre a data de chegada.stays_in_weekend_nights,stays_in_week_nights: Número de noites de estadia durante fins de semana e dias da semana.
Informações sobre os hóspedes:
adults,children,babies: Número de adultos, crianças e bebês na reserva.country: País de origem do hóspede.is_repeated_guest: Indica se o hóspede é recorrente.
Informações sobre a estadia:
reserved_room_type,assigned_room_type: Tipo de quarto reservado e tipo de quarto atribuído.booking_changes: Número de alterações feitas na reserva.deposit_type: Tipo de depósito associado à reserva.agent,company: IDs da agência de viagens e da empresa associada à reserva.days_in_waiting_list: Número de dias que a reserva ficou na lista de espera.customer_type: Tipo de cliente (e.g., contrato, grupo, transiente).
Informações financeiras:
adr: Taxa média diária aplicada à reserva.required_car_parking_spaces: Número de vagas de estacionamento requeridas.total_of_special_requests: Número de pedidos especiais feitos pelo hóspede.
Essas informações nos permitirão identificar padrões e fatores que influenciam o cancelamento de reservas, auxiliando na construção de um modelo preditivo eficaz.
Dicas:
- Observar as extensão do arquivo dataSet.
- Verificar os acessos aos dataSet (LGPD).
- "Timeframe" dos dados (desde quando tem-se os dados).
- Valores Missing (será substituido por default ou outro valor - média, por exemplo).
- Cabeçalho (header=T), caso False, irá indicar cabeçalho.
- Dados possuem algum comentário.
- Possui delimitador os dados.
2.1 Carga dados¶
Dicas:
(i) Observar as extensão do arquivo dataSet;
(ii) Verificar os acessos aos dataSet;
(iii) "Timeframe" dos dados (desde quando tem-se os dados);
(iv) Valores Missing (será substituido por default ou outro valor - média, por exemplo);
(v) Cabeçalho (header=T), caso False, irá indicar cabeçalho;
(vi) Dados possuem algum comentário;
(vii) Possui delimitador os dados;
(viii) Até 3 dimensões PANDAS (1Series, 2DataFrame e 3Panel). Acima 3 dim NUMPY (nparray).
path = "/home/buso/mestrado/aedi-ppca/dados/hotel_bookings.csv"
df = pd.read_csv(path)
print('Os dados possuem {} linhas e {} colunas'.format(df.shape[0], df.shape[1]))
df.head()
Os dados possuem 119390 linhas e 32 colunas
| hotel | is_canceled | lead_time | arrival_date_year | arrival_date_month | arrival_date_week_number | arrival_date_day_of_month | stays_in_weekend_nights | stays_in_week_nights | adults | ... | deposit_type | agent | company | days_in_waiting_list | customer_type | adr | required_car_parking_spaces | total_of_special_requests | reservation_status | reservation_status_date | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | Resort Hotel | 0 | 342 | 2015 | July | 27 | 1 | 0 | 0 | 2 | ... | No Deposit | NaN | NaN | 0 | Transient | 0.0 | 0 | 0 | Check-Out | 2015-07-01 |
| 1 | Resort Hotel | 0 | 737 | 2015 | July | 27 | 1 | 0 | 0 | 2 | ... | No Deposit | NaN | NaN | 0 | Transient | 0.0 | 0 | 0 | Check-Out | 2015-07-01 |
| 2 | Resort Hotel | 0 | 7 | 2015 | July | 27 | 1 | 0 | 1 | 1 | ... | No Deposit | NaN | NaN | 0 | Transient | 75.0 | 0 | 0 | Check-Out | 2015-07-02 |
| 3 | Resort Hotel | 0 | 13 | 2015 | July | 27 | 1 | 0 | 1 | 1 | ... | No Deposit | 304.0 | NaN | 0 | Transient | 75.0 | 0 | 0 | Check-Out | 2015-07-02 |
| 4 | Resort Hotel | 0 | 14 | 2015 | July | 27 | 1 | 0 | 2 | 2 | ... | No Deposit | 240.0 | NaN | 0 | Transient | 98.0 | 0 | 1 | Check-Out | 2015-07-03 |
5 rows × 32 columns
def visaogeral(df, mensagem):
print(f'{mensagem}:\n')
print("Qtd Observações:", df.shape[0])
print("\nQtd Atributos:", df.shape[1])
print("\nAtributos:")
print(df.columns.values)
# print(df.columns.tolist())
print("\nQtd Valores missing:", df.isnull().sum().values.sum())
print("\nValores Unicos:")
print(df.nunique().sort_values(ascending=True))
visaogeral(df,'Visão Geral do dataSet')
Visão Geral do dataSet: Qtd Observações: 119390 Qtd Atributos: 32 Atributos: ['hotel' 'is_canceled' 'lead_time' 'arrival_date_year' 'arrival_date_month' 'arrival_date_week_number' 'arrival_date_day_of_month' 'stays_in_weekend_nights' 'stays_in_week_nights' 'adults' 'children' 'babies' 'meal' 'country' 'market_segment' 'distribution_channel' 'is_repeated_guest' 'previous_cancellations' 'previous_bookings_not_canceled' 'reserved_room_type' 'assigned_room_type' 'booking_changes' 'deposit_type' 'agent' 'company' 'days_in_waiting_list' 'customer_type' 'adr' 'required_car_parking_spaces' 'total_of_special_requests' 'reservation_status' 'reservation_status_date'] Qtd Valores missing: 129425 Valores Unicos: hotel 2 is_canceled 2 is_repeated_guest 2 arrival_date_year 3 deposit_type 3 reservation_status 3 customer_type 4 required_car_parking_spaces 5 meal 5 babies 5 distribution_channel 5 children 5 total_of_special_requests 6 market_segment 8 reserved_room_type 10 arrival_date_month 12 assigned_room_type 12 adults 14 previous_cancellations 15 stays_in_weekend_nights 17 booking_changes 21 arrival_date_day_of_month 31 stays_in_week_nights 35 arrival_date_week_number 53 previous_bookings_not_canceled 73 days_in_waiting_list 128 country 177 agent 333 company 352 lead_time 479 reservation_status_date 926 adr 8879 dtype: int64
O conjunto de dados possui aproximadamente cerca de 120 mil observações e 32 atributos, podemos observar a presença de valores ausentes (missing). Os atributos incluem característica e informações sobre a reserva, os hóspedes, a estadia escolhida e valores pagos nas estadias. Abaixo, destacamos os atributos categóricos e quantitativos:
Atributos categóricos: a princípio as features elencadas abaixo podem representar categorias:
hotel,is_canceled,is_repeated_guest,deposit_type,reservation_status,customer_type,required_car_parking_spaces,meal,distribution_channel,total_of_special_requests,market_segment,reserved_room_type,assigned_room_type,country.
Atributos quantitativos: as demais, a priori demonstra ser features numéricas e contínuas, sendo que após o processo de exploração dos dados (EDA) seja interessante realizar algumas trasformaões em categórias nelas.
Nosso objetivo é realmente entender se esses formatos representam a informação que a feature precisa passar!
Essa combinação de atributos categóricos e quantitativos é fundamental para capturar os diferentes fatores que influenciam a probabilidade de cancelamento dos hóspedes.
df.info()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 119390 entries, 0 to 119389 Data columns (total 32 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 hotel 119390 non-null object 1 is_canceled 119390 non-null int64 2 lead_time 119390 non-null int64 3 arrival_date_year 119390 non-null int64 4 arrival_date_month 119390 non-null object 5 arrival_date_week_number 119390 non-null int64 6 arrival_date_day_of_month 119390 non-null int64 7 stays_in_weekend_nights 119390 non-null int64 8 stays_in_week_nights 119390 non-null int64 9 adults 119390 non-null int64 10 children 119386 non-null float64 11 babies 119390 non-null int64 12 meal 119390 non-null object 13 country 118902 non-null object 14 market_segment 119390 non-null object 15 distribution_channel 119390 non-null object 16 is_repeated_guest 119390 non-null int64 17 previous_cancellations 119390 non-null int64 18 previous_bookings_not_canceled 119390 non-null int64 19 reserved_room_type 119390 non-null object 20 assigned_room_type 119390 non-null object 21 booking_changes 119390 non-null int64 22 deposit_type 119390 non-null object 23 agent 103050 non-null float64 24 company 6797 non-null float64 25 days_in_waiting_list 119390 non-null int64 26 customer_type 119390 non-null object 27 adr 119390 non-null float64 28 required_car_parking_spaces 119390 non-null int64 29 total_of_special_requests 119390 non-null int64 30 reservation_status 119390 non-null object 31 reservation_status_date 119390 non-null object dtypes: float64(4), int64(16), object(12) memory usage: 29.1+ MB
A análise exploratória realizada proporcionou uma compreensão inicial sobre a estrutura e qualidade do conjunto de dados. O dataset contém aproximadamente 120 mil registros de reservas em hotéis, detalhando informações sobre os clientes e se as reservas foram canceladas ou não.
A inspeção das variáveis revelou a presença de atributos categóricos, exigindo a aplicação de técnicas de codificação de variáveis, como One-Hot Encoding, para possibilitar sua utilização em modelos de Machine Learning baseados em algoritmos que requerem representações numéricas.
Outro ponto relevante é a identificação de valores ausentes em algumas variáveis, o que demanda um tratamento adequado. Além disso, durante o processo de engenharia de atributos (feature engineering) e transformação de variáveis, podem surgir valores nulos em decorrência de operações como discretização, normalização ou derivação de novas variáveis. Assim, será essencial monitorar, imputar e tratar esses valores para garantir a qualidade e integridade dos dados, assegurando que o modelo preditivo alcance um desempenho confiável.
TRADE-OFFs da Ciência de Dados e o Dilema Viés-Variância
No desenvolvimento de modelos de Machine Learning, é fundamental equilibrar quantidade de dados, dimensionalidade e tempo de treinamento, garantindo um modelo eficiente e generalizável. Esse processo envolve decisões estratégicas para evitar problemas como underfitting e overfitting, além de considerar o dilema viés-variância.
Tamanho do Conjunto de Dados
Se o número de observações no conjunto de dados for muito grande, o treinamento pode ser computacionalmente custoso, exigindo técnicas como amostragem representativa, processamento distribuído (ex.: Spark, Dask) ou redução de dimensionalidade (ex.: PCA). Por outro lado, um número muito pequeno de registros pode levar ao underfitting, onde o modelo não consegue capturar padrões relevantes e apresenta baixa capacidade preditiva.
Dimensionalidade e Seleção de Features
Muitas colunas/atributos podem gerar problemas de alta dimensionalidade, dificultando a modelagem e aumentando o risco de overfitting, quando o modelo se ajusta excessivamente aos dados de treino e perde capacidade de generalização. Para mitigar esse problema, técnicas como seleção de variáveis (ex.: feature selection) e redução de dimensionalidade (ex.: PCA, Autoencoders) são recomendadas.
O Dilema Viés-Variância
O dilema viés-variância descreve o equilíbrio entre simplicidade e complexidade do modelo:
- Viés alto (underfitting): O modelo é muito simples e não captura os padrões dos dados adequadamente, erra muito ou tudo.
- Variância alta (overfitting): O modelo é muito complexo e se ajusta excessivamente aos dados de treino, resultando em baixa generalização.
A solução ideal envolve encontrar um meio-termo, onde o modelo aprende bem os padrões dos dados sem se tornar excessivamente específico.
Divisão de Dados e Validação
A prática recomendada é dividir o conjunto de dados em:
- Treino (70%): Para ensinar o modelo.
- Teste (30%): Para avaliar a performance em dados novos.
- Validação cruzada: Para evitar dependência de uma única divisão dos dados e garantir que o modelo tenha boa generalização.
E então?
Não existe uma solução única para esses desafios, e cada problema exige um equilíbrio entre quantidade de dados, dimensionalidade, tempo de treinamento e capacidade de generalização. Estratégias como seleção de variáveis, validação cruzada e ajuste de hiperparâmetros são essenciais para otimizar o modelo e garantir desempenho robusto e confiável.
Observando uma amostra inicial e uma amostra final do dataSet.
df.head()
| hotel | is_canceled | lead_time | arrival_date_year | arrival_date_month | arrival_date_week_number | arrival_date_day_of_month | stays_in_weekend_nights | stays_in_week_nights | adults | ... | deposit_type | agent | company | days_in_waiting_list | customer_type | adr | required_car_parking_spaces | total_of_special_requests | reservation_status | reservation_status_date | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | Resort Hotel | 0 | 342 | 2015 | July | 27 | 1 | 0 | 0 | 2 | ... | No Deposit | NaN | NaN | 0 | Transient | 0.0 | 0 | 0 | Check-Out | 2015-07-01 |
| 1 | Resort Hotel | 0 | 737 | 2015 | July | 27 | 1 | 0 | 0 | 2 | ... | No Deposit | NaN | NaN | 0 | Transient | 0.0 | 0 | 0 | Check-Out | 2015-07-01 |
| 2 | Resort Hotel | 0 | 7 | 2015 | July | 27 | 1 | 0 | 1 | 1 | ... | No Deposit | NaN | NaN | 0 | Transient | 75.0 | 0 | 0 | Check-Out | 2015-07-02 |
| 3 | Resort Hotel | 0 | 13 | 2015 | July | 27 | 1 | 0 | 1 | 1 | ... | No Deposit | 304.0 | NaN | 0 | Transient | 75.0 | 0 | 0 | Check-Out | 2015-07-02 |
| 4 | Resort Hotel | 0 | 14 | 2015 | July | 27 | 1 | 0 | 2 | 2 | ... | No Deposit | 240.0 | NaN | 0 | Transient | 98.0 | 0 | 1 | Check-Out | 2015-07-03 |
5 rows × 32 columns
df.tail()
| hotel | is_canceled | lead_time | arrival_date_year | arrival_date_month | arrival_date_week_number | arrival_date_day_of_month | stays_in_weekend_nights | stays_in_week_nights | adults | ... | deposit_type | agent | company | days_in_waiting_list | customer_type | adr | required_car_parking_spaces | total_of_special_requests | reservation_status | reservation_status_date | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 119385 | City Hotel | 0 | 23 | 2017 | August | 35 | 30 | 2 | 5 | 2 | ... | No Deposit | 394.0 | NaN | 0 | Transient | 96.14 | 0 | 0 | Check-Out | 2017-09-06 |
| 119386 | City Hotel | 0 | 102 | 2017 | August | 35 | 31 | 2 | 5 | 3 | ... | No Deposit | 9.0 | NaN | 0 | Transient | 225.43 | 0 | 2 | Check-Out | 2017-09-07 |
| 119387 | City Hotel | 0 | 34 | 2017 | August | 35 | 31 | 2 | 5 | 2 | ... | No Deposit | 9.0 | NaN | 0 | Transient | 157.71 | 0 | 4 | Check-Out | 2017-09-07 |
| 119388 | City Hotel | 0 | 109 | 2017 | August | 35 | 31 | 2 | 5 | 2 | ... | No Deposit | 89.0 | NaN | 0 | Transient | 104.40 | 0 | 0 | Check-Out | 2017-09-07 |
| 119389 | City Hotel | 0 | 205 | 2017 | August | 35 | 29 | 2 | 7 | 2 | ... | No Deposit | 9.0 | NaN | 0 | Transient | 151.20 | 0 | 2 | Check-Out | 2017-09-07 |
5 rows × 32 columns
Relembrando o shape dos dados:
print('Os dados possuem {:.2f} linhas e {} colunas'.format(df.shape[0], df.shape[1]))
Os dados possuem 119390.00 linhas e 32 colunas
# checando índice (endereço de cada obs) e sua distribuição
df.index
RangeIndex(start=0, stop=119390, step=1)
# checando os nomes das colunas
df.columns
Index(['hotel', 'is_canceled', 'lead_time', 'arrival_date_year',
'arrival_date_month', 'arrival_date_week_number',
'arrival_date_day_of_month', 'stays_in_weekend_nights',
'stays_in_week_nights', 'adults', 'children', 'babies', 'meal',
'country', 'market_segment', 'distribution_channel',
'is_repeated_guest', 'previous_cancellations',
'previous_bookings_not_canceled', 'reserved_room_type',
'assigned_room_type', 'booking_changes', 'deposit_type', 'agent',
'company', 'days_in_waiting_list', 'customer_type', 'adr',
'required_car_parking_spaces', 'total_of_special_requests',
'reservation_status', 'reservation_status_date'],
dtype='object')
3.1.1 Variáveis Explicatórias e Variável Alvo¶
Para a construção do modelo preditivo, é fundamental separar as variáveis do conjunto de dados em dois grupos distintos:
Variável Alvo (
y): Representa o resultado que desejamos prever. No contexto deste estudo, a variável alvo é ais_canceled, que indica se a reserva foi cancelada (1) ou não (0). Essa variável será utilizada para treinar o modelo e avaliar sua capacidade de prever o cancelamento de uma estadia.Variáveis Explicatórias (
X): São as demais variáveis que contêm informações relevantes sobre o comportamento e perfil dos clientes. Elas serão utilizadas como fatores preditivos para estimar a probabilidade do cancelamento. Antes do treinamento podemos excluir variáveis que não apresentarem valor preditivo.
A separação correta entre X e y é essencial para garantir que o modelo aprenda a partir de variáveis relevantes e seja capaz de realizar previsões precisas sobre a saída ou permanência dos clientes.
df.head()
| hotel | is_canceled | lead_time | arrival_date_year | arrival_date_month | arrival_date_week_number | arrival_date_day_of_month | stays_in_weekend_nights | stays_in_week_nights | adults | ... | deposit_type | agent | company | days_in_waiting_list | customer_type | adr | required_car_parking_spaces | total_of_special_requests | reservation_status | reservation_status_date | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | Resort Hotel | 0 | 342 | 2015 | July | 27 | 1 | 0 | 0 | 2 | ... | No Deposit | NaN | NaN | 0 | Transient | 0.0 | 0 | 0 | Check-Out | 2015-07-01 |
| 1 | Resort Hotel | 0 | 737 | 2015 | July | 27 | 1 | 0 | 0 | 2 | ... | No Deposit | NaN | NaN | 0 | Transient | 0.0 | 0 | 0 | Check-Out | 2015-07-01 |
| 2 | Resort Hotel | 0 | 7 | 2015 | July | 27 | 1 | 0 | 1 | 1 | ... | No Deposit | NaN | NaN | 0 | Transient | 75.0 | 0 | 0 | Check-Out | 2015-07-02 |
| 3 | Resort Hotel | 0 | 13 | 2015 | July | 27 | 1 | 0 | 1 | 1 | ... | No Deposit | 304.0 | NaN | 0 | Transient | 75.0 | 0 | 0 | Check-Out | 2015-07-02 |
| 4 | Resort Hotel | 0 | 14 | 2015 | July | 27 | 1 | 0 | 2 | 2 | ... | No Deposit | 240.0 | NaN | 0 | Transient | 98.0 | 0 | 1 | Check-Out | 2015-07-03 |
5 rows × 32 columns
X = df.drop(columns= 'is_canceled', axis= 1)
y = df['is_canceled']
y.shape
(119390,)
print('DataSet original com {} atributos e {} observações'.format(df.shape[1], df.shape[0]))
print('As Variáveis Explicatórias possuem {} atributos e {} observações'.format(X.shape[1], X.shape[0]))
print('A Variável Alvo possuem {} observações'.format(y.shape))
DataSet original com 32 atributos e 119390 observações As Variáveis Explicatórias possuem 31 atributos e 119390 observações A Variável Alvo possuem (119390,) observações
3.1.2 Variável Alvo¶
A variável alvo deste estudo é o atributo is_canceled, que indica se o cliente cancelou ou não sua reserva no hotel. Essa variável é categórica binária e representa o resultado que o modelo de machine learning deve prever. Seus valores são definidos da seguinte forma:
- 0 → O cliente não cancelou, ou seja, compareceu para se hospedar.
- 1 → O cliente cancelou a reserva.
3.1.2.1 Amplitude¶
y.describe()
count 119390.000000 mean 0.370416 std 0.482918 min 0.000000 25% 0.000000 50% 0.000000 75% 1.000000 max 1.000000 Name: is_canceled, dtype: float64
y.unique()
array([0, 1])
Observando os valores únicos da variável is_canceled, confirmamos que se trata de uma variável categórica binária, com duas categorias distintas:
- 0 → Não houve cancelamento, o cliente compareceu para se hospedar.
- 1 → Houve cancelamento, o cliente cancelou e não compareceu para se hospedar.
Dessa forma, estamos diante de um problema de aprendizado supervisionado do tipo classificação, no qual o objetivo é prever a probabilidade de um cliente deixar ou não a instituição financeira.
Para essa tarefa, utilizaremos um modelo classificador, que será treinado para identificar padrões de comportamento associados nosso problema.
3.1.2.2 Balanceamento dos Dados¶
Em problemas de classificação, é frequente encontrar conjuntos de dados com classes desbalanceadas, onde uma das classes apresenta um número de registros significativamente maior do que a outra. Esse desequilíbrio pode comprometer o desempenho do modelo, tornando-o tendencioso para a classe majoritária e dificultando a correta identificação da classe minoritária.
O balanceamento das classes é um dos pressupostos fundamentais a serem considerados na construção de modelos de machine learning. Caso não seja tratado adequadamente, o modelo pode apresentar baixa capacidade de prever eventos menos frequentes. Por isso, é essencial adotar estratégias para equilibrar as classes durante o pré-processamento, garantindo previsões mais precisas e confiáveis.
print(y.value_counts())
print("\nObserva-se que {:.4f}% do dataSet cancelaram a reserva.".format((df.groupby('is_canceled').size()[1] / df.shape[0])*100))
print("Enquanto que {:.4f}% dos dados não cancelaram suas reservas.".format((df.groupby('is_canceled').size()[0] / df.shape[0])*100))
is_canceled 0 75166 1 44224 Name: count, dtype: int64 Observa-se que 37.0416% do dataSet cancelaram a reserva. Enquanto que 62.9584% dos dados não cancelaram suas reservas.
No contexto deste estudo, a variável is_canceled apresenta um leve desbalanceamento, mas não em um nível que exija, de imediato, a aplicação de técnicas de balanceamento. O impacto desse desbalanceamento na performance do modelo será avaliado antes de qualquer intervenção.
As principais técnicas de balanceamento de classes incluem:
Oversampling (Superamostragem): Aumenta a quantidade de registros da classe minoritária, seja por duplicação de dados existentes ou pela geração de novas amostras sintéticas. Um exemplo comum é o SMOTE (Synthetic Minority Over-sampling Technique), que cria novas instâncias baseadas na interpolação dos dados minoritários.
Undersampling (Subamostragem): Reduz a quantidade de registros da classe majoritária, eliminando amostras redundantes para equilibrar a distribuição entre as classes. Essa técnica pode ser útil para acelerar o treinamento, mas pode levar à perda de informações relevantes.
A decisão sobre a aplicação dessas técnicas deve ser baseada em evidências experimentais. Se o modelo demonstrar um viés excessivo para a classe majoritária, prejudicando a capacidade de prever corretamente os cancelamentos, estratégias de balanceamento serão consideradas para mitigar esse efeito.
3.1.2.3 Plots Variável Alvo¶
# seto algumas caracteristicas para os plots. Padornizar Plots
sns.set_theme(style='darkgrid')
sns.set_palette("hls", 3)
balData = pd.DataFrame(df['is_canceled'].value_counts())
balData['% total'] = round(100*balData['count']/df.shape[0], 2)
print(balData)
churn_plot = sns.countplot(data=df, x='is_canceled', order=df.is_canceled.value_counts().index, hue= 'is_canceled', palette='coolwarm')
plt.title('Distribuição das classes')
plt.ylabel('Quantidade')
plt.tight_layout()
plt.show()
count % total is_canceled 0 75166 62.96 1 44224 37.04
3.1.3 Preditores Categóricos¶
Durante a análise exploratória, identificamos que o conjunto de dados contém variáveis categóricas que são aquelas que representam categorias seja elas nominais ou ordinais. Elas auxiliam para mapear o entender o evento que estamos analisando.
x_categoricos = df.select_dtypes(include=['object'])
x_categoricos['is_canceled'] = df['is_canceled']
x_categoricos.head()
| hotel | arrival_date_month | meal | country | market_segment | distribution_channel | reserved_room_type | assigned_room_type | deposit_type | customer_type | reservation_status | reservation_status_date | is_canceled | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | Resort Hotel | July | BB | PRT | Direct | Direct | C | C | No Deposit | Transient | Check-Out | 2015-07-01 | 0 |
| 1 | Resort Hotel | July | BB | PRT | Direct | Direct | C | C | No Deposit | Transient | Check-Out | 2015-07-01 | 0 |
| 2 | Resort Hotel | July | BB | GBR | Direct | Direct | A | C | No Deposit | Transient | Check-Out | 2015-07-02 | 0 |
| 3 | Resort Hotel | July | BB | GBR | Corporate | Corporate | A | A | No Deposit | Transient | Check-Out | 2015-07-02 | 0 |
| 4 | Resort Hotel | July | BB | GBR | Online TA | TA/TO | A | A | No Deposit | Transient | Check-Out | 2015-07-03 | 0 |
x_categoricos.columns
Index(['hotel', 'arrival_date_month', 'meal', 'country', 'market_segment',
'distribution_channel', 'reserved_room_type', 'assigned_room_type',
'deposit_type', 'customer_type', 'reservation_status',
'reservation_status_date', 'is_canceled'],
dtype='object')
plt.figure(figsize=(8,5))
sns.countplot(data=df, x='hotel', hue='is_canceled', palette= 'coolwarm')
plt.title("Cancelamentos por Tipo de Hotel")
plt.xlabel("Tipo de Hotel")
plt.ylabel("Quantidade de Reservas")
plt.legend(title="Cancelado", labels=["Não", "Sim"])
plt.tight_layout()
plt.show()
O número absoluto de cancelamentos no City Hotel é muito maior do que no Resort Hotel.
No City Hotel, a proporção entre reservas canceladas e concluídas está mais equilibrada, sugerindo que os hóspedes desse tipo de hotel têm maior propensão ao cancelamento.
O Resort Hotel apresenta uma proporção significativamente menor de cancelamentos em relação ao total de reservas. Isso pode indicar diferenças no perfil dos clientes ou no tipo de estadia esperada para cada hotel.
No City Hotel, por exemplo, podem ocorrer mais cancelamentos devido a mudanças em viagens a trabalho ou estadias curtas, enquanto no Resort Hotel, as reservas são mais planejadas e menos propensas a cancelamento.
top_countries = df['country'].value_counts().nlargest(10).index
df_country = df[df['country'].isin(top_countries)]
plt.figure(figsize=(12,6))
sns.countplot(data=df_country, y='country', hue='is_canceled', palette='coolwarm', order=top_countries)
plt.title("Top 10 Países com Mais Reservas e Cancelamentos")
plt.xlabel("Quantidade de Reservas")
plt.ylabel("País")
plt.legend(title="Cancelado", labels=["Não", "Sim"])
plt.tight_layout()
plt.show()
O maior volume de reservas vem de Portugal, e o número de cancelamentos também é expressivo, indicando que turistas portugueses fazem muitas reservas, mas também cancelam em uma proporção elevada. Reino Unido (GBR) e França (FRA) seguem em volume de reservas, ambos os países têm um número significativo de reservas, mas a taxa de cancelamento parece menor em comparação com Portugal.
Em países como Alemanha (DEU), Espanha (ESP) e Itália (ITA), a proporção de cancelamentos em relação ao total de reservas parece ser mais baixa. Brasil (BRA) aparece entre os 10 principais, não é tão alto quanto os países europeus, mas ainda assim figura entre os mais relevantes.
plt.figure(figsize=(8,5))
sns.countplot(data=df, x='deposit_type', hue='is_canceled', palette='coolwarm')
plt.title("Distribuição de Cancelamento por Tipo de Depósito")
plt.xlabel("Tipo de Depósito")
plt.ylabel("Quantidade de Reservas")
plt.legend(title="Cancelado", labels=["Não", "Sim"])
plt.tight_layout()
plt.show()
A maior parte das reservas é feita sem depósito antecipado, o que pode indicar uma maior flexibilidade para os clientes. No entanto, também é nesse grupo que ocorre o maior número absoluto de cancelamentos. As reservas que exigem pagamento antecipado e não são reembolsáveis ("Non Refund") apresentam uma menor quantidade total, mas um índice relativamente menor de cancelamentos, comparado com as reservas que não exige depósito.
plt.figure(figsize=(12,6))
sns.histplot(data=df, x='arrival_date_month', hue='is_canceled', multiple='stack', palette='coolwarm', shrink=0.8)
plt.title("Reservas e Cancelamentos por Mês")
plt.xlabel("Mês de Chegada")
plt.ylabel("Quantidade de Reservas")
plt.xticks(rotation=45)
plt.legend(title="Cancelado", labels=["Não", "Sim"])
plt.tight_layout()
plt.show()
- Alta sazonalidade nas reservas:
- Os meses com maior volume de reservas são Julho e Agosto, o que indica um pico no período de verão no Hemisfério Norte.
- Já os meses com menor volume de reservas são Janeiro e Dezembro, sugerindo uma queda na demanda durante o inverno, possivelmente por ser um período pós-feriado onde menos pessoas viajam.
- Cancelamentos acompanham o volume de reservas:
- Observa-se que meses com mais reservas também possuem maior número absoluto de cancelamentos.
- Isso pode indicar que as condições sazonais impactam não apenas a demanda por reservas, mas também a probabilidade de cancelamento, podendo estar relacionado a eventos, clima ou feriados.
- Taxa de cancelamento parece ser maior em alguns meses:
- Agosto, Julho e Outubro apresentam altas taxas de cancelamento, o que pode indicar que muitas dessas reservas são feitas antecipadamente e depois canceladas.
- Meses como Janeiro, Fevereiro e Novembro apresentam menos cancelamentos, o que pode sugerir que os clientes que viajam nessas épocas possuem maior intenção de manter suas reservas.
plt.figure(figsize=(10,5))
sns.countplot(data=df, x='distribution_channel', hue='is_canceled', palette='coolwarm')
plt.title("Taxa de Cancelamento por Canal de Distribuição")
plt.xlabel("Canal de Distribuição")
plt.ylabel("Quantidade de Reservas")
plt.legend(title="Cancelado", labels=["Não", "Sim"])
plt.tight_layout()
plt.show()
O canal "TA/TO" (Agências de Viagem e Operadoras de Turismo) tem a maior quantidade de reservas e cancelamentos, esse canal representa a maioria das reservas, mas também exibe um alto volume de cancelamentos. Pode indicar que reservas feitas por agências e operadoras são mais suscetíveis a alterações, cancelamentos ou mudanças por parte dos clientes.
Reservas feitas diretamente ("Direct") possuem um volume considerável de reservas com menor taxa de cancelamento, o número de cancelamentos é relativamente baixo, sugerindo que clientes que fazem reservas diretamente no hotel tendem a ser mais comprometidos.
3.1.4 Preditores Numéricos¶
As variáveis numéricas desempenham um papel crucial na construção de modelos preditivos, pois capturam informações quantitativas que podem influenciar diretamente a probabilidade de cancelamento.
x_numericos = df.select_dtypes(include=['int64', 'float64'])
plt.figure(figsize=(10, 5))
sns.kdeplot(data=df, x="lead_time", hue="is_canceled", common_norm=False, fill=True, alpha=0.3, palette='coolwarm')
plt.xlabel("Lead Time (dias)")
plt.ylabel("Densidade")
plt.title("Curva de Densidade do Lead Time por Cancelamento")
plt.legend(title="Cancelado", labels=["Não", "Sim"])
plt.tight_layout()
plt.show()
Clientes que fazem reservas com muita antecedência têm uma maior probabilidade de cancelar.
plt.figure(figsize=(10, 5))
sns.histplot(df[df["is_canceled"] == 0]["adr"], bins=50, kde=True, palette='coolwarm')
plt.xlabel("Preço diária (ADR)")
plt.ylabel("Frequência")
plt.title("Distribuição do preço da diária (ADR) para Reservas Confirmadas")
plt.tight_layout()
plt.show()
O preço da diária segue uma distribuição assimétrica, onde a maioria das reservas confirmadas se concentra em valores intermediários. A maioria das diárias está concentrada entre 50 e 150 unidades monetárias, com um pico por volta de 100.
Existem algumas diárias acima de 200, mas com menor frequência. Isso sugere que preços muito altos são raros e representam hotéis premium ou pacotes especiais.
Há uma pequena concentração de reservas com valores muito baixos (~0 a 20). Isso pode indicar erros na base de dados, reservas promocionais ou registros que precisam ser melhor investigados.
plt.figure(figsize=(12, 6))
sns.boxplot(data=df[df["is_canceled"] == 0], x="arrival_date_month", y="adr", hue="hotel", palette='coolwarm')
plt.xlabel("Mês de Chegada")
plt.ylabel("Preço diária (ADR)")
plt.title("Variação do preço da diária ao longo do ano")
plt.legend(title="Tipo de Hotel")
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()
1️⃣ Sazonalidade Clara: Os meses de junho, julho e agosto apresentam os maiores preços médios, indicando alta temporada. Já os meses de janeiro a março possuem preços mais baixos, sugerindo baixa demanda.
2️⃣ Diferença entre os Tipos de Hotéis: City Hotel possuem preços mais altos em geral, além de apresentar menor variação ao longo do ano.
3️⃣ Distribuição e Outliers: Há outliers frequentes, principalmente nos meses de alta temporada, indicando algumas diárias muito acima da média. A dispersão de preços é maior nos Resort Hotels, sugerindo uma gama de preços mais ampla, possivelmente devido a diferentes categorias de quartos e pacotes.
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
sns.boxplot(data=df, x="hotel", y="stays_in_weekend_nights", ax=axes[0], palette='coolwarm')
axes[0].set_title("Diárias fim de semana por tipo de hotel")
axes[0].set_xlabel("Tipo de Hotel")
axes[0].set_ylabel("Diárias - Fim de semana")
sns.boxplot(data=df, x="hotel", y="stays_in_week_nights", ax=axes[1], palette='coolwarm')
axes[1].set_title("Diárias durante a Semana por tipo de hotel")
axes[1].set_xlabel("Tipo de Hotel")
axes[1].set_ylabel("Diárias - Durante a semana")
plt.tight_layout()
plt.show()
O número de diárias durante a semana é maior do que nos fins de semana, indicando que muitas reservas são para estadias prolongadas. Isso sugere que os hóspedes, especialmente em hotéis Resort, ficam mais tempo durante a semana.
Ambos os tipos de hotéis apresentam um comportamento similar, a maioria das reservas tem poucas diárias (1 a 3 noites). No entanto, há outliers significativos em ambos os gráficos, mostrando que alguns hóspedes ficam por períodos muito longos, principalmente em Resort Hotels.
No gráfico de dias da semana, há mais valores extremos, com estadias chegando a 50 noites, o que pode indicar clientes corporativos ou contratos de longo prazo. No gráfico de fins de semana, os outliers são menos frequentes, e a maior parte das estadias fica em torno de 1 a 2 noites.
3.1.4.1 Distribuição dos Dados – Assimetria (Skewness) e Curtose (Kurtosis)¶
Nesta etapa, será analisada a distribuição dos dados de cada variável, com foco em duas medidas estatísticas importantes: assimetria (skewness) e curtose (kurtosis).
Assimetria (Skewness): Indica o grau de simetria da distribuição dos dados em relação à média.
- Uma distribuição simétrica tem skew próximo de 0.
- Assimetria positiva (skew > 0) indica concentração de valores à esquerda, com cauda alongada à direita.
- Assimetria negativa (skew < 0) indica concentração à direita, com cauda alongada à esquerda.
Curtose (Kurtosis): Mede o grau de achatamento ou picos da distribuição em comparação com a distribuição normal.
- Curtose próxima de 0 indica uma distribuição semelhante à normal.
- Curtose positiva indica uma distribuição com picos acentuados e caudas mais longas (leptocúrtica).
- Curtose negativa indica uma distribuição mais achatada (platicúrtica).
Muitos algoritmos de Machine Learning pressupõem que as variáveis possuem uma distribuição normal (gaussiana). Avaliar a assimetria e a curtose permite aplicar técnicas de pré-processamento adequadas (como transformação logarítmica, normalização ou padronização) para ajustar a distribuição dos dados. Esse ajuste pode melhorar significativamente a performance e a acurácia do modelo preditivo.
x_numericos.skew()
is_canceled 0.536678 lead_time 1.346550 arrival_date_year -0.232583 arrival_date_week_number -0.010014 arrival_date_day_of_month -0.002000 stays_in_weekend_nights 1.380046 stays_in_week_nights 2.862249 adults 18.317805 children 4.112590 babies 24.646545 is_repeated_guest 5.326315 previous_cancellations 24.458049 previous_bookings_not_canceled 23.539800 booking_changes 6.000270 agent 1.089386 company 0.601600 days_in_waiting_list 11.944353 adr 10.530214 required_car_parking_spaces 4.163233 total_of_special_requests 1.349189 dtype: float64
fig, ((ax1, ax2, ax3, ax4, ax5),
(ax6, ax7, ax8, ax9, ax10),
(ax11, ax12, ax13, ax14, ax15),
(ax16, ax17, ax18, ax19, ax20)) = plt.subplots(4, 5, figsize=(20, 20))
ax = [ax1, ax2, ax3, ax4, ax5, ax6, ax7, ax8, ax9, ax10, ax11, ax12, ax13, ax14, ax15, ax16, ax17, ax18, ax19, ax20]
for coluna in range(len(x_numericos.columns)):
sns.distplot(x_numericos.iloc[:, coluna], bins=20, hist=True, ax=ax[coluna], fit=stats.norm)
ax[coluna].set_title(x_numericos.columns[coluna])
plt.tight_layout()
plt.show()
Buscaremos agora compreender a força e direção de uma relação linear entre a variável independente e dependente.
sns.heatmap(x_numericos.corr(),annot = False, cmap="coolwarm")
plt.title("Matriz de correlação dos dados",fontsize= 15)
plt.show()
Distribuições Extremamente Assimétricas (Alta Skewness):
lead_time: Enviesado à direita, com muitas reservas de curto prazo e algumas poucas de longuíssimo prazo.
adr: Alto pico em valores baixos e uma longa cauda à direita, indicando a presença de preços de diária muito elevados.
previous_cancellations e previous_bookings_not_canceled: Muitos valores próximos de zero e uma longa cauda, indicando que a maioria das reservas não teve cancelamentos prévios, mas algumas poucas tiveram um histórico extenso.
days_in_waiting_list: A maioria das reservas não espera, mas algumas poucas esperam muito tempo.
Distribuições Moderadamente Assimétricas:
stays_in_weekend_nights e stays_in_week_nights: Maior concentração de valores baixos, com poucas reservas de longuíssima duração.
total_of_special_requests: A maioria das reservas faz poucos pedidos especiais, mas há alguns casos com muitos pedidos.
agent: Alguns agentes dominam a distribuição das reservas, criando um viés.
Distribuições Quase Normais (Sem Necessidade de Transformação):
arrival_date_week_number e arrival_date_day_of_month: Distribuições aproximadamente simétricas.
arrival_date_year: Apresenta apenas algumas variações discretas ao longo do tempo.
3.1.4.2 Identificação de Outliers¶
A análise de outliers é uma etapa essencial no processo de pré-processamento de dados, pois valores extremos podem distorcer estatísticas descritivas e impactar negativamente o desempenho dos modelos de Machine Learning.
Para identificar esses pontos fora do padrão, utilizaremos boxplots, que oferecem uma representação visual da distribuição de cada variável. A interpretação do boxplot inclui:
- A linha central da caixa representa a mediana (50º percentil).
- A borda inferior da caixa indica o primeiro quartil (Q1 - 25%).
- A borda superior da caixa indica o terceiro quartil (Q3 - 75%).
- As extensões se estendem até o menor e maior valor dentro de 1,5 vezes o intervalo interquartílico (IQR).
- Pontos fora do boxplot são considerados outliers.
Identificar outliers permite compreender melhor a dispersão dos dados e avaliar a necessidade de tratamento adequado, como remoção ou transformação desses valores. Essa análise contribui para melhorar a qualidade dos dados e aumentar a robustez do modelo preditivo.
# Configurando o tamanho da figura e o layout dos subplots
num_cols = 5 # Número de colunas de subplots
num_rows = len(x_numericos.columns) // num_cols + (len(x_numericos.columns) % num_cols > 0)
fig, axes = plt.subplots(num_rows, num_cols, figsize=(20, 4 * num_rows))
# Flatten the axes array for easy iteration
axes = axes.flatten()
# Loop para criar um boxplot para cada coluna numérica
for i, col in enumerate(x_numericos.columns):
sns.boxplot(y=df[col], ax=axes[i], palette='coolwarm')
axes[i].set_title(col)
# Remover subplots vazios
for j in range(i + 1, len(axes)):
fig.delaxes(axes[j])
Considerando a definição clássica de outliers — valores que excedem 1,5 vezes o intervalo interquartílico (IQR) —, identificamos que algumas variáveis apresentam valores extremos. No entanto, antes de decidir por sua remoção ou transformação, é fundamental analisar o contexto dos dados para garantir que essas observações não representem simplesmente características naturais do fenômeno estudado.
Entre as variáveis com maior quantidade de outliers visíveis estão:
lead_time: Algumas reservas possuem um tempo de antecedência muito elevado (acima de 600 dias). Embora seja um valor extremo, ele pode ser legítimo em casos de reservas feitas com muita antecedência, especialmente para eventos ou períodos de alta demanda.
adr (Preço da Diária): Apresenta uma distribuição altamente assimétrica, com valores extremamente altos. Esses valores podem indicar suítes de luxo ou períodos de preços elevados, como feriados e alta temporada. Excluir esses dados poderia remover informações importantes sobre a precificação dinâmica dos hotéis.
stays_in_weekend_nights e stays_in_week_nights: Algumas reservas apresentam um número muito alto de noites, o que pode indicar hospedagens atípicas ou contratos de longa duração. Dependendo do objetivo do modelo, pode ser interessante categorizá-los em diferentes faixas de duração.
previous_cancellations e previous_bookings_not_canceled: Alguns hóspedes possuem um número elevado de reservas anteriores, o que pode representar clientes corporativos ou usuários com comportamento recorrente de reserva e cancelamento. Esses valores são relevantes e devem ser mantidos para análise de padrões de cancelamento.
days_in_waiting_list: A maioria das reservas não passa tempo na lista de espera, mas alguns registros apresentam valores altos. Esse dado pode ser importante para entender a demanda por determinados períodos.
Dado que os valores extremos observados não são necessariamente erros e refletem comportamentos reais do mercado hoteleiro, optamos por não remover diretamente esses dados.
Nesta fase de exploração dos dados vemos que:
- Há dados faltantes.
- Existe uma predominância nos dados de feature categórica, que enseja no uso de técnicas de dummie's.
- Algumas features não tem poder preditivo.
- Ficou claro que um dos pressupostos da classificação foi atendido parcealmente, há um leve desbalaceamento de classes das variável a ser predita, que iremos usar no modelo e ver o comportamento.
- Poderíamos ter feito (a) correlações, (b) re-shape dos dados, (c) alterado variáveis categóricas para apenas binária... enfim, é muito vasto a fase de exploração. Para nosso propósito acredito já ser suficiente.
Iniciaremos a fase de preparar os dados para apresentá-los ao modelo preditivo.
4. Pré-Processamento dos Dados¶
Preparando os Dados para Machine Learning¶
O pré-processamento dos dados é uma etapa fundamental no desenvolvimento de modelos de Machine Learning, pois muitos algoritmos exigem que os dados estejam em formatos específicos e adequadamente preparados para garantir um desempenho eficiente. Essa preparação inclui desde o tratamento de dados ausentes, normalização e padronização até a codificação de variáveis categóricas e remoção de outliers.
Importância do Pré-Processamento:
- Melhora a performance e a eficiência dos algoritmos.
- Garante que os dados estejam em um formato compatível com o modelo.
- Reduz ruídos e viéses, aumentando a precisão das previsões.
Explorar diferentes técnicas de preparação de dados e avaliar seu impacto no desempenho dos modelos faz parte do processo iterativo da Ciência de Dados. Essa experimentação é essencial para desenvolver soluções robustas e eficazes.
Testar diferentes abordagens é uma das partes mais interessantes e criativas da Ciência de Dados!
4.1 Transformação de Variáveis/Atributos¶
df.head()
| hotel | is_canceled | lead_time | arrival_date_year | arrival_date_month | arrival_date_week_number | arrival_date_day_of_month | stays_in_weekend_nights | stays_in_week_nights | adults | ... | deposit_type | agent | company | days_in_waiting_list | customer_type | adr | required_car_parking_spaces | total_of_special_requests | reservation_status | reservation_status_date | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | Resort Hotel | 0 | 342 | 2015 | July | 27 | 1 | 0 | 0 | 2 | ... | No Deposit | NaN | NaN | 0 | Transient | 0.0 | 0 | 0 | Check-Out | 2015-07-01 |
| 1 | Resort Hotel | 0 | 737 | 2015 | July | 27 | 1 | 0 | 0 | 2 | ... | No Deposit | NaN | NaN | 0 | Transient | 0.0 | 0 | 0 | Check-Out | 2015-07-01 |
| 2 | Resort Hotel | 0 | 7 | 2015 | July | 27 | 1 | 0 | 1 | 1 | ... | No Deposit | NaN | NaN | 0 | Transient | 75.0 | 0 | 0 | Check-Out | 2015-07-02 |
| 3 | Resort Hotel | 0 | 13 | 2015 | July | 27 | 1 | 0 | 1 | 1 | ... | No Deposit | 304.0 | NaN | 0 | Transient | 75.0 | 0 | 0 | Check-Out | 2015-07-02 |
| 4 | Resort Hotel | 0 | 14 | 2015 | July | 27 | 1 | 0 | 2 | 2 | ... | No Deposit | 240.0 | NaN | 0 | Transient | 98.0 | 0 | 1 | Check-Out | 2015-07-03 |
5 rows × 32 columns
df_proc = df.copy()
df_proc.head()
| hotel | is_canceled | lead_time | arrival_date_year | arrival_date_month | arrival_date_week_number | arrival_date_day_of_month | stays_in_weekend_nights | stays_in_week_nights | adults | ... | deposit_type | agent | company | days_in_waiting_list | customer_type | adr | required_car_parking_spaces | total_of_special_requests | reservation_status | reservation_status_date | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | Resort Hotel | 0 | 342 | 2015 | July | 27 | 1 | 0 | 0 | 2 | ... | No Deposit | NaN | NaN | 0 | Transient | 0.0 | 0 | 0 | Check-Out | 2015-07-01 |
| 1 | Resort Hotel | 0 | 737 | 2015 | July | 27 | 1 | 0 | 0 | 2 | ... | No Deposit | NaN | NaN | 0 | Transient | 0.0 | 0 | 0 | Check-Out | 2015-07-01 |
| 2 | Resort Hotel | 0 | 7 | 2015 | July | 27 | 1 | 0 | 1 | 1 | ... | No Deposit | NaN | NaN | 0 | Transient | 75.0 | 0 | 0 | Check-Out | 2015-07-02 |
| 3 | Resort Hotel | 0 | 13 | 2015 | July | 27 | 1 | 0 | 1 | 1 | ... | No Deposit | 304.0 | NaN | 0 | Transient | 75.0 | 0 | 0 | Check-Out | 2015-07-02 |
| 4 | Resort Hotel | 0 | 14 | 2015 | July | 27 | 1 | 0 | 2 | 2 | ... | No Deposit | 240.0 | NaN | 0 | Transient | 98.0 | 0 | 1 | Check-Out | 2015-07-03 |
5 rows × 32 columns
4.1.1 Imputação de Valores Ausentes¶
Embora o conjunto de dados atual não apresente valores ausentes, é importante documentar estratégias de imputação para lidar com situações futuras em que dados faltantes possam surgir durante o processo de pré-processamento ou em atualizações da base de dados.
A imputação de valores ausentes deve ser realizada com cuidado, considerando o tipo de variável (numérica ou categórica) e a distribuição dos dados. Abaixo estão dois métodos amplamente utilizados para variáveis numéricas:
Imputação pela Mediana
A substituição de valores ausentes pela mediana é recomendada quando a variável possui outliers, pois a mediana é menos sensível a valores extremos do que a média.
Implementação com SimpleImputer:
from sklearn.impute import SimpleImputer
import numpy as np
# Criando o imputador com estratégia de mediana
imp_num = SimpleImputer(missing_values=np.nan, strategy='median', add_indicator=True)
# Aplicando a imputação nos dados
dados_imputados = imp_num.fit_transform(dados)
pct_miss = 100*df.isnull().sum()/len(df)
pct_miss[(-pct_miss).argsort()]
company 94.306893 agent 13.686238 country 0.408744 children 0.003350 hotel 0.000000 total_of_special_requests 0.000000 required_car_parking_spaces 0.000000 adr 0.000000 customer_type 0.000000 days_in_waiting_list 0.000000 deposit_type 0.000000 booking_changes 0.000000 assigned_room_type 0.000000 reserved_room_type 0.000000 previous_bookings_not_canceled 0.000000 previous_cancellations 0.000000 distribution_channel 0.000000 reservation_status 0.000000 market_segment 0.000000 meal 0.000000 babies 0.000000 adults 0.000000 stays_in_week_nights 0.000000 stays_in_weekend_nights 0.000000 arrival_date_day_of_month 0.000000 arrival_date_week_number 0.000000 arrival_date_month 0.000000 arrival_date_year 0.000000 lead_time 0.000000 is_canceled 0.000000 is_repeated_guest 0.000000 reservation_status_date 0.000000 dtype: float64
Iremos excluir as variaveis companye agent considerando (i) quantidade elevada de dados missing e a (ii) baixa capacidade de entregar relevancia o modelo, identificando no dicionário de dados perceberemos que são ID.
Definimos excluir as variáveis country e reservation_status_date, a primeira no processo de transformar a variável em numérica, devido a sua grande quantidade de registros categóricos únicos (177) poderia aumentar a dimensionalidade dos dados, já a segunda assuminos a priore que não possui valor preditivo. (PS(i). essas 2 variáveis so chegamos a essa conclusão quando estávamos desenvolvendo o passo 4.1.2)
Exluiremos também a variável reservation_status pois o modelo pode haver um "vazamento de informção" e o modelo não conseguirá generalizar pois essa feature, ao que parece, ocorre posterior a variável target. (PS(ii). treinamos o modelo com essa variavel e teve um overfitting)
Já para children vamos excluir os registros dado a quantidade de obsrvações geral que temos,
print(f'dados antes do tratamento de missing values: {df_proc.shape}')
dados antes do tratamento de missing values: (119390, 32)
df_proc= df_proc.drop(columns=['company', 'agent', 'country', 'reservation_status_date', 'reservation_status'])
print(f'dados após o primeiro tratamento de missing values: {df_proc.shape}')
dados após o primeiro tratamento de missing values: (119390, 27)
df_proc= df_proc.dropna(subset=['children'])
print(f'dados após o segundo tratamento de missing values: {df_proc.shape}')
dados após o segundo tratamento de missing values: (119386, 27)
4.1.2 Codificação de Variáveis Categóricas (Dummies)¶
Para que os algoritmos de Machine Learning possam processar corretamente os dados, todas as variáveis precisam estar em formato numérico. Como muitas variáveis categóricas contêm informações importantes para o modelo, é necessário convertê-las para valores numéricos sem perder informação semântica. Esse processo é conhecido como codificação de variáveis categóricas.
A técnica de codificação utilizada depende do tipo de variável categórica:
Codificação de Variáveis Binárias:
Quando a variável possui apenas duas categorias distintas (ex.: Gender com valores Masculino e Feminino), a abordagem mais eficiente é a codificação ordinal.
✅ Método Utilizado: LabelEncoder
Atribui um valor numérico sequencial para representar cada categoria. Exemplo: Masculino → 0 Feminino → 1 Essa abordagem é simples e evita a criação de colunas adicionais, tornando o modelo mais eficiente.
Codificação de Variáveis Multiclasse:
Quando a variável contém mais de duas categorias distintas (ex.: Market_Segment, Deposit_Type, Meal), o método mais adequado é a codificação One-Hot Encoding.
✅ Método Utilizado: pd.get_dummies()
Cria colunas binárias para cada categoria, onde cada coluna indica a presença (1) ou ausência (0) daquela categoria na amostra.
Atenção à Dimensionalidade dos Dados!
Criar muitas colunas adicionais pode aumentar significativamente a dimensionalidade do dataset, tornando o modelo mais complexo e podendo levar a overfitting. Para evitar redundância, recomenda-se excluir uma das colunas geradas (evitar dummy variable trap - efeito da multicolinearidade).
df_proc[df_proc.select_dtypes(include=['object']).columns].nunique().sort_values(ascending=True)
hotel 2 deposit_type 3 customer_type 4 meal 5 distribution_channel 5 market_segment 7 reserved_room_type 10 arrival_date_month 12 assigned_room_type 12 dtype: int64
df_proc.select_dtypes(include=['object']).columns
Index(['hotel', 'arrival_date_month', 'meal', 'market_segment',
'distribution_channel', 'reserved_room_type', 'assigned_room_type',
'deposit_type', 'customer_type'],
dtype='object')
Binárias
label_encoder = LabelEncoder()
df_proc['hotel'] = label_encoder.fit_transform(df_proc['hotel'])
print(df_proc['hotel'].head())
0 1 1 1 2 1 3 1 4 1 Name: hotel, dtype: int64
print(f'dataset antes do tratamento de variáveis categóricas: {df_proc.shape}')
dataset antes do tratamento de variáveis categóricas: (119386, 27)
Multiclasse
colunas_dummies = ['arrival_date_month', 'meal', 'market_segment', 'distribution_channel', 'reserved_room_type',\
'assigned_room_type', 'deposit_type', 'customer_type']
df_proc.head()
| hotel | is_canceled | lead_time | arrival_date_year | arrival_date_month | arrival_date_week_number | arrival_date_day_of_month | stays_in_weekend_nights | stays_in_week_nights | adults | ... | previous_bookings_not_canceled | reserved_room_type | assigned_room_type | booking_changes | deposit_type | days_in_waiting_list | customer_type | adr | required_car_parking_spaces | total_of_special_requests | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 1 | 0 | 342 | 2015 | July | 27 | 1 | 0 | 0 | 2 | ... | 0 | C | C | 3 | No Deposit | 0 | Transient | 0.0 | 0 | 0 |
| 1 | 1 | 0 | 737 | 2015 | July | 27 | 1 | 0 | 0 | 2 | ... | 0 | C | C | 4 | No Deposit | 0 | Transient | 0.0 | 0 | 0 |
| 2 | 1 | 0 | 7 | 2015 | July | 27 | 1 | 0 | 1 | 1 | ... | 0 | A | C | 0 | No Deposit | 0 | Transient | 75.0 | 0 | 0 |
| 3 | 1 | 0 | 13 | 2015 | July | 27 | 1 | 0 | 1 | 1 | ... | 0 | A | A | 0 | No Deposit | 0 | Transient | 75.0 | 0 | 0 |
| 4 | 1 | 0 | 14 | 2015 | July | 27 | 1 | 0 | 2 | 2 | ... | 0 | A | A | 0 | No Deposit | 0 | Transient | 98.0 | 0 | 1 |
5 rows × 27 columns
drop_first=True: Este parâmetro indica que a primeira categoria de cada coluna categórica deve ser removida. Isso é feito para evitar a multicolinearidade em modelos de regressão. Quando você tem n categorias, você só precisa de n-1 variáveis dummy para representá-las, pois a última categoria pode ser inferida a partir das outras.
df_proc = pd.get_dummies(df_proc, columns=colunas_dummies, dtype=int, drop_first=True)
print(f'dataset após o tratamento de variáveis categóricas: {df_proc.shape}')
dataset após o tratamento de variáveis categóricas: (119386, 69)
df_proc.head()
| hotel | is_canceled | lead_time | arrival_date_year | arrival_date_week_number | arrival_date_day_of_month | stays_in_weekend_nights | stays_in_week_nights | adults | children | ... | assigned_room_type_H | assigned_room_type_I | assigned_room_type_K | assigned_room_type_L | assigned_room_type_P | deposit_type_Non Refund | deposit_type_Refundable | customer_type_Group | customer_type_Transient | customer_type_Transient-Party | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 1 | 0 | 342 | 2015 | 27 | 1 | 0 | 0 | 2 | 0.0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 |
| 1 | 1 | 0 | 737 | 2015 | 27 | 1 | 0 | 0 | 2 | 0.0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 |
| 2 | 1 | 0 | 7 | 2015 | 27 | 1 | 0 | 1 | 1 | 0.0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 |
| 3 | 1 | 0 | 13 | 2015 | 27 | 1 | 0 | 1 | 1 | 0.0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 |
| 4 | 1 | 0 | 14 | 2015 | 27 | 1 | 0 | 2 | 2 | 0.0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 |
5 rows × 69 columns
df_proc.info()
<class 'pandas.core.frame.DataFrame'> Index: 119386 entries, 0 to 119389 Data columns (total 69 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 hotel 119386 non-null int64 1 is_canceled 119386 non-null int64 2 lead_time 119386 non-null int64 3 arrival_date_year 119386 non-null int64 4 arrival_date_week_number 119386 non-null int64 5 arrival_date_day_of_month 119386 non-null int64 6 stays_in_weekend_nights 119386 non-null int64 7 stays_in_week_nights 119386 non-null int64 8 adults 119386 non-null int64 9 children 119386 non-null float64 10 babies 119386 non-null int64 11 is_repeated_guest 119386 non-null int64 12 previous_cancellations 119386 non-null int64 13 previous_bookings_not_canceled 119386 non-null int64 14 booking_changes 119386 non-null int64 15 days_in_waiting_list 119386 non-null int64 16 adr 119386 non-null float64 17 required_car_parking_spaces 119386 non-null int64 18 total_of_special_requests 119386 non-null int64 19 arrival_date_month_August 119386 non-null int64 20 arrival_date_month_December 119386 non-null int64 21 arrival_date_month_February 119386 non-null int64 22 arrival_date_month_January 119386 non-null int64 23 arrival_date_month_July 119386 non-null int64 24 arrival_date_month_June 119386 non-null int64 25 arrival_date_month_March 119386 non-null int64 26 arrival_date_month_May 119386 non-null int64 27 arrival_date_month_November 119386 non-null int64 28 arrival_date_month_October 119386 non-null int64 29 arrival_date_month_September 119386 non-null int64 30 meal_FB 119386 non-null int64 31 meal_HB 119386 non-null int64 32 meal_SC 119386 non-null int64 33 meal_Undefined 119386 non-null int64 34 market_segment_Complementary 119386 non-null int64 35 market_segment_Corporate 119386 non-null int64 36 market_segment_Direct 119386 non-null int64 37 market_segment_Groups 119386 non-null int64 38 market_segment_Offline TA/TO 119386 non-null int64 39 market_segment_Online TA 119386 non-null int64 40 distribution_channel_Direct 119386 non-null int64 41 distribution_channel_GDS 119386 non-null int64 42 distribution_channel_TA/TO 119386 non-null int64 43 distribution_channel_Undefined 119386 non-null int64 44 reserved_room_type_B 119386 non-null int64 45 reserved_room_type_C 119386 non-null int64 46 reserved_room_type_D 119386 non-null int64 47 reserved_room_type_E 119386 non-null int64 48 reserved_room_type_F 119386 non-null int64 49 reserved_room_type_G 119386 non-null int64 50 reserved_room_type_H 119386 non-null int64 51 reserved_room_type_L 119386 non-null int64 52 reserved_room_type_P 119386 non-null int64 53 assigned_room_type_B 119386 non-null int64 54 assigned_room_type_C 119386 non-null int64 55 assigned_room_type_D 119386 non-null int64 56 assigned_room_type_E 119386 non-null int64 57 assigned_room_type_F 119386 non-null int64 58 assigned_room_type_G 119386 non-null int64 59 assigned_room_type_H 119386 non-null int64 60 assigned_room_type_I 119386 non-null int64 61 assigned_room_type_K 119386 non-null int64 62 assigned_room_type_L 119386 non-null int64 63 assigned_room_type_P 119386 non-null int64 64 deposit_type_Non Refund 119386 non-null int64 65 deposit_type_Refundable 119386 non-null int64 66 customer_type_Group 119386 non-null int64 67 customer_type_Transient 119386 non-null int64 68 customer_type_Transient-Party 119386 non-null int64 dtypes: float64(2), int64(67) memory usage: 63.8 MB
df_proc.corr()
| hotel | is_canceled | lead_time | arrival_date_year | arrival_date_week_number | arrival_date_day_of_month | stays_in_weekend_nights | stays_in_week_nights | adults | children | ... | assigned_room_type_H | assigned_room_type_I | assigned_room_type_K | assigned_room_type_L | assigned_room_type_P | deposit_type_Non Refund | deposit_type_Refundable | customer_type_Group | customer_type_Transient | customer_type_Transient-Party | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| hotel | 1.000000 | -0.136505 | -0.075405 | -0.035308 | -0.001261 | 0.001836 | 0.186595 | 0.234022 | 0.013199 | 0.044205 | ... | 0.108997 | 0.077712 | -0.034394 | 0.004073 | -0.003586 | -0.172014 | 0.042234 | 0.023120 | 0.005713 | -0.027768 |
| is_canceled | -0.136505 | 1.000000 | 0.293177 | 0.016732 | 0.008132 | -0.006084 | -0.001783 | 0.024771 | 0.059990 | 0.005048 | ... | -0.002866 | -0.040783 | -0.032811 | 0.003773 | 0.013072 | 0.481488 | -0.011310 | -0.038696 | 0.133170 | -0.124231 |
| lead_time | -0.075405 | 0.293177 | 1.000000 | 0.040093 | 0.126885 | 0.002234 | 0.085667 | 0.165799 | 0.119544 | -0.037622 | ... | -0.021101 | -0.019328 | -0.027952 | -0.002817 | -0.009759 | 0.380174 | 0.016586 | -0.031927 | -0.174026 | 0.159622 |
| arrival_date_year | -0.035308 | 0.016732 | 0.040093 | 1.000000 | -0.540566 | -0.000279 | 0.021489 | 0.030878 | 0.029674 | 0.054624 | ... | 0.001462 | 0.000249 | 0.008904 | -0.004732 | 0.004867 | -0.065987 | -0.000762 | -0.010987 | 0.227880 | -0.163499 |
| arrival_date_week_number | -0.001261 | 0.008132 | 0.126885 | -0.540566 | 1.000000 | 0.066824 | 0.018209 | 0.015559 | 0.025901 | 0.005518 | ... | 0.008424 | -0.004897 | 0.003531 | 0.000390 | 0.003133 | 0.007777 | -0.016887 | 0.011619 | -0.079526 | 0.042191 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| deposit_type_Non Refund | -0.172014 | 0.481488 | 0.380174 | -0.065987 | 0.007777 | -0.008629 | -0.114279 | -0.080002 | -0.028972 | -0.096671 | ... | -0.028234 | -0.020139 | -0.018057 | -0.001080 | -0.003741 | 1.000000 | -0.013752 | -0.026000 | 0.115851 | -0.121434 |
| deposit_type_Refundable | 0.042234 | -0.011310 | 0.016586 | -0.000762 | -0.016887 | 0.032149 | 0.001761 | 0.006791 | 0.003247 | -0.006754 | ... | -0.002855 | -0.002036 | -0.001784 | -0.000107 | -0.000370 | -0.013752 | 1.000000 | 0.023679 | -0.053433 | 0.054660 |
| customer_type_Group | 0.023120 | -0.038696 | -0.031927 | -0.010987 | 0.011619 | -0.001551 | -0.007163 | -0.016182 | 0.058978 | -0.006650 | ... | -0.003829 | 0.007120 | 0.004131 | -0.000202 | 0.011349 | -0.026000 | 0.023679 | 1.000000 | -0.120903 | -0.035975 |
| customer_type_Transient | 0.005713 | 0.133170 | -0.174026 | 0.227880 | -0.079526 | -0.000670 | 0.019475 | 0.007292 | 0.091051 | 0.096135 | ... | 0.031823 | -0.007902 | 0.003840 | 0.001668 | 0.003848 | 0.115851 | -0.053433 | -0.120903 | 1.000000 | -0.895584 |
| customer_type_Transient-Party | -0.027768 | -0.124231 | 0.159622 | -0.163499 | 0.042191 | 0.006339 | -0.064914 | -0.064277 | -0.115814 | -0.092846 | ... | -0.027174 | 0.006950 | -0.002428 | -0.001494 | -0.005176 | -0.121434 | 0.054660 | -0.035975 | -0.895584 | 1.000000 |
69 rows × 69 columns
pd.set_option('display.max_rows', None)
df_proc.corr().iloc[1]
hotel -0.136505 is_canceled 1.000000 lead_time 0.293177 arrival_date_year 0.016732 arrival_date_week_number 0.008132 arrival_date_day_of_month -0.006084 stays_in_weekend_nights -0.001783 stays_in_week_nights 0.024771 adults 0.059990 children 0.005048 babies -0.032488 is_repeated_guest -0.084788 previous_cancellations 0.110140 previous_bookings_not_canceled -0.057355 booking_changes -0.144371 days_in_waiting_list 0.054193 adr 0.047622 required_car_parking_spaces -0.195492 total_of_special_requests -0.234706 arrival_date_month_August 0.005223 arrival_date_month_December -0.010513 arrival_date_month_February -0.020201 arrival_date_month_January -0.031064 arrival_date_month_July 0.002953 arrival_date_month_June 0.029054 arrival_date_month_March -0.030254 arrival_date_month_May 0.018004 arrival_date_month_November -0.029534 arrival_date_month_October 0.006697 arrival_date_month_September 0.013707 meal_FB 0.038832 meal_HB -0.019829 meal_SC 0.001296 meal_Undefined -0.025893 market_segment_Complementary -0.039304 market_segment_Corporate -0.081660 market_segment_Direct -0.154420 market_segment_Groups 0.221886 market_segment_Offline TA/TO -0.028451 market_segment_Online TA -0.006269 distribution_channel_Direct -0.151609 distribution_channel_GDS -0.014889 distribution_channel_TA/TO 0.176056 distribution_channel_Undefined -0.002220 reserved_room_type_B -0.008771 reserved_room_type_C -0.007333 reserved_room_type_D -0.047684 reserved_room_type_E -0.038625 reserved_room_type_F -0.021760 reserved_room_type_G -0.001666 reserved_room_type_H 0.005488 reserved_room_type_L -0.000544 reserved_room_type_P 0.013072 assigned_room_type_B -0.037967 assigned_room_type_C -0.053872 assigned_room_type_D -0.128017 assigned_room_type_E -0.064784 assigned_room_type_F -0.045971 assigned_room_type_G -0.019858 assigned_room_type_H -0.002866 assigned_room_type_I -0.040783 assigned_room_type_K -0.032811 assigned_room_type_L 0.003773 assigned_room_type_P 0.013072 deposit_type_Non Refund 0.481488 deposit_type_Refundable -0.011310 customer_type_Group -0.038696 customer_type_Transient 0.133170 customer_type_Transient-Party -0.124231 Name: is_canceled, dtype: float64
pd.reset_option('display.max_rows')
Após a transformação das variáveis categóricas em numéricas por meio da técnica de dummies, analisamos a correlação entre essas variáveis e a variável alvo. Esse processo nos permitiu identificar quais atributos possuem maior potencial preditivo e quais apresentam uma relação fraca com o cancelamento das reservas.
Com base nos resultados, optamos por remover as variáveis cuja correlação foi insignificante, simplificando o modelo e reduzindo a complexidade sem perda substancial de informação. No entanto, é fundamental destacar que qualquer manipulação nos dados impacta diretamente o desempenho do modelo, podendo resultar tanto em melhorias quanto em perdas significativas de desempenho.
Por razões didáticas, seguiremos essa abordagem de exclusão, mas reforçamos que, em um processo mais refinado de feature engineering, seria recomendável explorar novas combinações de variáveis. Um exemplo seria a fusão das variáveis children, babies e adults em uma única variável familia, que poderia capturar melhor o comportamento dos hóspedes em relação ao cancelamento. Essa etapa de criação de novas features pode revelar relações ocultas e aprimorar a capacidade preditiva do modelo.
df_proc.columns
Index(['hotel', 'is_canceled', 'lead_time', 'arrival_date_year',
'arrival_date_week_number', 'arrival_date_day_of_month',
'stays_in_weekend_nights', 'stays_in_week_nights', 'adults', 'children',
'babies', 'is_repeated_guest', 'previous_cancellations',
'previous_bookings_not_canceled', 'booking_changes',
'days_in_waiting_list', 'adr', 'required_car_parking_spaces',
'total_of_special_requests', 'arrival_date_month_August',
'arrival_date_month_December', 'arrival_date_month_February',
'arrival_date_month_January', 'arrival_date_month_July',
'arrival_date_month_June', 'arrival_date_month_March',
'arrival_date_month_May', 'arrival_date_month_November',
'arrival_date_month_October', 'arrival_date_month_September', 'meal_FB',
'meal_HB', 'meal_SC', 'meal_Undefined', 'market_segment_Complementary',
'market_segment_Corporate', 'market_segment_Direct',
'market_segment_Groups', 'market_segment_Offline TA/TO',
'market_segment_Online TA', 'distribution_channel_Direct',
'distribution_channel_GDS', 'distribution_channel_TA/TO',
'distribution_channel_Undefined', 'reserved_room_type_B',
'reserved_room_type_C', 'reserved_room_type_D', 'reserved_room_type_E',
'reserved_room_type_F', 'reserved_room_type_G', 'reserved_room_type_H',
'reserved_room_type_L', 'reserved_room_type_P', 'assigned_room_type_B',
'assigned_room_type_C', 'assigned_room_type_D', 'assigned_room_type_E',
'assigned_room_type_F', 'assigned_room_type_G', 'assigned_room_type_H',
'assigned_room_type_I', 'assigned_room_type_K', 'assigned_room_type_L',
'assigned_room_type_P', 'deposit_type_Non Refund',
'deposit_type_Refundable', 'customer_type_Group',
'customer_type_Transient', 'customer_type_Transient-Party'],
dtype='object')
df_proc= df_proc.drop(columns=[ 'arrival_date_month_August', 'arrival_date_month_December', 'arrival_date_month_February',\
'arrival_date_month_January', 'arrival_date_month_July', 'arrival_date_month_June', 'arrival_date_month_March',\
'arrival_date_month_May', 'arrival_date_month_November', 'arrival_date_month_October', 'arrival_date_month_September',\
'arrival_date_week_number', 'arrival_date_day_of_month', 'adults', 'children', 'babies', 'stays_in_weekend_nights', 'stays_in_week_nights'])
print(f'dataset após o processamento: {df_proc.shape}')
dataset após o processamento: (119386, 51)
4.2 Split dados¶
Para avaliar o desempenho do modelo, é fundamental dividir os dados em conjuntos de treino e teste. Duas abordagens amplamente utilizadas são:
- (i) o método estático, implementado pela função train_test_split da biblioteca sklearn.model_selection, que separa os dados em uma única iteração; e
- (ii) a validação cruzada (Cross-validation), que realiza divisões aleatórias e avalia o modelo em diferentes subconjuntos para maior robustez.
Optamos pelo método estático, mantendo o default de 75% dos dados para treino e 25% para teste, garantindo simplicidade e uma divisão consistente para o desenvolvimento inicial do modelo.
4.2.1 Estático¶
df_proc.head()
| hotel | is_canceled | lead_time | arrival_date_year | is_repeated_guest | previous_cancellations | previous_bookings_not_canceled | booking_changes | days_in_waiting_list | adr | ... | assigned_room_type_H | assigned_room_type_I | assigned_room_type_K | assigned_room_type_L | assigned_room_type_P | deposit_type_Non Refund | deposit_type_Refundable | customer_type_Group | customer_type_Transient | customer_type_Transient-Party | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 1 | 0 | 342 | 2015 | 0 | 0 | 0 | 3 | 0 | 0.0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 |
| 1 | 1 | 0 | 737 | 2015 | 0 | 0 | 0 | 4 | 0 | 0.0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 |
| 2 | 1 | 0 | 7 | 2015 | 0 | 0 | 0 | 0 | 0 | 75.0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 |
| 3 | 1 | 0 | 13 | 2015 | 0 | 0 | 0 | 0 | 0 | 75.0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 |
| 4 | 1 | 0 | 14 | 2015 | 0 | 0 | 0 | 0 | 0 | 98.0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 |
5 rows × 51 columns
X = df_proc.drop(columns= ['is_canceled'], axis= 1)
y = df_proc.is_canceled
O parâmetro stratify=True assegura que a divisão dos dados em conjuntos de treino e teste mantenha a proporção original das classes da variável alvo, evitando desequilíbrios entre as categorias.
Já o parâmetro shuffle=True tem a função de embaralhar aleatoriamente os dados antes da divisão, garantindo que a separação entre treino e teste ocorra de forma aleatória, reduzindo o risco de viés nos conjuntos.
X_treino, X_teste, y_treino, y_teste = train_test_split(X, y, stratify=y, shuffle=True)
print('tamanho total dos dados são {}, para treino temos {} e teste são {}'.format(df_proc.shape[0], len(X_treino), len(X_teste)))
tamanho total dos dados são 119386, para treino temos 89539 e teste são 29847
4.3 Feature Engineer - II¶
4.3.1.Balanceamento de classes¶
São técnicas para manter um "certo balancemanto" entre as classes, pois se temos classe majoritária o modelo de ML, possivelmente aprenderá mais sobre ela, logo temos um problema -> modelo sem generalização, devemos evitar!!.
Podemos utilizar:
- Oversampling (Superamostragem): Aumentar a quantidade de registros da classe minoritária por meio da duplicação de dados ou geração de novas amostras sintéticas, como a técnica SMOTE (Synthetic Minority Over-sampling Technique).
- Undersampling (Subamostragem): Reduzir a quantidade de registros da classe majoritária, eliminando amostras redundantes para equilibrar a proporção entre as classes.
ATENÇÃO 1: o ideal é sempre aplicar as técnicas depois de realizar o split dos dados (treino e teste). Se aplicar o balancemento antes, o padrão usado para aplicar o oversampling/undersampling será o mesmo nos dados de treino e de teste, com isso a avaliação do modelo fica comprometida.
ATENÇÃO 2: você ao tomar decisão de balancear os dados, diretamente está alterando seu conjunto de dados. Ou seja, saiba o que está fazendo e para que está fazendo!!!!
Conforme já dito anteriormente(item [3.1.2.2]), neste estudo a variável is_canceled apresenta um leve desbalanceamento, mas não em um nível que exija, de imediato, a aplicação de técnicas de balanceamento. Iremos treinar o modelo, assumindo esse leve desbalanceamento, muito embora seja realizado uma segunda versão buscando balancear as classes.
= output: dados pré-processados e splitados para iniciar a construção do modelo de ML
4.4 Avaliação de Multicolinearidade¶
A multicolinearidade ocorre quando variáveis independentes estão altamente correlacionadas, o que pode distorcer os coeficientes do modelo, dificultar a interpretação e reduzir a precisão preditiva.
Para detectar esse problema, utiliza-se o Fator de Inflação da Variância (VIF), que indica o quanto a variância de um coeficiente é ampliada pela correlação entre variáveis.
Interpretação do VIF:
- VIF ≈ 1: Baixa correlação (sem multicolinearidade).
- VIF > 10: Indica multicolinearidade severa.
Tratamento da Multicolinearidade:
- Ajustar ou remover variáveis com VIF elevado para melhorar a robustez e a interpretabilidade do modelo.
- Quando a remoção não for viável, aplicar técnicas de regularização como:
- Ridge Regression (L2): Penaliza coeficientes grandes, suavizando o impacto de variáveis correlacionadas.
- Lasso Regression (L1): Realiza seleção de variáveis ao reduzir coeficientes menos relevantes a zero.
Essas técnicas ajudam a mitigar a multicolinearidade sem a necessidade de excluir variáveis importantes.
X_treino, X_teste, y_treino, y_teste
vif_data = pd.DataFrame()
vif_data['Feature'] = X_treino.columns
vif_data['VIF'] = [variance_inflation_factor(X_treino.values, i) for i in range(X_treino.shape[1])]
print("Valores de VIF para cada variável:")
print(vif_data)
Valores de VIF para cada variável:
Feature VIF
0 duration 5.91
1 credit_amount 5.39
2 installment_commitment 10.52
3 residence_since 9.75
4 age 4.56
5 existing_credits 11.91
6 num_dependents 12.52
7 own_telephone 2.24
8 foreign_worker 28.04
9 other_parties_guarantor 2.18
10 other_parties_none 23.94
11 other_payment_plans_none 7.66
12 other_payment_plans_stores 1.41
13 housing_own 16.72
14 housing_rent 4.54
15 checking_status_<0 2.33
16 checking_status_>=200 1.32
17 checking_status_no checking 2.83
18 personal_status_male div/sep 1.25
19 personal_status_male mar/wid 1.38
20 personal_status_male single 3.73
21 property_magnitude_life insurance 1.88
22 property_magnitude_no known property 3.20
23 property_magnitude_real estate 2.23
24 job_skilled 7.56
25 job_unemp/unskilled non res 1.47
26 job_unskilled resident 3.52
27 credit_history_critical/other existing credit 8.89
28 credit_history_delayed previously 3.23
29 credit_history_existing paid 12.62
30 credit_history_no credits/all paid 2.04
31 savings_status_500<=X<1000 1.74
32 savings_status_<100 7.48
33 savings_status_>=1000 1.56
34 savings_status_no known savings 3.17
35 employment_4<=X<7 1.61
36 employment_<1 1.69
37 employment_>=7 2.20
38 employment_unemployed 1.75
39 purpose_domestic appliance 1.22
40 purpose_education 1.71
41 purpose_furniture/equipment 3.16
42 purpose_new car 3.86
43 purpose_other 1.21
44 purpose_radio/tv 4.32
45 purpose_repairs 1.30
46 purpose_retraining 1.20
47 purpose_used car 2.34
Pelas features que ficaram com valores acima de 10, sugere-no usar no precesso de regressão logística algurma técnica de regularização.
5. Seleção de Algoritmos¶
5.1 Algoritmos de Classificação¶
A escolha do algoritmo de classificação mais adequado não é previsível, pois diferentes modelos podem apresentar desempenhos distintos dependendo das características dos dados. Por esse motivo, é essencial ajustar (fit) e avaliar diversos algoritmos de classificação no conjunto de dados. O objetivo é identificar o modelo que oferece o melhor desempenho preditivo e generalização.
# Instanciar o modelo
ridge_model = LogisticRegression(penalty='l2', solver='lbfgs', max_iter=1000, random_state=42)
# Treinar o modelo
ridge_model.fit(X_treino, y_treino)
LogisticRegression(max_iter=1000, random_state=42)In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
LogisticRegression(max_iter=1000, random_state=42)
# Avaliar coeficientes após regularização
coef_df = pd.DataFrame({'Feature': X_treino.columns, 'Coefficient': ridge_model.coef_[0]})
print(coef_df.sort_values(by='Coefficient', ascending=False))
Feature Coefficient 45 deposit_type_Non Refund 5.936046 4 previous_cancellations 3.406655 27 reserved_room_type_D 0.856323 20 market_segment_Online TA 0.765776 48 customer_type_Transient 0.532560 28 reserved_room_type_E 0.508711 11 meal_FB 0.308719 26 reserved_room_type_C 0.245691 25 reserved_room_type_B 0.236302 0 hotel 0.215996 23 distribution_channel_TA/TO 0.176384 13 meal_SC 0.095766 15 market_segment_Complementary 0.069976 30 reserved_room_type_G 0.066661 49 customer_type_Transient-Party 0.035394 33 reserved_room_type_P 0.031387 44 assigned_room_type_P 0.031387 46 deposit_type_Refundable 0.008175 8 adr 0.005498 1 lead_time 0.004199 43 assigned_room_type_L 0.000000 24 distribution_channel_Undefined -0.000310 7 days_in_waiting_list -0.000522 2 arrival_date_year -0.001076 32 reserved_room_type_L -0.001119 29 reserved_room_type_F -0.001789 16 market_segment_Corporate -0.002077 31 reserved_room_type_H -0.052992 18 market_segment_Groups -0.063287 21 distribution_channel_Direct -0.070214 47 customer_type_Group -0.070670 40 assigned_room_type_H -0.122995 22 distribution_channel_GDS -0.140848 17 market_segment_Direct -0.150251 12 meal_HB -0.170786 42 assigned_room_type_K -0.222342 41 assigned_room_type_I -0.300484 34 assigned_room_type_B -0.316117 14 meal_Undefined -0.329256 6 booking_changes -0.382760 39 assigned_room_type_G -0.401218 3 is_repeated_guest -0.468324 35 assigned_room_type_C -0.544646 38 assigned_room_type_F -0.602136 5 previous_bookings_not_canceled -0.607917 19 market_segment_Offline TA/TO -0.610231 37 assigned_room_type_E -0.671326 10 total_of_special_requests -0.727341 36 assigned_room_type_D -1.078639 9 required_car_parking_spaces -3.714556
Os coeficientes apresentados representam o impacto de cada variável preditora na probabilidade de um cliente cancelar sua reserva em um hotel. Um coeficiente positivo indica que o aumento da variável preditora aumenta a probabilidade de cancelamento, enquanto um coeficiente negativo indica o oposto. A magnitude do coeficiente reflete a força da influência.
Variáveis com Maior Impacto:
deposit_type_Non Refund (5.936046): Este é o fator mais preditivo para cancelamentos. Depósitos não reembolsáveis estão fortemente associados a uma maior probabilidade de cancelamento, provavelmente porque os clientes que fazem esse tipo de depósito podem ter menos incentivo para manter a reserva se seus planos mudarem.
previous_cancellations (3.406655): Histórico de cancelamentos anteriores é um forte indicador de que um cliente poderá cancelar novamente.
required_car_parking_spaces (-3.714556): Este é o fator com maior impacto negativo. Clientes que precisam de vaga de estacionamento têm uma probabilidade significativamente menor de cancelar, sugerindo que a necessidade de estacionamento pode indicar uma viagem mais planejada e, portanto, menos propensa a ser cancelada.
Variáveis com Impacto Moderado:
market_segment_Online TA (0.765776): Reservas feitas através de agências de viagens online (OTAs) estão associadas a uma maior probabilidade de cancelamento, possivelmente devido à maior flexibilidade e concorrência de preços online.
customer_type_Transient (0.532560): Clientes que se hospedam no hotel sem fazer parte de um grupo ou evento têm maior probabilidade de cancelar.
assigned_room_type_D (-1.078639): Aparentemente, clientes que reservam quartos do tipo "D" têm uma chance menor de cancelar.
Variáveis com Menor Impacto:
meal_FB (0.308719), reserved_room_type_C (0.245691), reserved_room_type_B (0.236302), hotel (0.215996): Estas variáveis têm um impacto positivo relativamente pequeno na probabilidade de cancelamento.
Outras variáveis: A maioria das outras variáveis, como tipo de refeição, tipo de quarto reservado, canal de distribuição e número de solicitações especiais, tem um impacto relativamente pequeno (positivo ou negativo) na probabilidade de cancelamento.
Variáveis com impacto negativo: Indicam fatores que podem reduzir a probabilidade de cancelamento, como necessidade de estacionamento, tempo de antecedência da reserva e ser um hóspede recorrente. Magnitude dos coeficientes: Quanto maior o valor absoluto do coeficiente, maior o impacto da variável na probabilidade de cancelamento. Contexto e conhecimento do negócio: A interpretação dos coeficientes deve ser feita em conjunto com o conhecimento do negócio e outros dados disponíveis. Por exemplo, pode ser interessante investigar por que clientes que reservam através de OTAs ou que fazem depósitos não reembolsáveis têm maior probabilidade de cancelar.
# Probabilidades previstas (classe positiva)
y_prob = ridge_model.predict_proba(X_teste)[:, 1]
# Curva ROC
fpr, tpr, thresholds = roc_curve(y_teste, y_prob)
auc = roc_auc_score(y_teste, y_prob)
# Plot da Curva ROC
plt.figure(figsize=(8, 6))
plt.plot(fpr, tpr, label=f'AUC = {auc:.2f}', color='blue')
plt.plot([0, 1], [0, 1], 'k--', label='Modelo Aleatório')
plt.title('Curva ROC')
plt.xlabel('Taxa de Falsos Positivos (FPR)')
plt.ylabel('Taxa de Verdadeiros Positivos (TPR)')
plt.legend(loc='lower right')
plt.grid()
plt.show()
plt.tight_layout();
<Figure size 640x480 with 0 Axes>
A Curva ROC (Receiver Operating Characteristic) é uma métrica de avaliação muito utilizada para modelos de classificação binária. Ela mede a capacidade do modelo em diferenciar entre as classes positiva e negativa.
O modelo apresenta um AUC = 0,86, o que indica uma boa capacidade de classificação. Isso significa que o modelo tem 86% de chance de classificar corretamente um cliente que poderá realizar ou não um cancelameto.
# Impressão das métricas de classificação
y_pred = ridge_model.predict(X_teste)
print("\nRelatório de Classificação:\n", classification_report(y_teste, y_pred))
Relatório de Classificação:
precision recall f1-score support
0 0.80 0.93 0.86 18792
1 0.84 0.60 0.70 11055
accuracy 0.81 29847
macro avg 0.82 0.77 0.78 29847
weighted avg 0.81 0.81 0.80 29847
O relatório indica que o modelo apresenta um bom desempenho na Classe 0 (reservas não canceladas). O modelo atingiu um recall de 93%, o que significa que ele consegue identificar corretamente 93% das reservas que realmente foram concluídas. Além disso, a precisão de 80% mostra que, entre todas as reservas classificadas como não canceladas, 80% foram corretamente identificadas.
Por outro lado, o desempenho na Classe 1 (reservas canceladas) foi moderado. O recall de 60% indica que o modelo conseguiu identificar 60% das reservas que realmente foram canceladas. No entanto, a precisão de 84% revela que, entre todas as reservas classificadas como canceladas, 84% estavam corretas, indicando uma quantidade considerável de falsos negativos.
A alta taxa de falsos negativos implica que muitas reservas que foram efetivamente canceladas não foram corretamente previstas, o que pode impactar a estratégia operacional do hotel, reduzindo a capacidade de antecipar cancelamentos. Por outro lado, a taxa de falsos positivos também merece atenção, pois algumas reservas que seriam concluídas podem ser incorretamente previstas como canceladas, o que pode levar a ações desnecessárias, como tentativa de retenção ou bloqueio indevido de quartos.
Em termos gerais, o modelo tem boa capacidade de prever reservas que serão mantidas, mas pode ser otimizado para melhorar a detecção de cancelamentos reais e reduzir falsos negativos, garantindo uma melhor alocação de recursos e minimizando impactos operacionais
# Matriz de confusão
cm = confusion_matrix(y_teste, y_pred)
tn, fp, fn, tp = cm.ravel()
specificity = tn / (tn + fp)
print(f"Especificidade: {specificity:.2f}")
disp = ConfusionMatrixDisplay(confusion_matrix=cm, display_labels=ridge_model.classes_)
disp.plot(cmap='Blues', values_format='d')
plt.title('Matriz de Confusão')
plt.grid(False)
plt.show()
Especificidade: 0.93
Verdadeiros Negativos (TN) = 17.541: O modelo classificou corretamente 17.541 reservas como não canceladas, o que mostra que ele tem boa capacidade de identificar reservas que serão concluídas.
Verdadeiros Positivos (TP) = 6.612: O modelo conseguiu prever corretamente 6.612 casos em que as reservas foram canceladas, o que indica que há uma boa detecção de cancelamentos, mas ainda há espaço para melhoria.
Falsos Positivos (FP) = 1.251 O modelo classificou 1.251 reservas como canceladas, mas na realidade elas não foram canceladas. Isso pode levar a ações desnecessárias, como tentativas de retenção para clientes que não precisariam delas.
Falsos Negativos (FN) = 4.443 O modelo previu 4.443 reservas como não canceladas, mas elas foram canceladas. Esse é um número significativo, pois significa que muitos cancelamentos reais não foram detectados, o que pode afetar a gestão de disponibilidade do hotel e causar prejuízos operacionais.
Impacto dos Erros do Modelo Falsos Positivos (FP) Podem levar a ações desnecessárias, como tentativas de contato para retenção de clientes que não iriam cancelar a reserva.
Falsos Negativos (FN) Têm um impacto mais crítico, pois representam reservas canceladas que o modelo não previu, podendo gerar superlotação fictícia e problemas na gestão do hotel.
6. Conclusões¶
Nesta primeira versão do modelo, aplicamos uma abordagem básica de engenharia de atributos, focando na conversão de variáveis categóricas, análise de correlação e remoção de atributos menos relevantes. O modelo apresentou um desempenho satisfatório, especialmente na identificação de reservas que não serão canceladas.
Para versões futuras, podemos explorar engenharia de features mais avançada, como a criação de novas variáveis derivadas, interações entre atributos e técnicas de balanceamento da base para aprimorar a capacidade preditiva. Além disso, ajustes de hiperparâmetros e testes com modelos mais sofisticados podem contribuir para um melhor desempenho.
Esta versão representa um primeiro passo na modelagem preditiva do cancelamento de reservas. Com novas iterações e refinamentos, poderemos evoluir para um modelo mais robusto e eficaz, auxiliando de forma estratégica na gestão de demanda e retenção de clientes da rede hoteleira.