1. 호텔 예약 수요 데이터셋
- 호텔 예약 수요 데이터셋은 일반적으로 호텔 예약에 대한 수요 패턴을 분석하기 위한 데이터셋입니다.
- 이 데이터셋은 예약 취소, 체크인 날짜, 고객 유형, 체류 기간, 객실 유형, 예약 경로 등 다양한 요소를 포함하며, 주로 예약 트랜드 분석, 고객 행동 예측, 수요 예측 등에 사용됩니다.
2. 데이터셋 컬럼 설명
-
- hotel: 호텔 유형 (Resort Hotel, City Hotel)
- is_canceled: 예약 취소 여부 (0: 예약 유지, 1: 예약 취소) ← label
- 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: 보증금 유형 (No Deposit, Non Refund, Refundable)
- days_in_waiting_list: 대기자 명단에 있었던 일 수
- agent: 예약을 중개한 기관
- customer_type: 고객 유형 (예: Transient, Group)
- adr: 평균 일일 요금 (유로)
- required_car_parking_spaces: 주차 공간 요구 수
- total_of_special_requests: 특별 요청 수
- reservation_status: 예약 상태 (Check-Out, Canceled, No-Show)
- reservation_status_date: 예약 상태가 마지막으로 업데이트된 날짜
3. 데이터 전처리 및 EDA(데이터 탐색)
- 이번 데이터 셋에서는 is_canceled를 종속 변수로 두고 독립변수는 선택하여 진행하겠습니다.
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
hotel_df = pd.read_csv('/content/drive/MyDrive/(상위폴더 1)/(상위폴더 2)/Data/hotel_bookings.csv')
hotel_df
hotel_df.info()
hotel_df.describe()
hotel_df.isna().sum()
sns.displot(hotel_df['lead_time'])
sns.boxplot(hotel_df['lead_time'])
# IQR 활용하기
# Quantile() : 분위수를 나눠줌
Q1 = hotel_df['lead_time'].quantile(0.25)
Q3 = hotel_df['lead_time'].quantile(0.75)
print(Q1, Q3)
IQR = Q3 - Q1
# min지점 값 만들기
lower_bound = Q1 - 1.5 * IQR
# max지점 값 만들기
upper_bound = Q3 + 1.5 * IQR
print(lower_bound, upper_bound)
# 1사분위수(Q1) 3사분위수(Q3) 사이에 있는 데이터 개수 확인
hotel_df = hotel_df[(hotel_df['lead_time'] >= lower_bound) & (hotel_df['lead_time'] <= upper_bound)]
len(hotel_df)
sns.boxplot(hotel_df['lead_time'])
sns.barplot(x=hotel_df['distribution_channel'], y=hotel_df['is_canceled'])
# barplot 의 bar 역할
- 신뢰구간(CI) : 평균값이 속할 것으로 예상되는 값의 범위입니다.
- 바 그래프에서 신뢰구간 : 심지(Error Bar)로 나타냅니다.
- 신뢰구간이 좁다 : 평균값에 대한 확신이 높다.
- 신뢰구간이 넓다 : 평균값에 대한 확인이 낮고 데이터가 흩어져있다.
# 월별 취소율 시각화
plt.figure(figsize=(15, 5))
sns.barplot(x=hotel_df['arrival_date_month'], y=hotel_df['is_canceled'])
# barplot을 확인해보니 월이 순서대로 되어있지 않다.
import calender
months = []
# 0은 아무것도 없기 때문에 1부터 시작
for i in range(1, 13) :
months.append(calendar.month_name[i])
print(months)
plt.figure(figsize=(15, 5))
# order : 지정하는 값은 리스트여야하며,
# 그 리스트 안에있는 값과 x축의 값이 일치하면 order의 순서대로 보여지게 된다.
sns.barplot(x=hotel_df['arrival_date_month'], y=hotel_df['is_canceled'], order=months)
hotel_df.isna().sum()
hotel_df['children'].value_counts()
# 0이 굉장히 많기 때문에 null을 0으로 처리해도 괜찮을 거라고 판단
hotel_df['children'] = hotel_df['children'].fillna(0)
# 이번 데이터셋은 취소 여부를 확인하기 때문에
# W(가중치)를 굳이 3개를 사용하기 보단
# 성인, 아이, 애기 3개의 Feature를 합치는게 나을 것 같다
hotel_df['people'] = hotel_df['adults'] + hotel_df['children'] + hotel_df['babies']
hotel_df.drop(['adults', 'children', 'babies'], axis=1, inplace=True)
hotel_df[hotel_df['people'] == 0]
# 3개의 Feature를 합쳐도 0인 값이 있기 때문에 제거
hotel_df.drop(hotel_df[hotel_df['people'] == 0].index, inplace=True)
# 주중과 주말을 굳이 나눌 필요가 없다고 판단
hotel_df['total_nights'] = hotel_df['stays_in_weekend_nights'] + hotel_df['stays_in_week_nights']
hotel_df.drop(['stays_in_weekend_nights', 'stays_in_week_nights'], axis=1, inplace=True)
# Season 파생변수 만들고 'arrival_date_month'를 사용하여 계절로 나누기
# 12, 1, 2 : winter
# 3, 4, 5 : spring
# 6, 7, 8 : summer
# 9, 10, 11 : fall
season_dic = {
'spring' : {3, 4, 5},
'summer' : {6, 7, 8},
'fall' : {9, 10, 11},
'winter' : {12, 1, 2},
}
new_season_dic = {}
for i in season_dic :
for j in season_dic[i] :
new_season_dic[calendar.month_name[j]] = i
new_season_dic
# map(dict) : 키값에 해당하는 값 찾아서 벨류값 집어넣기
hotel_df['season'] = hotel_df['arrival_date_month'].map(new_season_dic)
hotel_df.head()
hotel_df.drop(['arrival_date_month'], axis=1, inplace=True)
# 예약된 룸 타입과 실제 룸타입이 같은지 여부를 확인하는 파생변수 생성
hotel_df['expected_room_type'] = (hotel_df['reserved_room_type'] == hotel_df['assigned_room_type']).astype(int)
hotel_df.drop(['reserved_room_type', 'assigned_room_type'], axis=1, inplace=True)
# 총 예약중 취소 한 비율 파생변수 생성
hotel_df['cancel_rate'] = hotel_df['previous_cancellations'] / (hotel_df['previous_cancellations'] + hotel_df['previous_bookings_not_canceled'])
hotel_df.head()
# 총 예약이 0인 값들도 존재함 (그 값은 NaN으로 되어있다)
hotel_df[hotel_df['cancel_rate'].isna()]
# 위 수식에서 나올 수 없는 값을 넣어 차별점 주기
hotel_df['cancel_rate'] = hotel_df['cancel_rate'].fillna(-1)
hotel_df.drop(['previous_cancellations', 'previous_bookings_not_canceled'], axis=1, inplace=True)
# 예약된 경로를 알수 없는 데이터 확인
hotel_df['agent'].value_counts(dropna=False).sort_index()
# 나올 수 없는 값으로 변경
hotel_df['agent'] = hotel_df['agent'].fillna(-1)
hotel_df['company'].value_counts(dropna=False).sort_index()
hotel_df['company'] = hotel_df['company'].fillna(-1)
# 수치형 데이터가 아닌 컬럼 중 유니크한 데이터의 종류 개수 확인
for i in hotel_df.select_dtypes(exclude=['number']).columns.tolist() :
print(i, hotel_df[i].nunique())
# 연관성이 크게 있어보이지 않는 컬럼들 제거
hotel_df.drop(['meal', 'country', 'reservation_status_date'], axis=1, inplace=True)
# object타입 one-hot encoding
hotel_df = pd.get_dummies(hotel_df, columns=hotel_df.select_dtypes(exclude=['number']).columns.tolist(), drop_first=True)
hotel_df.info()
4. Logistic Regression의 규제
- 규제(Regularization)는 머신러닝 모델이 Overfitting 되는 것을 방지하기 위해 사용되는 기법입니다.
- Overfitting이란 모델이 학습 데이터에 지나치게 맞춰져서, 새로운 데이터(Test 데이터)에는 제대로 일반화 하지 못하는 현상입니다.
- 규제는 모델의 복잡도를 줄이고, 불필요한 W(가중치)를 작게 만들어 과적합을 방지합니다.

