1. 실습 주제
이 실습은 호텔 예약 데이터로부터 예약 취소 여부를 예측하는 이진 분류 문제를 다룬다.
예측 대상은 다음과 같다.
is_canceled
즉, 각 예약 정보가 주어졌을 때 이 예약이 실제로 취소될지 유지될지를 분류하는 것이 목표이다.
입력 변수로는 다음과 같은 정보가 활용된다.
- 호텔 유형
- 예약 후 체크인까지 걸린 시간
- 도착 연도, 월, 주차 정보
- 주중 및 주말 숙박일 수
- 성인, 어린이, 아기 수
- 식사 유형
- 국가
- 유통 채널
- 이전 예약 이력
- 객실 예약 및 배정 정보
- 예약 보증금 유형
- 고객 유형
- 대기 기간
- 주차 공간 요청 수
- 특별 요청 수
- 예약 상태 등
전체 흐름은 다음과 같다.
데이터 로드
↓
데이터 구조 확인
↓
lead_time 이상치 탐색 및 제거
↓
범주형 변수와 취소율 관계 확인
↓
결측치 처리
↓
파생 변수 생성
↓
불필요 컬럼 제거
↓
원-핫 인코딩
↓
학습 / 테스트 데이터 분리
↓
로지스틱 회귀 학습
↓
스케일링 적용
↓
정확도, 정밀도, 재현율, F1 점수 확인
↓
예측 확률과 임계값 조정
↓
랜덤 포레스트 분류기 비교
↓
ROC AUC 및 ROC Curve 확인
↓
K-Fold 교차 검증
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 | 성인 수 |
| 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 | 예약 대행사 ID |
| company | 회사 ID |
| days_in_waiting_list | 대기 리스트 대기일 |
| customer_type | 고객 유형 |
| adr | 평균 일일 요금 |
| required_car_parking_spaces | 필요 주차 공간 수 |
| total_of_special_requests | 특별 요청 수 |
| reservation_status_date | 예약 상태 변경 날짜 |
이 실습의 핵심 타깃은 예약 취소 여부이므로,
이후 모든 전처리와 모델링은 is_canceled 예측에 맞춰 진행된다.
3. 라이브러리 불러오기
실습에서는 NumPy, Pandas, Seaborn, Matplotlib를 먼저 불러온다.
import numpy as np
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
각 라이브러리의 역할은 다음과 같다.
- NumPy: 수치 연산
- Pandas: 데이터 처리와 전처리
- Seaborn: 분포, 박스플롯, 범주형 평균 시각화
- Matplotlib: 그래프 크기와 ROC Curve 시각화
4. pd.read_csv() 와 DataFrame.info()
개요
pd.read_csv()는 CSV 파일을 읽어 DataFrame으로 변환하는 함수이다.
info()는 불러온 데이터의 구조를 한 번에 점검하는 메서드이다.
실습에서는 데이터를 로드한 직후 info()를 함께 사용해 다음을 확인한다.
- 전체 데이터 개수
- 컬럼별 자료형
- 결측치 존재 여부
- 전처리 필요한 컬럼
실습 코드
hotel_df = pd.read_csv('/content/drive/MyDrive/.../hotel_bookings.csv')
hotel_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
ai가 추천하는 심화 예제
hotel_df = pd.read_csv("hotel_bookings.csv")
print(hotel_df.shape)
print(hotel_df.dtypes)
# 결과
# 데이터 크기와 각 컬럼 자료형 출력
5. DataFrame.describe()
개요
describe()는 수치형 컬럼의 기초 통계량을 요약하는 메서드이다.
다음 항목을 한 번에 확인할 수 있다.
- count
- mean
- std
- min
- 25%
- 50%
- 75%
- max
이 실습에서는 수치형 변수의 범위와 이상치 가능성을 빠르게 확인하는 데 사용된다.
실습 코드
hotel_df.describe()
주요 역할
- 수치형 변수의 분포 개요 파악
- 극단값 존재 여부 확인
- 이상치 탐색의 출발점 제공
반환값
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 agent company days_in_waiting_list adr required_car_parking_spaces total_of_special_requests
count 119390.000000 119390.000000 119390.000000 119390.000000 119390.000000 119390.000000 119390.000000 119390.000000 119386.000000 119390.000000 119390.000000 119390.000000 119390.000000 119390.000000 103050.000000 6797.000000 119390.000000 119390.000000 119390.000000 119390.000000
mean 0.370416 104.011416 2016.156554 27.165173 15.798241 0.927599 2.500302 1.856403 0.103890 0.007949 0.031912 0.087118 0.137097 0.221124 86.693382 189.266735 2.321149 101.831122 0.062518 0.571363
std 0.482918 106.863097 0.707476 13.605138 8.780829 0.998613 1.908286 0.579261 0.398561 0.097436 0.175767 0.844336 1.497437 0.652306 110.774548 131.655015 17.594721 50.535790 0.245291 0.792798
min 0.000000 0.000000 2015.000000 1.000000 1.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 1.000000 6.000000 0.000000 -6.380000 0.000000 0.000000
25% 0.000000 18.000000 2016.000000 16.000000 8.000000 0.000000 1.000000 2.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 9.000000 62.000000 0.000000 69.290000 0.000000 0.000000
50% 0.000000 69.000000 2016.000000 28.000000 16.000000 1.000000 2.000000 2.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 14.000000 179.000000 0.000000 94.575000 0.000000 0.000000
75% 1.000000 160.000000 2017.000000 38.000000 23.000000 2.000000 3.000000 2.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 229.000000 270.000000 0.000000 126.000000 0.000000 1.000000
max 1.000000 737.000000 2017.000000 53.000000 31.000000 19.000000 50.000000 55.000000 10.000000 10.000000 1.000000 26.000000 72.000000 21.000000 535.000000 543.000000 391.000000 5400.000000 8.000000 5.000000
ai가 추천하는 심화 예제
print(hotel_df["lead_time"].describe())
# 결과
# lead_time 컬럼의 통계량 출력
6. sns.displot()
개요
displot()은 특정 변수의 분포를 시각화하는 함수이다.
실습에서는 lead_time 분포를 먼저 확인한다.
lead_time은 예약 시점과 실제 체크인까지의 간격을 뜻하므로,
이 값이 지나치게 큰 예약이 존재하는지 확인하는 것이 중요하다.
실습 코드
sns.displot(hotel_df['lead_time'])
주요 역할
- lead_time 분포 확인
- 오른쪽 꼬리가 긴 분포인지 확인
- 이상치 탐색 필요성 판단
반환값

