[ML]호텔 예약 취소 예측하기①

포동동·2022년 5월 12일
0
post-thumbnail

첫 번째 ML 프로젝트


AI 부트캠프를 진행하면서 내가 스스로 결정해서 하는 프로젝트가 시작되었다. Section1에서 하던 것도 물론 재밌었지만, 스스로 예측한다는 느낌보다는 주어진 데이터를 가지고 분석하고 "아마도 잘 팔릴걸요....?"하는 느낌이어서 재미가 없었다. 따라서 이번에 시작되는 프로젝트에 조금 더 신경을 써보고 싶다😝 (과제로 푼 문제의 kaggle 점수가 0.044가 나와서 그러는 거 아니다....)



문제 선택📃

Kaggle Dataset 중에서 [Hotel booking demand]
(https://www.kaggle.com/datasets/jessemostipak/hotel-booking-demand)

많이 찾아보고 흥미가 가는 것도 많았지만(예를 들면, 교통, 재정, 패션, 영화 추천, 유튜브 등, 인사) 일단 데이터 찾기가 너무 어려웠다. 데이콘 같은 곳에서 제공해주는 것도 있었지만, feature가 너무 적거나 특별하게 문제 설정하기 어려워보이거나, 아예 데이터를 크롤링 해야하는 곳도 있어서 포기했다....😢

그래서 캐글에서 지금까지 많이 사용된 데이터셋 중에 최근에 관심이 많이 간 이진분류 문제를 선택했다. 만약 시간이 남는다면 데이콘에 주차 수요 예측도 해보고 싶다. (회귀인가...?)



문제 설정🔑

어느 호텔은 예약을 취소건이 많은 것이 운영상의 큰 문제로 지적되어왔다.
이를 해결하기위해, 예약을 취소할 확률이 높은 고객을 선별해 예약을 취소 전에 예약 확인 전화를 건다던가 할인 쿠폰이나 주변 관광지에 대한 정보가 담긴 웹페이지 등을 제공함에 따라, 예약 취소를 줄이려고 한다. 그를 위한 ML 모델을 구축하자.

문제 설정은 이렇게 하였다. 실제로 EDA를 해보니, 투숙객 중 예약을 취소하는 비율은 (전체 고객 중) 37%나 되었다😮😨

저녁으로 양꼬치까지 신나게 먹고 왔으니 신나게 EDA를 진행해주었다🍗



데이터 꼼꼼히 살펴보기🔎

import pandas as pd

df = pd.read_csv('/content/drive/MyDrive/Colab Notebooks/kaggle/hotel_demand/hotel_bookings.csv')

df.shape
#(119390, 32)

shape을 확인해보니 약 12만개의 행과 32개의 feature(이 중엔 target이 1개 있으니 정확하게는 31개겠지만) 정말 내가 찾던 풍부한 정보를 담고 있는 데이터셋이었다....!

df.info()

 #   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)




중간중간에 결측치도 보이고, 데이터 타입들도 확인해주었다.
더 자세하게 살펴보았다.


[(x, df[x].isnull().sum()) for x in df.columns if df[x].isnull().any()]
#[('children', 4), ('country', 488), ('agent', 16340), ('company', 112593)]

df.nunique().sort_values()

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

결측치가 어디있는지 확인하고, unique값이 각 컬럼별로 몇 개나 있나 확인해보았다.
우선 각 컬럼별로 어떤 걸 의미하는지를 모르기 때문에 정리해보았다.


'hotel' : city인가 resort인가
'is_canceled' : **target값**
'lead_time' : 도착까지 남은 일수
'arrival_date_year' : 도착한 연도
'arrival_date_month' : 도착한 달
'arrival_date_week_number' : 몇 번째 주에 도착한지(1년 중에)
'arrival_date_day_of_month' : 도착한 일
'stays_in_weekend_nights' : 주말 투숙으로 예약한 건수
'stays_in_week_nights' : 주중 투숙으로 예약한 건수
'adults', 'children', 'babies', : 각각 몇 명
'meal' : 식사 예약(BB:방과 조식, HB:조식+중식or석식, FB:조식+중식+석식, SC:아무것도 예약X)
'country' : 국적
'market_segment' : 통한 대리점의 형태
'distribution_channel' : 예약한 방법(대리점 or 직접)
'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' : 예약한 날짜

컬럼을 살펴본 뒤에는, 결측값을 처리하고 새로운 컬럼을 만들었다.

#불필요한 행 제거
df = df[df['adr'] > 0.00]
df = df[df['adults'] != 0]

#결측값들을 처리
#children, country 결측치는 얼마 없으니 삭제
df = df.dropna(subset=['children'])
df = df.dropna(subset=['country'])

#agent, company는 예약을 direct로 하면 nan으로 처리되는 듯하니 그대로 놔두기

df.shape
#(116711, 32)

이렇게 하니 아래와 같은 shape이 되었다.
그리고 새로운 컬럼을 만들었다.

#새로운 피쳐 생성1(예약을 취소 안 하고 진짜 왔을 경우의 총 숙박객 수)
df['final_total_guests'] = df['adults'] + df['children'] + df['babies']
#예약을 취소한 사람은 final guests가 없으니까 0으로 처리
df.loc[df['is_canceled']==1, 'final_total_guests'] = 0

#새로운 피쳐 생성2(투숙객당 평균 비용)
df['price_per_guest'] = round(df['adr'] / df['final_total_guests'],4)

df.shape
#(116711, 34)
#원래 shape (119390, 32)


이것저것 구글링해서 공부하는 과정이 생각보다 시간이 많이 걸리고 나의 이 느린 타자에 답답도 했지만 그래도 순수하게 나의 힘만으로 데이터를 처리했다는 것에 의의를 두고(...!) 오늘은 이만 마치려 한다🤗🙉⭕

profile
완료주의

0개의 댓글