from sklearn.linear_model import LogisticRegression
lr = LogisticRegression()
lr.fit(X_train, y_train)
- 반복 횟수가 부족하면 ConvergenceWarning 경고가 나타날 수 있습니다.
- 이는 알고리즘이 최적의 해를 찾지 못했다는 의미입니다.
- max_iter : 최대 반복 횟수를 지정하여 최적의 가중치를 찾습니다. (기본값은 100입니다)
※ epoch와 max_iter의 차이

5. 데이터 스케일링
- 데이터 스케일링은 서로 다른 범위와 단위를 가진 데이터를 일정한 범위로 변환하여 모델 학습을 더 효율적으로 수행 할 수 있도록 만드는 전처리 과정입니다.
- 주로 변수 간의 값의 크기 차이가 클 때 발생하는 불균형을 해결하기 위해 사용되며, 대표적인 방법으로는 표준화(Standardization)와 정규화(Normalization)가 있습니다.
- 스케일링을 통해 학습 속도를 높이고, 기울기 소실(Gradient Vinishing) 문제를 완화하며, 특정 변수에 모델이 과도하게 의존하는 것을 방지 할 수 있습니다.
5-1. Standardization
- 정규화는 데이터의 평균을 0으로, 표준편차를 1로 변환하여 모든 변수가 동일한 척도를 갖도록 만드는 데이터 전처리 기법입니다.
- 주로 데이터의 분포가 정규 분포를 따를 때 효과적이며, 값의 크기나 단위가 서로 다른 변수를 비교하거나 머신러닝 알고리즘(ex : Logistic Regression, SVM 등)에서 최적의 성능을 내기 위해 사용됩니다.