ai가 추천하는 심화 예제
sns.displot(hotel_df["adr"])
# 결과
# 평균 일일 요금의 분포 시각화
7. sns.boxplot(), quantile(), IQR 기반 이상치 제거
개요
박스플롯은 중앙값, 사분위수, 이상치를 시각적으로 확인하는 그래프이다.
실습에서는 lead_time에 대해 박스플롯을 먼저 확인한 후, 사분위수와 IQR을 계산해서 이상치를 제거한다.
이상치 기준은 다음과 같다.
[
IQR = Q3 - Q1
]
[
Lower\ Bound = Q1 - 1.5 \times IQR
]
[
Upper\ Bound = Q3 + 1.5 \times IQR
]
이 범위를 벗어나는 값은 일반적으로 이상치로 본다.
실습 코드
sns.boxplot(hotel_df['lead_time'])
Q1 = hotel_df['lead_time'].quantile(0.25)
Q3 = hotel_df['lead_time'].quantile(0.75)
print(Q1)
print(Q3)
IQR = Q3 - Q1
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR
print(lower_bound)
print(upper_bound)
hotel_df = hotel_df[(hotel_df['lead_time'] >= lower_bound) & (hotel_df['lead_time'] <= upper_bound)]
sns.boxplot(hotel_df['lead_time'])
개별 메서드 설명
quantile(0.25), quantile(0.75)
1사분위수와 3사분위수를 계산한다.
boxplot()
사분위수 기반으로 분포와 이상치를 시각화한다.
불리언 인덱싱
조건을 만족하는 행만 남기고 이상치 행을 제거한다.
주요 역할
- lead_time 이상치 탐색
- 극단값 제거
- 모델이 비정상적인 예약 길이에 과도하게 흔들리지 않도록 조정
반환값


ai가 추천하는 심화 예제
Q1 = hotel_df["adr"].quantile(0.25)
Q3 = hotel_df["adr"].quantile(0.75)
IQR = Q3 - Q1
lower = Q1 - 1.5 * IQR
upper = Q3 + 1.5 * IQR
filtered_df = hotel_df[(hotel_df["adr"] >= lower) & (hotel_df["adr"] <= upper)]
print(filtered_df.shape)
# 결과
# adr 이상치 제거 후 데이터 크기 출력
8. sns.barplot() 와 범주형 변수별 취소율 확인
개요
barplot()은 범주형 변수별 수치형 값의 평균을 보여주는 시각화 함수이다.
실습에서는 distribution_channel과 arrival_date_month에 따라 평균 취소율이 어떻게 달라지는지 확인한다.
is_canceled는 0 또는 1의 값을 가지므로,
barplot에서 y축으로 is_canceled를 두면 각 그룹의 평균값은 곧 취소율로 해석할 수 있다.
예를 들어 어떤 채널의 평균 is_canceled 값이 0.35라면,
해당 채널 예약의 약 35%가 취소되었다고 해석할 수 있다.
실습 코드
sns.barplot(x=hotel_df['distribution_channel'], y=hotel_df['is_canceled'])
plt.figure(figsize=(15, 5))
sns.barplot(x=hotel_df['arrival_date_month'], y=hotel_df['is_canceled'])
주요 역할
- 유통 채널별 취소율 비교
- 월별 취소율 패턴 확인
- 계절성과 예약 채널 특성 탐색
반환값


ai가 추천하는 심화 예제
plt.figure(figsize=(12, 4))
sns.barplot(x=hotel_df["customer_type"], y=hotel_df["is_canceled"])
# 결과
# 고객 유형별 평균 취소율 비교
9. calendar.month_name 과 월 순서 정렬
개요
arrival_date_month는 월 이름이 문자열로 저장되어 있기 때문에,
그대로 시각화하면 알파벳 순서로 정렬될 수 있다.
이를 막기 위해 calendar.month_name을 사용해 1월부터 12월까지의 순서를 직접 만든 뒤,
barplot의 order 옵션으로 전달한다.
실습 코드
import calendar
print(calendar.month_name[1])
print(calendar.month_name[2])
months = []
for i in range(1, 13):
months.append(calendar.month_name[i])
months
plt.figure(figsize=(15, 5))
sns.barplot(x=hotel_df['arrival_date_month'], y=hotel_df['is_canceled'], order=months)
주요 역할
- 월 이름을 시간 순서대로 정렬
- 월별 취소율 패턴을 자연스럽게 해석 가능하게 만듦
반환값
- calendar.month_name[i] → 문자열
- order=months → 범주 정렬 기준 제공

