UNB logo

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
In [1]:
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:

  1. Observar as extensão do arquivo dataSet.
  2. Verificar os acessos aos dataSet (LGPD).
  3. "Timeframe" dos dados (desde quando tem-se os dados).
  4. Valores Missing (será substituido por default ou outro valor - média, por exemplo).
  5. Cabeçalho (header=T), caso False, irá indicar cabeçalho.
  6. Dados possuem algum comentário.
  7. 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).

In [2]:
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
Out[2]:
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

3. Análise Exploratória¶

3.1 Visão Geral dataSet¶

Nesta estapa estamos interessados em entender melhor a pergunta "Quais tipos de dados tem no nosso dataSet?"

  • Tipo Objeto que estamos trabalhando
  • Tipos Dados
  • Shape - Dimensionalidade
  • Índices
  • Descrição dos dados
In [3]:
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))
In [4]:
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.

In [5]:
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.

In [6]:
df.head()
Out[6]:
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

In [7]:
df.tail()
Out[7]:
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:

In [8]:
print('Os dados possuem {:.2f} linhas e {} colunas'.format(df.shape[0], df.shape[1]))
Os dados possuem 119390.00 linhas e 32 colunas
In [9]:
# checando índice (endereço de cada obs) e sua distribuição
df.index
Out[9]:
RangeIndex(start=0, stop=119390, step=1)
In [10]:
# checando os nomes das colunas
df.columns
Out[10]:
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 é a is_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.

In [11]:
df.head()
Out[11]:
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

In [12]:
X = df.drop(columns= 'is_canceled', axis= 1)
y = df['is_canceled']	
In [13]:
y.shape
Out[13]:
(119390,)
In [14]:
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¶
In [15]:
y.describe()
Out[15]:
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
In [16]:
y.unique()
Out[16]:
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.

In [17]:
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¶
In [18]:
# seto algumas caracteristicas para os plots. Padornizar Plots
sns.set_theme(style='darkgrid')
sns.set_palette("hls", 3)
In [19]:
balData = pd.DataFrame(df['is_canceled'].value_counts())
balData['% total'] = round(100*balData['count']/df.shape[0], 2)
In [20]:
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
No description has been provided for this image

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.

In [21]:
x_categoricos =  df.select_dtypes(include=['object'])
x_categoricos['is_canceled'] = df['is_canceled']
x_categoricos.head()
Out[21]:
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
In [22]:
x_categoricos.columns 
Out[22]:
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')
In [23]:
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()
No description has been provided for this image

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.

In [24]:
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()
No description has been provided for this image

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.

In [25]:
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()
No description has been provided for this image

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.

In [26]:
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()
No description has been provided for this image
  1. 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.
  1. 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.
  1. 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.
In [27]:
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()
No description has been provided for this image

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.

In [28]:
x_numericos = df.select_dtypes(include=['int64', 'float64'])
In [29]:
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()
No description has been provided for this image

Clientes que fazem reservas com muita antecedência têm uma maior probabilidade de cancelar.

In [30]:
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()
No description has been provided for this image

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.

In [31]:
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()
No description has been provided for this image

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.

In [32]:
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()
No description has been provided for this image

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.

In [33]:
x_numericos.skew()
Out[33]:
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
In [34]:
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()
No description has been provided for this image

Buscaremos agora compreender a força e direção de uma relação linear entre a variável independente e dependente.

In [35]:
sns.heatmap(x_numericos.corr(),annot = False, cmap="coolwarm")
plt.title("Matriz de correlação dos dados",fontsize= 15)
plt.show()
No description has been provided for this image

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.

In [36]:
# 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])
No description has been provided for this image

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:

  1. Há dados faltantes.
  2. Existe uma predominância nos dados de feature categórica, que enseja no uso de técnicas de dummie's.
  3. Algumas features não tem poder preditivo.
  4. 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.
  5. 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¶

In [37]:
df.head()
Out[37]:
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

In [38]:
df_proc = df.copy()
df_proc.head()
Out[38]:
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)
In [39]:
pct_miss = 100*df.isnull().sum()/len(df)
pct_miss[(-pct_miss).argsort()]
Out[39]:
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,

In [40]:
print(f'dados antes do tratamento de missing values: {df_proc.shape}')
dados antes do tratamento de missing values: (119390, 32)
In [41]:
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)
In [42]:
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).

In [43]:
df_proc[df_proc.select_dtypes(include=['object']).columns].nunique().sort_values(ascending=True)
Out[43]:
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
In [44]:
df_proc.select_dtypes(include=['object']).columns
Out[44]:
Index(['hotel', 'arrival_date_month', 'meal', 'market_segment',
       'distribution_channel', 'reserved_room_type', 'assigned_room_type',
       'deposit_type', 'customer_type'],
      dtype='object')

Binárias

In [45]:
label_encoder = LabelEncoder()
In [46]:
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
In [47]:
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

In [48]:
colunas_dummies = ['arrival_date_month', 'meal', 'market_segment', 'distribution_channel', 'reserved_room_type',\
                    'assigned_room_type', 'deposit_type', 'customer_type']
In [49]:
df_proc.head()
Out[49]:
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.

In [50]:
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)
In [51]:
df_proc.head()
Out[51]:
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

In [52]:
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
In [53]:
df_proc.corr()
Out[53]:
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

In [54]:
pd.set_option('display.max_rows', None)
df_proc.corr().iloc[1]
Out[54]:
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
In [55]:
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.

In [56]:
df_proc.columns
Out[56]:
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')
In [57]:
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'])
In [58]:
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¶

In [59]:
df_proc.head()
Out[59]:
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

In [60]:
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.

In [61]:
X_treino, X_teste, y_treino, y_teste = train_test_split(X, y, stratify=y, shuffle=True)
In [62]:
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

In [165]:
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.

In [64]:
# 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)
Out[64]:
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)
In [65]:
# 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.

In [66]:
# 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();
No description has been provided for this image
<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.

In [70]:
# 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

In [68]:
# 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
No description has been provided for this image

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.