5-2. Normalization
- 정규화는 데이터의 값을 특정 범위(주로 0과 1사이)로 변환하여 변수 간의 스케일 차이를 줄이는 데이터 전처리 기법입니다.
- 이는 주로 최솟값과 최댓값을 사용해 데이터를 조정하며, 대표적인 방법으로는 Min-Max Scaling이 있습니다.

from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = sclaer.fit_transform(X_test)
model = LogisticRegression(max_iter=1000)
model.fit(X_train_scaled, y_train)
pred = model.prdict(X_test_scaled)
# 분류 모델의 정확도 계산하기 0~1사이의 값을 가짐
# 올바르게 분류 된 샘플의 비율을 반환함
from sklearn.metrics import accuracy_score
accuracy_score(y_test, pred)
6. 혼돈 행렬
- 혼돈 행렬(Confusion Matrix)은 분류 모델의 성능을 평가하기 위해 사용되는 도구로, 예측 결과와 실제 레이블 간의 관계를 요약하여 나타낸 행렬입니다.
- 이 행렬은 Binary Classification 문제 에서 4가지 값으로 구성됩니다.
- True Positive(TP)
- 실제값이 양성이고, 모델도 양성으로 올바르게 예측한 경우
- ex : 실제로 스팸 메일이고, 모델이 스펨메일로 예측
- True Nagative(TN)
- 실제값이 음성이고, 모델도 음성으로 올바르게 예측한 경우
- ex : 실제로 정상 메일이고, 모델이 정상 메일로 예측
- False Positive(FP)
- 실제값은 음성이지만, 모델이 양성으로 잘못 예측한 경우
- ex : 정상 메일을 스팸으로 잘못 예측
- False Negative(FN)
- 실제값은 양성이지만, 모델이 음성으로 잘못 예측한 경우
- ex : 스팸 메일을 정상 메일로 잘못 예측
- TP와 TN은 모델이 올바르게 에측한 경우를 나타내며, FP와 FN은 모델이 잘못 예측된 경우를 나타냅니다.
- 이를 통해 분류 모델의 정확도(Accuracy), 정밀도(Precision), 재현율(Recall), F1-score와 같은 다양한 성능 지표를 계산할 수 있습니다.
- 혼동 행렬은 특히 클래스 간의 불균형이 있는 데이터에서 모델의 예측 성능을 구체적으로 분석할 때 유용합니다.

※ 혼돈행렬로 계산할 수 있는 지표

from sklearn.metrics import confusion_matrix, precision_score, recall_score, f1_score
confusion_matrix(y_test, pred)
# 양성으로 예측한 것에서 실제 양성의 비율
print(precision_score(y_test, pred))
# 실제 양성 중에서 양성으로 예측한 비율
print(recall_score(y_test, pred))
# 정밀도와 재현율의 조화평균
print(f1_score(y_test, pred))
※ Weight와 bias 값 확인하기
# 독립 변수의 Weight
lr.coef_
# bias
lr.intercept_
# 확률 값 뽑기
proba = lr.predict_proba(X_test)
# Binary Classification 문제 이기 때문에 임계점을 설정하여 2번째 값과 비교 후 분류
proba = lr.predict_proba(X_test)[:, 1]
threshold = 0.5
pred = (proba >= threshold).astype(int)
pred
7. 분류 문제에서의 Random Forest (주택 임대로 예측에서 설명하였기 때문에 정의 설명은 생략하겠습니다.)
(과정)
- 각 트리의 Node Split 단계에서, 전체 특성이 아닌 무작위로 선택된 일부 특성만을 사용합니다.
- 이를 통해 트리 간의 상관관계를 줄이고, Overfitting을 방지합니다.
- 각 트리에 들어가는 데이터의 개수는 원본 데이터셋 크기와 동일합니다.
- 그러나 중복을 허용하여 샘플링(부트스트래핑)하므로, 일부 데이터는 여러 번 샘플링 되고, 일부 데이터는 선택되지 않을 수 있습니다.
- 부트스트래핑(Bootstrapping)된 데이터와 랜덤하게 선택된 특성을 사용하여 Decision Tree를 학습합니다.
- Sklearn에서 Random Forest의 기본 트리 개수는 100개 입니다.
- 다수결(Majority Voting) : Classification 문제에서 가장 많이 예측된 클래스를 선택합니다.
from sklearn.ensemble import RandomForestClassifier
X_train, X_test, y_train, y_test = train_test_split(hotel_df.drop('is_canceled', axis=1), hotel_df['is_canceled'], test_size=0.3, random_state=2025)
rf = RandomForestClassifier(random_state=2025)
rf.fit(X_train, y_train)
pred = rf.predict(X_test)
pred
proba = rf.predict_proba(X_test)
# 모든 테스트 데이터에 대한 호텔 예약을 취소 할 확률만 출력
proba[:, 1]
confusion_matrix(y_test, pred)
# 혼돈 행렬을 코드 출력값으로 보기
from sklearn.metrics import classification_report
print(classification_report(y_test, pred))
8. ROC AUC Score
- ROC AUC Score(Receriver Operating Characteristic - Area Under the Curve)는 이진 분류 모델의 성능을 평가하는 지표로, 분류기의 예측 능력을 직관적으로 나타냅니다.
- ROC곡선은 FPR(거짓 양성 비율)과 TPR(참 양성 비율)을 축으로 하여 다양한 임계값에서 모델의 성능을 시각화한 곡선입니다.
- AUC는 ROC곡선 아래의 면적을 나타내며 값이 1에 가까울수록 완벽한 분류 성능을 의미하고, 0.5에 가까울수록 랜덤 추측에 가까운 성능을 나타냅니다.
- 따라서 ROC AUC Score는 모델의 분류 성능이 얼마나 좋은지를 평가하는 데에 중요한 역할을 합니다.
8-1. ROC Curve
- ROC 곡선은 Binary Classification Model의 성능을 평가하기 위해 사용하는 곡선으로, 모델의 민감도(참 양성 비율, TPR)와 특이도(참 음성 비율, TNR)의 반대인 거짓 양성 비율(FPR)의 관계를 시각화한 그래프입니다.