ai가 추천하는 심화 예제
months = [calendar.month_name[i] for i in range(1, 13)]
print(months)
# 결과
# January부터 December까지 순서대로 출력
10. isna().sum(), value_counts(dropna=False), fillna()
개요
실습에서는 결측치가 있는지 확인한 뒤, children 컬럼의 결측치를 0으로 채운다.
children은 동반 어린이 수를 의미하므로,
결측치를 실제로 값이 없다고 보기보다는 어린이가 없는 경우로 처리하는 것이 자연스러울 수 있다.
value_counts(dropna=False)는 결측치까지 포함해서 각 값의 빈도를 확인하는 메서드이다.
실습 코드
hotel_df.isna().sum()
hotel_df['children'].value_counts(dropna=False)
hotel_df['children'] = hotel_df['children'].fillna(0)
hotel_df['children'].value_counts(dropna=False)
개별 메서드 설명
isna().sum()
컬럼별 결측치 개수를 계산한다.
value_counts(dropna=False)
결측치를 포함해 각 값의 등장 횟수를 계산한다.
fillna(0)
결측치를 0으로 대체한다.
주요 역할
- 결측치 확인
- children 결측치 처리
- 결측치가 실제로 의미하는 바를 반영한 전처리
반환값
- isna().sum() → pandas.Series
- value_counts() → pandas.Series
- fillna() → Series 또는 DataFrame
ai가 추천하는 심화 예제
hotel_df["agent"] = hotel_df["agent"].fillna(-1)
print(hotel_df["agent"].value_counts(dropna=False).head())
# 결과
# 결측치가 -1로 대체된 뒤 agent 분포 확인
11. 조건 필터링과 people 파생 변수 생성
개요
성인, 어린이, 아기 수를 각각 따로 두는 대신,
총 인원수를 나타내는 파생 변수 people을 만든다.
먼저 adults가 0인 경우를 확인하고,
그 다음 adults + children + babies로 people을 만든다.
people이 0이라는 것은 예약에 사람이 한 명도 없는 비정상 데이터이므로 제거한다.
실습 코드
hotel_df[hotel_df['adults'] == 0]
hotel_df['people'] = hotel_df['adults'] + hotel_df['children'] + hotel_df['babies']
hotel_df.head()
hotel_df[hotel_df['people'] == 0]
hotel_df = hotel_df[hotel_df['people'] != 0]
hotel_df[hotel_df['people'] == 0]
주요 역할
- 실제 투숙 인원 수를 하나의 변수로 통합
- 비정상 예약 데이터 제거
- 입력 변수 단순화
ai가 추천하는 심화 예제
hotel_df["people"] = hotel_df["adults"] + hotel_df["children"] + hotel_df["babies"]
print(hotel_df["people"].describe())
# 결과
# 총 인원 수 분포 확인
12. drop() 으로 기존 인원 컬럼 제거
개요
people 파생 변수를 만든 뒤에는 adults, children, babies를 제거한다.
이렇게 하면 중복 정보가 줄어들고 입력 공간이 더 단순해진다.
실습 코드
hotel_df.drop(['adults', 'children', 'babies'], axis=1, inplace=True)
주요 역할
- 파생 변수로 대체된 기존 컬럼 제거
- 중복 정보 감소
- 모델 입력 단순화
ai가 추천하는 심화 예제
reduced_df = hotel_df.drop(["adults", "children", "babies"], axis=1)
print(reduced_df.head())
# 결과
# 기존 인원 컬럼이 제거된 데이터프레임 출력
13. total_nights 파생 변수 생성
개요
숙박일은 주중 숙박일과 주말 숙박일로 나뉘어 있다.
실습에서는 이를 합쳐 총 숙박일 수를 나타내는 total_nights를 만든다.
실습 코드
hotel_df['total_nights'] = hotel_df['stays_in_week_nights'] + hotel_df['stays_in_weekend_nights']
hotel_df.head()
hotel_df.drop(['stays_in_week_nights', 'stays_in_weekend_nights'], axis=1, inplace=True)
hotel_df.info()
주요 역할
- 주중 / 주말 숙박일 통합
- 총 체류 기간 정보 생성
- 입력 변수 단순화
ai가 추천하는 심화 예제
print(hotel_df["total_nights"].describe())
# 결과
# 총 숙박일 분포 확인
14. map() 과 계절 파생 변수 생성
개요
arrival_date_month는 월 이름으로 저장되어 있지만,
모델링과 해석을 위해 이를 계절 범주로 바꾸는 것이 더 유용할 수 있다.
실습에서는 봄, 여름, 가을, 겨울 기준으로 월을 다시 매핑하여 season 변수를 만든다.
- 3, 4, 5 → spring
- 6, 7, 8 → summer
- 9, 10, 11 → fall
- 12, 1, 2 → winter
이 과정은 월 자체보다 더 큰 주기적 패턴을 반영할 수 있게 해준다.
실습 코드
# season 파생변수
# arrival_date_month에 따라 아래와 같이 값을 저장
# 12, 1, 2: winter
# 3, 4, 5: spring
# 6, 7, 8: summer
# 9, 10, 11: fall
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)
map() 의 역할
map()은 기존 값과 새로운 값을 대응시켜 변환하는 메서드이다.
여기서는 월 정보를 계절 정보로 바꾸는 데 사용된다.
주요 역할
- 월 정보를 계절 범주로 변환
- 계절성 반영
- 월 컬럼보다 더 압축된 범주형 정보 생성
ai가 추천하는 심화 예제
season_map = {
"January": "winter", "February": "winter", "March": "spring",
"April": "spring", "May": "spring", "June": "summer",
"July": "summer", "August": "summer", "September": "fall",
"October": "fall", "November": "fall", "December": "winter"
}
hotel_df["season"] = hotel_df["arrival_date_month"].map(season_map)
print(hotel_df[["arrival_date_month", "season"]].head())
# 결과
# 월 이름과 대응되는 계절 출력
15. 객실 일치 여부 파생 변수와 astype(int)
개요
예약한 객실 유형과 실제 배정된 객실 유형이 같은지 여부는 고객 경험과 취소 여부에 영향을 줄 수 있다.
실습에서는 reserved_room_type과 assigned_room_type이 같은지 비교해서
expected_room_type이라는 이진 변수로 만든다.
비교 결과는 True 또는 False가 나오므로,
astype(int)를 사용해 1과 0으로 바꾼다.
실습 코드
hotel_df['expected_room_type'] = (hotel_df['reserved_room_type'] == hotel_df['assigned_room_type']).astype(int)
hotel_df.head()
hotel_df.drop(['reserved_room_type', 'assigned_room_type'], axis=1, inplace=True)
astype(int) 의 역할
불리언 값을 정수형으로 바꾼다.
- True → 1
- False → 0
주요 역할
- 예약 객실과 실제 객실의 일치 여부를 하나의 feature로 압축
- 문자열 비교 결과를 숫자로 변환
- 모델 입력 가능한 형태로 변환
ai가 추천하는 심화 예제
match_col = (hotel_df["reserved_room_type"] == hotel_df["assigned_room_type"]).astype(int)
print(match_col.value_counts())
# 결과
# 객실 유형이 일치하는 예약과 그렇지 않은 예약 개수 출력
16. cancel_rate 파생 변수와 fillna(-1)
개요
이전 취소 횟수와 이전 비취소 예약 횟수를 따로 쓰는 대신,
그 비율을 하나의 변수로 압축해서 예약 취소 이력을 표현할 수 있다.
실습에서는 다음과 같은 방식으로 cancel_rate를 계산한다.
[
cancel_rate = \frac{previous_cancellations}{previous_cancellations + previous_bookings_not_canceled}
]
분모가 0인 경우에는 결과가 NaN이 되므로,
이 값은 fillna(-1)로 채운다.
여기서 -1은 실제 비율이 아니라,
이전 예약 이력이 전혀 없는 경우를 구분하기 위한 특별한 표식으로 볼 수 있다.
실습 코드
hotel_df['cancel_rate'] = hotel_df['previous_cancellations'] / (hotel_df['previous_cancellations'] + hotel_df['previous_bookings_not_canceled'])
hotel_df.head()
hotel_df['cancel_rate']
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)
주요 역할
- 이전 예약 이력을 하나의 비율 변수로 요약
- 과거 취소 성향을 수치화
- 분모가 0인 경우를 특수값으로 구분
ai가 추천하는 심화 예제
cancel_rate = hotel_df["previous_cancellations"] / (
hotel_df["previous_cancellations"] + hotel_df["previous_bookings_not_canceled"]
)
cancel_rate = cancel_rate.fillna(-1)
print(cancel_rate.head())
# 결과
# 이전 예약 이력을 반영한 취소율 변수 출력
17. value_counts(), fillna() 로 agent 와 company 처리
개요
agent와 company 컬럼에는 결측치가 존재한다.
이 실습에서는 결측치를 0이나 평균값으로 채우지 않고, -1로 채운다.
이 방식은 결측을 단순 누락이 아니라
예약 대행사나 회사 정보가 없음을 나타내는 별도 범주처럼 다루겠다는 의미에 가깝다.
실습 코드
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)
hotel_df.isna().sum()
주요 역할
- agent, company 결측치 처리
- 결측을 별도 범주처럼 반영
- 이후 수치형 또는 범주형 처리 안정화
ai가 추천하는 심화 예제
hotel_df["company"] = hotel_df["company"].fillna(-1)
print(hotel_df["company"].value_counts(dropna=False).head())
# 결과
# 결측치가 -1로 대체된 뒤 company 분포 확인
18. select_dtypes(), nunique(), drop() 으로 고유값 많은 컬럼 정리
개요
실습에서는 숫자가 아닌 컬럼을 자동으로 찾아 각 컬럼의 고유값 개수를 확인한다.
고유값이 지나치게 많은 컬럼은 원-핫 인코딩 시 차원을 크게 늘릴 수 있다.
이 때문에 meal, country, reservation_status_date 같은 컬럼은 제거한다.
특히 reservation_status_date는 날짜 문자열이고,
country는 국가 종류가 많으며,
meal도 경우에 따라 직접적인 예측 기여보다 차원 증가 부담이 더 클 수 있다.
실습 코드
hotel_df.select_dtypes(exclude=['number']).columns.tolist()
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)
주요 역할
- 범주형 컬럼 자동 탐색
- 고유값 개수 점검
- 차원 증가 위험이 큰 컬럼 제거
ai가 추천하는 심화 예제
cat_cols = hotel_df.select_dtypes(exclude=['number']).columns.tolist()
for col in cat_cols:
print(col, hotel_df[col].nunique())
# 결과
# 범주형 컬럼별 고유값 개수 출력
19. pd.get_dummies()
개요
문자열 범주형 변수는 대부분의 머신러닝 모델에 그대로 넣을 수 없다.
따라서 각 범주를 0과 1의 더미 변수로 바꾸는 원-핫 인코딩이 필요하다.
실습에서는 숫자가 아닌 컬럼 전체를 한 번에 인코딩하고,
drop_first=True를 사용해 기준 범주 하나를 제거한다.
이렇게 하면 다중공선성 문제를 다소 줄일 수 있다.
실습 코드
hotel_df = pd.get_dummies(hotel_df, columns=hotel_df.select_dtypes(exclude=['number']).columns.tolist(), drop_first=True)
hotel_df.head()
주요 역할
- 범주형 데이터를 수치형으로 변환
- 분류 모델 입력 가능 상태로 변경
- 기준 범주 제거로 변수 수를 조금 줄임
반환값
is_canceled lead_time arrival_date_year arrival_date_week_number arrival_date_day_of_month is_repeated_guest booking_changes agent company days_in_waiting_list ... deposit_type_Non Refund deposit_type_Refundable customer_type_Group customer_type_Transient customer_type_Transient-Party reservation_status_Check-Out reservation_status_No-Show season_spring season_summer season_winter
0 0 342 2015 27 1 0 3 -1.0 -1.0 0 ... False False False True False True False False True False
2 0 7 2015 27 1 0 0 -1.0 -1.0 0 ... False False False True False True False False True False
3 0 13 2015 27 1 0 0 304.0 -1.0 0 ... False False False True False True False False True False
4 0 14 2015 27 1 0 0 240.0 -1.0 0 ... False False False True False True False False True False
5 0 14 2015 27 1 0 0 240.0 -1.0 0 ... False False False True False True False False True False
5 rows × 39 columns
ai가 추천하는 심화 예제
encoded_df = pd.get_dummies(
hotel_df,
columns=["hotel", "season"],
drop_first=True
)
print(encoded_df.head())
# 결과
# 지정한 범주형 컬럼이 더미 변수로 변환됨
20. train_test_split()
개요
train_test_split()은 데이터를 학습용과 테스트용으로 나누는 함수이다.
분류 모델은 학습 데이터로 패턴을 배우고,
훈련에 사용하지 않은 테스트 데이터로 일반화 성능을 확인해야 한다.
실습에서는 test_size를 0.3으로 두고, random_state를 고정해 실험을 재현 가능하게 만든다.
실습 코드
from sklearn.model_selection import train_test_split
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=10
)
X_train.shape, y_train.shape
X_test.shape, y_test.shape
주요 역할
- 학습셋과 테스트셋 분리
- 모델 일반화 성능 평가
- 재현 가능한 실험 조건 설정
ai가 추천하는 심화 예제
print(X_train.shape)
print(X_test.shape)
print(y_train.mean(), y_test.mean())
# 결과
# 데이터 크기와 취소 비율 확인
21. 로지스틱 회귀 이론
개요
로지스틱 회귀는 이진 분류와 다중 분류 문제를 해결하기 위한 선형 분류 모델이다.
입력 변수의 선형 결합 값을 그대로 쓰지 않고,
시그모이드 함수를 적용해 0과 1 사이의 확률로 변환한다.
시그모이드 함수는 다음과 같다.
[
\sigma(z) = \frac{1}{1 + e^{-z}}
]
이 값은 특정 샘플이 양성 클래스에 속할 확률로 해석할 수 있다.
예를 들어 예약 취소 확률이 0.8이라면,
해당 예약이 취소될 가능성이 높다고 볼 수 있다.
22. 규제와 max_iter
개요
로지스틱 회귀는 규제를 함께 사용해 과적합을 방지하는 경우가 많다.
규제는 모델의 가중치가 지나치게 커지는 것을 막아
복잡도를 줄이고 새로운 데이터에도 잘 일반화하도록 돕는다.
또한 최적화를 반복적으로 수행하는 모델이므로,
반복 횟수가 부족하면 ConvergenceWarning이 발생할 수 있다.
이 경고는 최적해를 충분히 찾지 못했다는 뜻이다.
이를 줄이기 위해 max_iter 값을 늘린다.
실습에서는 기본 LogisticRegression()도 사용하고,
스케일링 후에는 max_iter=1000으로 늘려 안정적으로 학습시킨다.
23. StandardScaler 와 데이터 스케일링
개요
로지스틱 회귀는 입력 변수의 스케일 차이에 영향을 많이 받는 모델이다.
어떤 변수는 수천 단위이고 어떤 변수는 0~1 범위라면,
최적화 과정에서 특정 변수에 지나치게 끌릴 수 있다.
이 문제를 줄이기 위해 표준화를 사용한다.
표준화는 각 변수를 평균 0, 표준편차 1로 변환한다.
[
z = \frac{x - \mu}{\sigma}
]
실습에서는 StandardScaler를 이용해 입력 데이터를 스케일링한다.
주의할 점은 테스트 데이터에는 fit_transform이 아니라 transform만 적용하는 것이 일반적으로 맞다.
실무에서는 학습 데이터에서 구한 평균과 표준편차를 그대로 테스트 데이터에 적용해야 데이터 누수를 막을 수 있다.
실습 코드
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
x_train_scaled = scaler.fit_transform(X_train)
x_test_scaled = scaler.fit_transform(X_test)
x_train_scaled
주요 역할
- 입력 변수 스케일 맞춤
- 로지스틱 회귀 최적화 안정화
- 수렴 속도 개선 가능
ai가 추천하는 심화 예제
scaler = StandardScaler()
x_train_scaled = scaler.fit_transform(X_train)
x_test_scaled = scaler.transform(X_test)
print(x_train_scaled[:3])
# 결과
# 표준화된 학습 데이터 일부 출력
24. LogisticRegression(), fit(), predict()
개요
LogisticRegression()은 로지스틱 회귀 분류기 객체를 생성하고,
fit()은 학습 데이터를 사용해 모델을 학습시키며,
predict()는 최종 클래스 예측값을 반환한다.
실습 코드
from sklearn.linear_model import LogisticRegression
lr = LogisticRegression()
lr.fit(X_train, y_train)
스케일링 후:
model = LogisticRegression(max_iter=1000)
model.fit(x_train_scaled, y_train)
pred = model.predict(x_test_scaled)
각 메서드의 역할
LogisticRegression()
이진 분류 모델 생성
fit(X, y)
입력과 정답을 이용해 가중치 학습
predict(X)
각 샘플의 최종 클래스 0 또는 1 예측
ai가 추천하는 심화 예제
model = LogisticRegression(max_iter=1000)
model.fit(x_train_scaled, y_train)
print(model.coef_)
print(model.intercept_)
# 결과
# 가중치와 절편 출력
25. accuracy_score()
개요
accuracy_score()는 전체 예측 중 몇 개를 맞췄는지를 비율로 나타내는 지표이다.
[
Accuracy = \frac{정확히\ 맞춘\ 샘플\ 수}{전체\ 샘플\ 수}
]
직관적이고 해석하기 쉽지만,
클래스 불균형이 심할 때는 Accuracy만으로 모델을 평가하면 오해가 생길 수 있다.
실습 코드
from sklearn.metrics import accuracy_score
accuracy_score(y_test, pred)
hotel_df['is_canceled'].value_counts()
주요 역할
- 전체적인 정답률 확인
- 클래스 불균형 여부와 함께 해석
ai가 추천하는 심화 예제
acc = accuracy_score(y_test, pred)
print(acc)
# 결과
# 정확도 출력
26. 혼동 행렬과 confusion_matrix()
개요
혼동 행렬은 실제값과 예측값의 관계를 네 가지 경우로 나눠 보여준다.
- TP: 실제 양성, 예측 양성
- TN: 실제 음성, 예측 음성
- FP: 실제 음성, 예측 양성
- FN: 실제 양성, 예측 음성
이진 분류에서는 단순 정확도보다 혼동 행렬을 함께 보는 것이 훨씬 중요하다.
실습 코드
from sklearn.metrics import confusion_matrix
confusion_matrix(y_test, pred)
주요 역할
- 어떤 오류가 많이 발생하는지 확인
- FP와 FN의 균형 확인
- 정밀도, 재현율, F1 해석의 기반 제공
반환값
array([[22155, 0],
[ 0, 12707]])
ai가 추천하는 심화 예제
cm = confusion_matrix(y_test, pred)
print(cm)
# 결과
# [[TN FP]
# [FN TP]]
27. precision_score(), recall_score(), f1_score()
개요
정밀도, 재현율, F1 점수는 Accuracy가 놓칠 수 있는 부분을 보완하는 대표적인 분류 지표이다.
정밀도
모델이 양성이라고 예측한 것 중 실제 양성의 비율
[
Precision = \frac{TP}{TP + FP}
]
재현율
실제 양성 중 모델이 양성으로 맞춘 비율
[
Recall = \frac{TP}{TP + FN}
]
F1 점수
정밀도와 재현율의 조화평균
[
F1 = 2 \cdot \frac{Precision \cdot Recall}{Precision + Recall}
]
취소 예측 문제에서는 어떤 오류가 더 중요한지에 따라
정밀도를 우선할지, 재현율을 우선할지 달라질 수 있다.
실습 코드
from sklearn.metrics import precision_score, recall_score, f1_score
print(precision_score(y_test, pred))
print(recall_score(y_test, pred))
print(f1_score(y_test, pred))
주요 역할
- 양성 클래스 예측 품질 점검
- 정확도 외의 세부 성능 확인
- 비즈니스 목적에 맞는 임계값 조정 방향 판단
반환값
1.0
1.0
1.0
ai가 추천하는 심화 예제
precision = precision_score(y_test, pred)
recall = recall_score(y_test, pred)
f1 = f1_score(y_test, pred)
print(precision, recall, f1)
# 결과
# 정밀도, 재현율, F1 점수 출력
28. coef_, intercept_
개요
로지스틱 회귀는 선형 결합을 기반으로 작동하므로,
학습 후 각 변수의 가중치와 절편을 확인할 수 있다.
- coef_ 는 각 feature의 기울기 역할
- intercept_ 는 절편 역할
가중치가 양수이면 해당 변수가 커질수록 양성 클래스 쪽으로 기울고,
음수이면 반대로 음성 클래스 쪽으로 기울 가능성이 있다.
실습 코드
model.coef_
model.intercept_
주요 역할
- 변수 영향 방향 확인
- 모델 해석 가능성 제공
ai가 추천하는 심화 예제
print(model.coef_.shape)
print(model.intercept_)
# 결과
# 계수 배열 형태와 절편 출력
29. predict_proba() 와 확률 기반 예측
개요
predict()는 최종 클래스만 반환하지만,
predict_proba()는 각 클래스에 속할 확률을 반환한다.
이진 분류에서는 다음과 같은 형태가 나온다.
- 첫 번째 값: 클래스 0일 확률
- 두 번째 값: 클래스 1일 확률
실습에서는 취소할 확률만 따로 뽑아 확인한다.
실습 코드
proba = model.predict_proba(x_test_scaled)
proba
proba = model.predict_proba(x_test_scaled)[:, 1]
proba
주요 역할
- 단순 클래스 예측을 넘어 확률적 판단 가능
- 임계값 조정의 기반 제공
- ROC AUC 계산에 활용 가능
반환값
array([9.99730467e-01, 1.42358244e-04, 2.03425814e-04, ...,
1.97182673e-04, 4.97684677e-05, 9.99982951e-01])
ai가 추천하는 심화 예제
proba = model.predict_proba(x_test_scaled)[:5]
print(proba)
# 결과
# 앞 5개 샘플에 대한 각 클래스 확률 출력
30. 임계값 조정과 astype(int)
개요
기본적으로 이진 분류 모델은 양성 확률이 0.5 이상이면 1로 예측한다.
하지만 상황에 따라 이 기준을 높이거나 낮출 수 있다.
실습에서는 threshold를 매우 높게 설정해서
취소라고 판단하는 기준을 더 엄격하게 만든다.
threshold = 0.9999
pred = (proba >= threshold).astype(int)
여기서 astype(int)는 True, False를 1, 0으로 바꾸는 역할을 한다.
임계값을 높이면 일반적으로 다음 경향이 생긴다.
- 정밀도는 높아질 수 있음
- 재현율은 낮아질 수 있음
즉, 정말 확실한 경우만 취소라고 예측하겠다는 전략이다.
실습 코드
threshold = 0.9999
pred = (proba >= threshold).astype(int)
pred
주요 역할
- 양성 판정 기준 직접 제어
- 정밀도 / 재현율 trade-off 조정
- 비즈니스 목적에 맞는 예측 전략 설계
ai가 추천하는 심화 예제
threshold = 0.7
pred_custom = (proba >= threshold).astype(int)
print(pred_custom[:10])
# 결과
# 임계값 0.7 기준 예측 결과 출력
31. RandomForestClassifier()
개요
랜덤 포레스트는 여러 개의 결정트리를 결합한 앙상블 분류 모델이다.
각 트리는 무작위 데이터 샘플과 일부 특성을 사용해 학습되고,
최종 예측은 다수결로 결정된다.
장점은 다음과 같다.
- 비선형 패턴 학습 가능
- 과적합 완화
- 특성 중요도 제공
- 스케일링 민감도가 상대적으로 낮음
실습 코드
from sklearn.ensemble import RandomForestClassifier
rf = RandomForestClassifier(random_state=10)
rf.fit(X_train, y_train)
pred1 = rf.predict(X_test)
pred1
주요 역할
- 로지스틱 회귀와 다른 구조의 분류 모델 비교
- 비선형 관계 반영
- 확률 기반 출력 가능
ai가 추천하는 심화 예제
rf = RandomForestClassifier(n_estimators=200, random_state=10)
rf.fit(X_train, y_train)
pred_rf = rf.predict(X_test)
print(accuracy_score(y_test, pred_rf))
# 결과
# 랜덤 포레스트 정확도 출력
32. RandomForestClassifier 의 predict_proba()
개요
랜덤 포레스트도 predict_proba()를 통해 각 클래스 확률을 반환할 수 있다.
각 트리가 특정 클래스를 얼마나 지지하는지를 종합해
최종 확률 형태로 제공한다.
실습 코드
proba1 = rf.predict_proba(X_test)
proba1
proba1[0]
proba1[:, 1]
주요 역할
- 각 예약이 취소될 확률 계산
- 확률 기반 임계값 조정 가능
- ROC AUC 분석에 활용 가능
ai가 추천하는 심화 예제
proba_rf = rf.predict_proba(X_test)[:, 1]
print(proba_rf[:10])
# 결과
# 취소 클래스 확률 상위 10개 출력
33. classification_report()
개요
classification_report()는 정밀도, 재현율, F1 점수, support를 한 번에 요약해 보여주는 함수이다.
클래스별 성능을 종합적으로 보고 싶을 때 매우 편리하다.
실습 코드
from sklearn.metrics import classification_report, roc_auc_score
print(classification_report(y_test, pred))
주요 역할
- 클래스별 정밀도, 재현율, F1 한 번에 확인
- macro average, weighted average 확인
- 모델 성능 요약
반환값
문자열 형태의 보고서가 출력된다.
ai가 추천하는 심화 예제
report = classification_report(y_test, pred1)
print(report)
# 결과
# 랜덤 포레스트 분류 성능 요약 출력
34. ROC AUC Score 와 roc_auc_score()
개요
ROC AUC는 이진 분류 모델의 확률 예측 능력을 평가하는 대표 지표이다.
ROC Curve는 다양한 임계값에서의 TPR과 FPR 관계를 그린 곡선이고,
AUC는 그 곡선 아래 면적을 의미한다.
- AUC = 1 이면 완벽한 분류기
- AUC = 0.5 이면 랜덤 추측 수준
- AUC < 0.5 이면 랜덤보다 나쁜 수준
ROC AUC는 양성과 음성 샘플을 얼마나 잘 구분하는지를 나타내는 지표로 이해할 수 있다.
실습 코드
roc_auc_score(y_test, pred)
주요 역할
- 단순 정확도를 넘는 분류 성능 평가
- 임계값 변화 전체를 고려한 평가
- 클래스 구분 능력 수치화
반환값
np.float64(0.6928071141890297)
ai가 추천하는 심화 예제
auc = roc_auc_score(y_test, pred1)
print(auc)
# 결과
# 랜덤 포레스트 기준 ROC AUC 출력
35. roc_curve() 와 ROC Curve 시각화
개요
roc_curve()는 여러 임계값에 대해 FPR, TPR, threshold를 계산하는 함수이다.
- FPR: 거짓 양성 비율
- TPR: 참 양성 비율
실습에서는 이를 이용해 ROC Curve를 직접 그린다.
다만 ROC Curve는 일반적으로 확률값을 넣는 것이 더 적절하다.
클래스 예측값만 넣으면 곡선 정보가 크게 줄어들 수 있다.
실습 코드
import matplotlib.pyplot as plt
from sklearn.metrics._plot.roc_curve import roc_curve
fpr, tpr, thr = roc_curve(y_test, pred)
print(fpr, tpr, thr)
plt.plot(fpr, tpr, label='ROC Curve')
plt.plot([0, 1], [0, 1])
plt.show()
주요 역할
- 임계값에 따른 분류 성능 변화 확인
- 무작위 추측 기준선과 비교
- ROC AUC 해석 보조
반환값

ai가 추천하는 심화 예제
proba_rf = rf.predict_proba(X_test)[:, 1]
fpr, tpr, thr = roc_curve(y_test, proba_rf)
plt.plot(fpr, tpr)
plt.plot([0, 1], [0, 1], linestyle="--")
plt.show()
# 결과
# 확률값 기반 ROC Curve 출력
36. KFold 와 교차 검증
개요
교차 검증은 모델 성능을 더 안정적으로 평가하기 위해 데이터를 여러 번 나누어 검증하는 기법이다.
K-Fold 교차 검증은 데이터를 k개로 나누고,
각 폴드를 한 번씩 검증셋으로 사용하면서 나머지를 학습셋으로 사용한다.
이렇게 하면 특정 한 번의 train/test 분할에 덜 의존하는 평가가 가능하다.
실습 코드
from sklearn.model_selection import KFold
kf = KFold(n_splits=5)
kf
for train_index, test_index in kf.split(range(len(hotel_df))):
print(train_index, test_index, len(train_index), len(test_index))
주요 역할
- 데이터 분할을 여러 번 반복
- 단일 split보다 안정적인 성능 추정
- 검증 편향 완화
주요 파라미터
| 파라미터 | 설명 |
|---|---|
| n_splits | 폴드 개수 |
| shuffle | 섞을지 여부 |
| random_state | 셔플 시 난수 고정 |
ai가 추천하는 심화 예제
kf = KFold(n_splits=5, shuffle=True, random_state=2025)
for fold, (train_idx, test_idx) in enumerate(kf.split(range(len(hotel_df))), start=1):
print(fold, len(train_idx), len(test_idx))
# 결과
# 각 폴드별 학습/검증 크기 출력
37. shuffle=True 와 random_state
개요
기본 KFold는 데이터를 순서대로 나눈다.
하지만 데이터가 특정 순서로 정렬되어 있으면 분할 편향이 생길 수 있다.
이를 줄이기 위해 shuffle=True를 설정하고,
random_state로 셔플 결과를 고정한다.
실습 코드
kf = KFold(n_splits=5, random_state=2025, shuffle=True)
kf
for train_index, test_index in kf.split(range(len(hotel_df))):
print(train_index, test_index, len(train_index), len(test_index))
주요 역할
- 데이터 순서 편향 완화
- 더 무작위적인 폴드 구성
- 재현 가능한 교차 검증 환경 구축
38. iloc 를 이용한 교차 검증용 데이터 분리
개요
KFold가 반환하는 것은 인덱스 배열이므로,
실제 데이터프레임과 시리즈에서 해당 행을 선택하려면 iloc가 필요하다.
iloc는 정수 위치 기반 인덱싱 메서드이다.
실습 코드
acc_list = []
for train_index, test_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_test = X.iloc[test_index]
y_train = y.iloc[train_index]
y_test = y.iloc[test_index]
주요 역할
- 정수 인덱스 배열 기반으로 행 선택
- 각 폴드별 학습/검증 데이터 분리
- 교차 검증 루프 구성
ai가 추천하는 심화 예제
X = hotel_df.drop("is_canceled", axis=1)
y = hotel_df["is_canceled"]
for train_index, test_index in kf.split(range(len(hotel_df))):
X_train = X.iloc[train_index]
X_test = X.iloc[test_index]
print(X_train.shape, X_test.shape)
break
# 결과
# 첫 번째 폴드 기준 학습/검증 데이터 크기 출력
39. 실습 전체 흐름 요약
pd.read_csv()
↓
info() / describe()
↓
sns.displot()
↓
sns.boxplot()
↓
quantile() / IQR 계산
↓
이상치 제거
↓
sns.barplot()
↓
calendar.month_name
↓
isna().sum() / value_counts() / fillna()
↓
people 파생 변수 생성
↓
drop()
↓
total_nights 파생 변수 생성
↓
map() 으로 season 생성
↓
expected_room_type 생성
↓
cancel_rate 생성
↓
select_dtypes() / nunique()
↓
pd.get_dummies()
↓
train_test_split()
↓
StandardScaler()
↓
LogisticRegression()
↓
fit() / predict() / predict_proba()
↓
accuracy_score()
↓
confusion_matrix()
↓
precision_score() / recall_score() / f1_score()
↓
임계값 조정
↓
RandomForestClassifier()
↓
classification_report()
↓
roc_auc_score()
↓
roc_curve()
↓
KFold()
↓
iloc 기반 교차 검증
40. 최종 정리
이 실습은 호텔 예약 취소 예측 문제를 다루는 이진 분류문제로,
단순히 모델 하나를 학습하는 수준을 넘어 분류 문제의 전형적인 전체 흐름을 잘 담고 있다.
핵심적으로 포함된 내용은 다음과 같다.
데이터 이해
- 예약 취소 여부를 예측하기 위해 호텔 유형, 예약 채널, 체류 기간, 인원 수, 고객 이력, 객실 정보 등을 사용한다.
EDA
- lead_time 분포와 이상치를 확인하고
- 유통 채널과 월별 취소율을 시각화한다.
전처리
- children, agent, company 결측치 처리
- people, total_nights, season, expected_room_type, cancel_rate 같은 파생 변수 생성
- 불필요 컬럼 제거
- 범주형 변수 원-핫 인코딩
모델링
- 로지스틱 회귀
- 표준화 적용 로지스틱 회귀
- 랜덤 포레스트 분류기
평가
- Accuracy
- 혼동 행렬
- Precision, Recall, F1
- ROC AUC
- ROC Curve
추가 실험
- 확률 기반 임계값 조정
- K-Fold 교차 검증
'AI 공부' 카테고리의 다른 글
| GPT와 함께 정리한 ACER(Actor-Critic with Experience Replay) (1) | 2026.03.18 |
|---|---|
| GPT와 함께하는 클러스터링 정리 (1) | 2026.03.17 |
| 결정트리를 이용해서 서울 자전거 공유 수요 예측해보기 (0) | 2026.03.16 |
| 선형 회귀 모델을 사용해서 주택 임대료 예측해보기 (0) | 2026.03.16 |
| GPT와 함께하는 A2C(Advantage Actor-Critic) 정리 (1) | 2026.03.16 |