8-2. AUC
- AUC는 ROC Curve 아래의 면적을 나타내며, Binary Classification의 성능을 하나의 숫자로 나타내는 지표입니다.
- AUC = 1 : 완벽한 분류기
- AUC = 0.5 : 랜덤 추즉 수준
- AUC < 0.5 : 모델 성능이 무작위 추측보다 나쁨
- 수학적으로 AUC는 양성 클래스와 음성 클래스의 예측 점수를 비교해 양성 클래스가 더 높은 점수를 받을 확률을 나타냅니다.
from sklearn.metrics import roc_auc_score
from sklearn.metrics._plot.roc_curve import roc_curve
import matplotlib.pyplot as plt
# roc_auc_score(TestSet의 종속변수, 확률값())
roc_auc_score(y_test, proba[:, 1])
# FPR, TPR, Threshold값 나옴
fpr, tpr, thr = roc_curve(y_test, proba[:, 1])
print(fpr, tpr, thr)
plt.plot(fpr, tpr, label='ROC Curve')
plt.plot([0, 1], [0, 1])
plt.show()
9. 교차 검증
- 교차 검증(Cross Validation)은 모델의 성능을 더 정확히 평가하기 위해 데이터를 반복적으로 Train 데이터와, Validation 데이터로 나누어 사용하는 기법입니다.
- 가장 일반적인 방식인 k-겹 교차 검증(k-fold Cross Validation)은 데이터를 k개의 동일한 크기로 나누고, 각 부분을 한 번씩을 Validation 데이터로 사용하며 나머지 부분을 Train 데이터로 사용하여 모델을 훈련하고 평가합니다.
- 이를 통해 데이터 분할에 따른 편향을 줄이고, 모델의 예측 능력을 신뢰성 있게 측정할 수 있습니다.
- Cross Validation은 특히 데이터셋이 작거나 편향된 경우에 유용하며, Overfitting을 방지하는 데 도움을 줍니다.

from sklearn.model_selection import KFold
# n_split : 쪼개는 단위
kf = KFold(n_split=5)
kf
# kf.split() : k개의 폴드로 나눔
for train_index, valid_index in kf.split(range(len(hotel_df))) :
print(train_index, valid_index, len(train_index), len(valid_index))
# 하이퍼 파라미터 조정하기
kf = KFold(n_split=5, random_state=2025, shuffle=True)
kf
for train_index, valid_index in kf.split(range(len(hotel_df))) :
print(train_index, valid_index, len(train_index), len(valid_index))
acc_list = []
for train_index, valid_index in kf.split(range(len(hotel_df))) :
X = hotel_df.drop('is_canceled', axis=1)
y = hotel_df['is_canceled']
X_train = X.iloc[train_index]
X_valid = X.iloc[valid_index]
y_train = y.iloc[train_index]
y_valid = y.iloc[valid_index]
rf = RandomForestClassifier()
rf.fit(X_train, y_train)
pred = rf.predict(X_valid)
acc_list.append(accuracy_score(y_valid, pred))
print(acc_list)
print(pred)
print(y_valid)