1 Giới thiệu tổng quan về bộ dữ liệu

Trong bối cảnh ngành khách sạn ngày càng cạnh tranh, việc ra quyết định dựa trên dữ liệu đã trở thành yếu tố sống còn để tối ưu hóa vận hành, tối đa hóa doanh thu và nâng cao trải nghiệm khách hàng. Để phục vụ cho mục đích nghiên cứu và phân tích các yếu tố ảnh hưởng đến hành vi đặt phòng, bài luận này sử dụng bộ dữ liệu công khai có tên “Hotel Booking Demand”.

Bộ dữ liệu này được cung cấp bởi Nuno Antonio, Ana Almeida, và Luis Nunes trong bài báo khoa học “Hotel booking demand datasets” đăng trên tạp chí Data in Brief, và đã được phổ biến rộng rãi trên các nền tảng khoa học dữ liệu như Kaggle và TidyTuesday. Dữ liệu ghi lại các giao dịch đặt phòng thực tế tại hai khách sạn: một khách sạn nghỉ dưỡng (Resort Hotel) và một khách sạn thành phố (City Hotel), đều tọa lạc tại Bồ Đào Nha.

hotels <- read.csv("D:/R/hotel_bookings.csv/hotel_bookings.csv")

2 Những thông tin cơ bản

2.1 Tổng quan về dữ liệu

str(hotels)
## 'data.frame':    119390 obs. of  32 variables:
##  $ hotel                         : chr  "Resort Hotel" "Resort Hotel" "Resort Hotel" "Resort Hotel" ...
##  $ is_canceled                   : int  0 0 0 0 0 0 0 0 1 1 ...
##  $ lead_time                     : int  342 737 7 13 14 14 0 9 85 75 ...
##  $ arrival_date_year             : int  2015 2015 2015 2015 2015 2015 2015 2015 2015 2015 ...
##  $ arrival_date_month            : chr  "July" "July" "July" "July" ...
##  $ arrival_date_week_number      : int  27 27 27 27 27 27 27 27 27 27 ...
##  $ arrival_date_day_of_month     : int  1 1 1 1 1 1 1 1 1 1 ...
##  $ stays_in_weekend_nights       : int  0 0 0 0 0 0 0 0 0 0 ...
##  $ stays_in_week_nights          : int  0 0 1 1 2 2 2 2 3 3 ...
##  $ adults                        : int  2 2 1 1 2 2 2 2 2 2 ...
##  $ children                      : int  0 0 0 0 0 0 0 0 0 0 ...
##  $ babies                        : int  0 0 0 0 0 0 0 0 0 0 ...
##  $ meal                          : chr  "BB" "BB" "BB" "BB" ...
##  $ country                       : chr  "PRT" "PRT" "GBR" "GBR" ...
##  $ market_segment                : chr  "Direct" "Direct" "Direct" "Corporate" ...
##  $ distribution_channel          : chr  "Direct" "Direct" "Direct" "Corporate" ...
##  $ is_repeated_guest             : int  0 0 0 0 0 0 0 0 0 0 ...
##  $ previous_cancellations        : int  0 0 0 0 0 0 0 0 0 0 ...
##  $ previous_bookings_not_canceled: int  0 0 0 0 0 0 0 0 0 0 ...
##  $ reserved_room_type            : chr  "C" "C" "A" "A" ...
##  $ assigned_room_type            : chr  "C" "C" "C" "A" ...
##  $ booking_changes               : int  3 4 0 0 0 0 0 0 0 0 ...
##  $ deposit_type                  : chr  "No Deposit" "No Deposit" "No Deposit" "No Deposit" ...
##  $ agent                         : chr  "NULL" "NULL" "NULL" "304" ...
##  $ company                       : chr  "NULL" "NULL" "NULL" "NULL" ...
##  $ days_in_waiting_list          : int  0 0 0 0 0 0 0 0 0 0 ...
##  $ customer_type                 : chr  "Transient" "Transient" "Transient" "Transient" ...
##  $ adr                           : num  0 0 75 75 98 ...
##  $ required_car_parking_spaces   : int  0 0 0 0 0 0 0 0 0 0 ...
##  $ total_of_special_requests     : int  0 0 0 0 1 1 0 1 1 0 ...
##  $ reservation_status            : chr  "Check-Out" "Check-Out" "Check-Out" "Check-Out" ...
##  $ reservation_status_date       : chr  "2015-07-01" "2015-07-01" "2015-07-02" "2015-07-02" ...

2.2 Số biến và số quan sát

dim(hotels)
## [1] 119390     32

Kết quả trên cho thấy bộ dữ liệu có 119390 quan sát và 32 biến.

2.3 Kiểm tra dữ liệu trùng lặp

so_dong_trung_lap <- sum(duplicated(hotels))
print(so_dong_trung_lap)
## [1] 31994

Kết quả cho thấy có 31994 dòng dữ liệu bị trùng lặp.

2.4 Kiểm tra dữ liệu bị thiếu

colSums(is.na(hotels))
##                          hotel                    is_canceled 
##                              0                              0 
##                      lead_time              arrival_date_year 
##                              0                              0 
##             arrival_date_month       arrival_date_week_number 
##                              0                              0 
##      arrival_date_day_of_month        stays_in_weekend_nights 
##                              0                              0 
##           stays_in_week_nights                         adults 
##                              0                              0 
##                       children                         babies 
##                              4                              0 
##                           meal                        country 
##                              0                              0 
##                 market_segment           distribution_channel 
##                              0                              0 
##              is_repeated_guest         previous_cancellations 
##                              0                              0 
## previous_bookings_not_canceled             reserved_room_type 
##                              0                              0 
##             assigned_room_type                booking_changes 
##                              0                              0 
##                   deposit_type                          agent 
##                              0                              0 
##                        company           days_in_waiting_list 
##                              0                              0 
##                  customer_type                            adr 
##                              0                              0 
##    required_car_parking_spaces      total_of_special_requests 
##                              0                              0 
##             reservation_status        reservation_status_date 
##                              0                              0

Kết quả cho thấy cột Children có chứa dữ liệu bị thiếu.

2.5 Làm sạch dữ liệu

analyzed <- hotels %>%
  distinct() %>%
  mutate(children = replace_na(children, 0)) %>%
  filter(!(adults == 0 & children == 0 & babies == 0), adr > 0)

Kết quả đã loại bỏ các giá trị trùng lặp, thay thế giá trị bị thiếu và loại bỏ những dòng dữ liệu vô lý.

2.6 Ý nghĩa của các biến

Tên Biến Ý Nghĩa Kiểu Dữ liệu Ví dụ Giá trị
hotel Loại khách sạn (Resort Hotel hoặc City Hotel). character Resort Hotel, City Hotel
lead_time Số ngày tính từ lúc đặt phòng đến ngày khách nhận phòng. integer 7, 13, 14
arrival_date_month Tháng khách đến nhận phòng. character July, August, September
country Quốc gia của khách hàng (định dạng mã ISO 3166-1 alpha-3). character GBR, PRT, USA
market_segment Kênh thị trường tạo ra lượt đặt phòng (ví dụ: TA/TO - Đại lý du lịch). character Direct, Corporate, Online TA
is_canceled Biến nhị phân cho biết lượt đặt phòng có bị hủy không (1 = Đã hủy, 0 = Không hủy). integer 0, 1
adr Giá phòng trung bình mỗi ngày (Average Daily Rate = Tổng doanh thu phòng / Tổng số đêm ở). numeric 75, 98, 107
arrival_date_year Năm khách đến nhận phòng. integer 2015, 2016, 2017
stays_in_weekend_nights Số đêm khách ở vào các ngày cuối tuần (Thứ 7 hoặc Chủ Nhật). integer 0, 1, 2
stays_in_week_nights Số đêm khách ở vào các ngày trong tuần (Thứ 2 - Thứ 6). integer 1, 2, 3
children Số lượng trẻ em. integer 0, 1, 2
babies Số lượng em bé. integer 0, 1, 2
is_repeated_guest Biến nhị phân cho biết khách có phải là khách đã từng ở trước đây không (1 = Có, 0 = Không). integer 0, 1
reserved_room_type Mã loại phòng đã được đặt. character A, C, D
deposit_type Loại tiền đặt cọc (No Deposit: Không cọc; Non Refund: Cọc không hoàn lại; Refundable: Cọc hoàn lại). character No Deposit, Refundable, Non Refund
required_car_parking_spaces Số lượng chỗ đỗ xe được khách yêu cầu. integer 0, 1, 2
total_of_special_requests Tổng số các yêu cầu đặc biệt được khách đưa ra (ví dụ: phòng tầng cao, giường phụ). integer 0, 1, 3

3 Phân tổ các biến

3.1 Rủi ro hủy phòng theo thời gian đặt trước

3.1.1 Xem xét rủi ro hủy phòng thay đổi như thế nào theo các khoảng thời gian đặt trước khác nhau

ruirohuyphong <- analyzed %>%
  mutate(lead_time_group = case_when(
    lead_time <= 7 ~ "Đặt trong 1 tuần",
    lead_time <= 90 ~ "Đặt trong 3 tháng",
    TRUE ~ "Đặt rất sớm (>3 tháng)"
  )) %>%
  group_by(lead_time_group) %>%
  summarise(ty_le_huy = mean(is_canceled)) %>%
  mutate(lead_time_group = factor(lead_time_group, levels = c("Đặt trong 1 tuần", "Đặt trong 3 tháng", "Đặt rất sớm (>3 tháng)"))) %>%
  arrange(lead_time_group)
kable(ruirohuyphong)
lead_time_group ty_le_huy
Đặt trong 1 tuần 0.0843032
Đặt trong 3 tháng 0.2948239
Đặt rất sớm (>3 tháng) 0.3702148

Kết quả sẽ cho thấy nhóm “Đặt rất sớm (>3 tháng)” có tỷ lệ hủy phòng cao nhất, và tỷ lệ này giảm dần ở các nhóm đặt gần ngày hơn. Điều này chứng tỏ rằng các cam kết đặt phòng dài hạn có độ chắc chắn thấp. Dựa vào đây, bộ phận kinh doanh có thể thiết kế các loại giá khác nhau: giá linh hoạt (cho phép hủy) cho nhóm đặt gần ngày, và giá tiết kiệm (không hoàn hủy) cho nhóm đặt rất sớm để giảm thiểu rủi ro.

3.1.2 Mối liên hệ quan sát được giữa các nhóm thời gian đặt phòng và việc hủy phòng có ý nghĩa thống kê không?

Giả thuyết H₀: Không có mối liên hệ nào giữa nhóm thời gian đặt phòng và việc hủy phòng. Chúng độc lập với nhau.

Công cụ: Kiểm định Chi-bình phương (Chi-squared Test), vì cả hai đều là biến phân loại.

lead_time_data_for_test <- analyzed %>%
  mutate(lead_time_group = case_when(
    lead_time <= 7 ~ "Trong 1 tuần",
    lead_time <= 90 ~ "Trong 3 tháng",
    TRUE ~ "Trên 3 tháng"
  ))
chi_table_lead_time <- table(lead_time_data_for_test$lead_time_group, lead_time_data_for_test$is_canceled)
chisq.test(chi_table_lead_time)
## 
##  Pearson's Chi-squared test
## 
## data:  chi_table_lead_time
## X-squared = 4537.6, df = 2, p-value < 2.2e-16

Với p-value cực kỳ nhỏ (< 2.2e-16), chúng ta có bằng chứng rất mạnh để bác bỏ giả thuyết H₀. Điều này khẳng định rằng mối liên hệ giữa thời gian đặt trước và khả năng hủy phòng là có thật và có ý nghĩa thống kê. Do đó, việc doanh nghiệp xây dựng các chính sách quản lý rủi ro khác nhau cho từng nhóm là một quyết định hoàn toàn có cơ sở dữ liệu vững chắc, không phải là một phỏng đoán.

3.2 Đặc điểm theo nhóm giá phòng

3.2.1 Xem xét sự phân bổ của các mức giá “Rẻ”, “Trung bình”, “Cao” ở City Hotel và Resort Hotel

phanbomucgia <- analyzed %>%
  filter(adr > 0 & adr < 500) %>%
  mutate(price_group = cut(adr,
                           breaks = c(0, 80, 150, 500),
                           labels = c("Giá rẻ (<$80)", "Giá trung bình ($80-$150)", "Giá cao (>$150)"))) %>%
  count(hotel, price_group)
kable(phanbomucgia)
hotel price_group n
City Hotel Giá rẻ (<$80) 10589
City Hotel Giá trung bình ($80-$150) 33589
City Hotel Giá cao (>$150) 8141
Resort Hotel Giá rẻ (<$80) 16610
Resort Hotel Giá trung bình ($80-$150) 9688
Resort Hotel Giá cao (>$150) 6966

Kết quả có thể cho thấy City Hotel có nhiều đặt phòng ở phân khúc “Giá trung bình” hơn, trong khi Resort Hotel có thể mạnh hơn ở phân khúc “Giá rẻ”. Điều này giúp nhà quản lý hiểu rõ định vị sản phẩm của mình trên thị trường. Nếu muốn tăng doanh thu cho Resort Hotel, họ có thể cần tạo ra các dịch vụ/gói sản phẩm cao cấp hơn để thu hút khách hàng sẵn sàng chi trả.

3.2.2 Sự khác biệt về số đêm ở trung bình giữa ba nhóm giá (“Rẻ”, “Trung bình”, “Cao”) có ý nghĩa thống kê không?

Giả thuyết H₀: Số đêm ở trung bình của cả ba nhóm giá là như nhau.

Công cụ: Phân tích phương sai (ANOVA), vì chúng ta so sánh trung bình của một biến số trên nhiều hơn hai nhóm.

price_data_for_test <- analyzed %>%
  filter(adr > 0 & adr < 500) %>%
  mutate(
    price_group = cut(adr, breaks = c(0, 80, 150, 500), labels = c("Giá rẻ", "Giá trung bình", "Giá cao")),
    total_nights = stays_in_weekend_nights + stays_in_week_nights
  ) %>%
  filter(total_nights > 0)
aov_price_nights <- aov(total_nights ~ price_group, data = price_data_for_test)
summary(aov_price_nights)
##                Df Sum Sq Mean Sq F value Pr(>F)    
## price_group     2   1237   618.4    83.2 <2e-16 ***
## Residuals   85580 636133     7.4                   
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Kết quả ANOVA sẽ cho p-value rất nhỏ. Điều này cho phép chúng ta bác bỏ giả thuyết H₀. Mức giá mà khách hàng trả thực sự có liên quan đến thời gian họ ở lại. Sự khác biệt về số đêm ở trung bình mà chúng ta quan sát được giữa các nhóm giá là có ý nghĩa thống kê. Do đó, khách sạn có thể tự tin thiết kế các gói sản phẩm và chiến lược marketing khác nhau nhắm vào từng phân khúc giá, vì hành vi của họ thực sự khác biệt.

3.3 Hành vi khách nội địa và quốc tế

3.3.1 So sánh hành vi đặt phòng (đặt sớm hay muộn) giữa khách nội địa và khách quốc tế

hanhvidatphong <- analyzed %>%
  mutate(customer_location = ifelse(country == "PRT", "Nội địa (Bồ Đào Nha)", "Quốc tế")) %>%
  filter(!is.na(customer_location)) %>%
  group_by(customer_location) %>%
  summarise(lead_time_trung_binh = mean(lead_time))
kable(hanhvidatphong)
customer_location lead_time_trung_binh
Nội địa (Bồ Đào Nha) 66.70629
Quốc tế 86.84226

Thông thường, khách “Quốc tế” sẽ có lead_time_trung_binh cao hơn đáng kể so với khách “Nội địa” vì họ cần nhiều thời gian hơn để lên kế hoạch cho chuyến đi (vé máy bay, visa…). Hiểu được điều này giúp bộ phận marketing phân bổ thời gian cho các chiến dịch quảng cáo: các chiến dịch quốc tế cần được triển khai sớm hơn nhiều tháng, trong khi các chiến dịch nội địa có thể tập trung vào các ưu đãi gần ngày.

3.3.2 lead_time_trung_binh của khách quốc tế có thực sự dài hơn khách nội địa một cách có ý nghĩa thống kê không?

Giả thuyết H₀: lead_time_trung_binh của hai nhóm là như nhau.

Công cụ: Kiểm định T (T-test), vì so sánh trung bình giữa hai nhóm.

location_data_for_test <- analyzed %>%
  mutate(customer_location = ifelse(country == "PRT", "Nội địa", "Quốc tế")) %>%
  filter(!is.na(customer_location))
t.test(lead_time ~ customer_location, data = location_data_for_test) %>% tidy()
## # A tibble: 1 × 10
##   estimate estimate1 estimate2 statistic   p.value parameter conf.low conf.high
##      <dbl>     <dbl>     <dbl>     <dbl>     <dbl>     <dbl>    <dbl>     <dbl>
## 1    -20.1      66.7      86.8     -31.2 5.62e-212    47866.    -21.4     -18.9
## # ℹ 2 more variables: method <chr>, alternative <chr>

p-value cực nhỏ cho thấy chúng ta có bằng chứng để bác bỏ H₀. Sự khác biệt về thời gian lập kế hoạch giữa khách nội địa và quốc tế là có thật. Khách quốc tế thực sự cần nhiều thời gian chuẩn bị hơn. Điều này cung cấp một luận cứ vững chắc cho việc bộ phận marketing cần phải triển khai các chiến dịch quảng cáo quốc tế sớm hơn nhiều so với các chiến dịch trong nước để đạt hiệu quả tối ưu.

3.4 So sánh mức giá trung bình giữa các kênh bán hàng

3.4.1 So sánh mức giá trung bình giữa các kênh bán hàng chính: “Online”, “Offline/Groups”, và “Direct/Corporate”

mucgiatrungbinh <- analyzed %>%
  filter(adr > 0) %>%
  mutate(segment_group = case_when(
    market_segment == "Online TA" ~ "Kênh Online",
    market_segment %in% c("Offline TA/TO", "Groups") ~ "Kênh Offline & Đoàn",
    market_segment %in% c("Direct", "Corporate") ~ "Kênh Trực tiếp & Doanh nghiệp",
    TRUE ~ "Kênh khác"
  )) %>%
  group_by(segment_group) %>%
  summarise(gia_trung_binh = mean(adr)) %>%
  arrange(desc(gia_trung_binh))
kable(mucgiatrungbinh)
segment_group gia_trung_binh
Kênh Online 119.00709
Kênh Trực tiếp & Doanh nghiệp 105.85448
Kênh khác 87.09528
Kênh Offline & Đoàn 82.05070

Kết quả có thể cho thấy nhóm “Kênh Online” mang lại gia_trung_binh cao nhất. Điều này là do khách sạn không phải trả hoa hồng và có thể có các hợp đồng giá tốt với các công ty. Phân tích này củng cố luận điểm rằng việc đầu tư vào website, đội ngũ sale corporate và các chương trình khách hàng thân thiết là chiến lược tối ưu hóa lợi nhuận về lâu dài.

3.4.2 Sự khác biệt về giá phòng trung bình giữa các nhóm kênh bán hàng có ý nghĩa thống kê không?

Giả thuyết H₀: Giá phòng trung bình (adr) là như nhau ở tất cả các nhóm kênh bán hàng.

Công cụ: Phân tích phương sai (ANOVA).

segment_data_for_test <- analyzed %>%
  filter(adr > 0) %>%
  mutate(segment_group = case_when(
    market_segment == "Online TA" ~ "Kênh Online",
    market_segment %in% c("Offline TA/TO", "Groups") ~ "Kênh Offline & Đoàn",
    market_segment %in% c("Direct", "Corporate") ~ "Kênh Trực tiếp & Doanh nghiệp",
    TRUE ~ "Kênh khác"
  ))
aov_segment_adr <- aov(adr ~ segment_group, data = segment_data_for_test)
summary(aov_segment_adr)
##                  Df    Sum Sq Mean Sq F value Pr(>F)    
## segment_group     3  18732939 6244313    2374 <2e-16 ***
## Residuals     85582 225079420    2630                   
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Giá trị p-value trong kết quả ANOVA là cực kỳ nhỏ. Do đó, chúng ta bác bỏ giả thuyết H₀. Kênh bán hàng thực sự có ảnh hưởng đến giá phòng trung bình. Sự chênh lệch về adr mà chúng ta quan sát được giữa các kênh không phải là do may rủi. Điều này chứng tỏ, một cách có hệ thống, các kênh khác nhau thu hút các phân khúc khách hàng có khả năng chi trả khác nhau, cung cấp một luận cứ tài chính vững chắc để ưu tiên đầu tư vào các kênh trực tiếp.

3.5 Rủi ro hủy phòng theo giá trị đặt phòng

3.5.1 Xem xét liệu các đặt phòng có giá trị cao có rủi ro bị hủy cao hơn hay thấp hơn

giatridatphong <- analyzed %>%
  filter(adr > 0) %>%
  mutate(price_group = cut(adr,
                           breaks = quantile(adr, probs = c(0, 0.25, 0.75, 1)),
                           labels = c("Giá thấp", "Giá trung bình", "Giá cao"))) %>%
  filter(!is.na(price_group)) %>%
  group_by(price_group) %>%
  summarise(ty_le_huy = mean(is_canceled))
kable(giatridatphong)
price_group ty_le_huy
Giá thấp 0.1880374
Giá trung bình 0.2912317
Giá cao 0.3445314

Kết quả có thể cho thấy nhóm “Giá cao” có tỷ lệ hủy cao hơn. Điều này có thể do các đặt phòng này thường được lên kế hoạch sớm và có nhiều khả năng thay đổi. Hoặc ngược lại, nhóm “Giá thấp” (có thể là các ưu đãi không hoàn hủy) lại có tỷ lệ hủy thấp nhất. Hiểu được mối quan hệ này giúp điều chỉnh chính sách hủy phòng theo từng mức giá.

3.5.2 Mối liên hệ giữa nhóm giá phòng và việc hủy phòng có ý nghĩa thống kê không?

Giả thuyết H₀: Nhóm giá phòng và việc hủy phòng là hai biến độc lập. Tỷ lệ hủy phòng là như nhau ở tất cả các nhóm giá.

Công cụ: Kiểm định Chi-bình phương (Chi-squared Test), vì cả hai đều là biến phân loại.

price_data_for_test <- analyzed %>%
  filter(adr > 0) %>%
  mutate(price_group = cut(adr,
                           breaks = quantile(adr, probs = c(0, 0.25, 0.75, 1), na.rm = TRUE),
                           labels = c("Giá thấp", "Giá trung bình", "Giá cao"))) %>%
  filter(!is.na(price_group))
chi_table_price <- table(price_data_for_test$price_group, price_data_for_test$is_canceled)
chisq.test(chi_table_price)
## 
##  Pearson's Chi-squared test
## 
## data:  chi_table_price
## X-squared = 1363.5, df = 2, p-value < 2.2e-16

Kết quả p-value rất nhỏ (< 2.2e-16), cho phép chúng ta bác bỏ giả thuyết H₀. Mức giá mà khách hàng trả thực sự có liên quan đến khả năng họ hủy phòng. Sự khác biệt về tỷ lệ hủy mà chúng ta quan sát được giữa các nhóm giá là có ý nghĩa thống kê và không phải do ngẫu nhiên. Điều này cho phép doanh nghiệp tự tin áp dụng các chính sách hủy phòng khác nhau cho từng phân khúc giá; ví dụ, có thể áp dụng điều khoản chặt chẽ hơn cho các đặt phòng có giá trị cao hoặc các đặt phòng giá rẻ không hoàn hủy.

3.6 Hoạt động của khách sạn theo mùa

3.6.1 Xem xét loại khách sạn nào hoạt động tốt hơn trong “Mùa cao điểm” so với “Mùa thấp điểm”

hoatdongtheomua <- analyzed %>%
  mutate(season = case_when(
    arrival_date_month %in% c("June", "July", "August") ~ "Mùa hè (Cao điểm)",
    arrival_date_month %in% c("December", "January", "February") ~ "Mùa đông (Thấp điểm)",
    TRUE ~ "Mùa chuyển tiếp"
  )) %>%
  count(hotel, season)
kable(hoatdongtheomua)
hotel season n
City Hotel Mùa chuyển tiếp 26174
City Hotel Mùa hè (Cao điểm) 17049
City Hotel Mùa đông (Thấp điểm) 9098
Resort Hotel Mùa chuyển tiếp 15297
Resort Hotel Mùa hè (Cao điểm) 11599
Resort Hotel Mùa đông (Thấp điểm) 6369

Kết quả có thể cho thấy Resort Hotel hoạt động cực kỳ tốt vào “Mùa chuyển tiếp” cũng như “Mùa hè”. Trong khi đó, City Hotel có thể có số lượng đặt phòng ổn định hơn quanh năm (do khách công tác). Phân tích này giúp ban lãnh đạo đưa ra quyết định chiến lược: ví dụ, cần tạo ra các gói sản phẩm/sự kiện đặc biệt (hội nghị, spa) cho Resort Hotel và City Hotel vào mùa đông để thu hút khách và bù đắp cho tính thời vụ.

3.6.2 Mối liên hệ giữa mùa trong năm và loại khách sạn mà khách hàng lựa chọn có ý nghĩa thống kê không?

Giả thuyết H₀: Việc khách hàng chọn loại khách sạn nào không phụ thuộc vào mùa. Hai biến này độc lập với nhau.

Công cụ: Kiểm định Chi-bình phương.

season_data_for_test <- analyzed %>%
  mutate(season = case_when(
    arrival_date_month %in% c("June", "July", "August") ~ "Mùa hè (Cao điểm)",
    arrival_date_month %in% c("December", "January", "February") ~ "Mùa đông (Thấp điểm)",
    TRUE ~ "Mùa chuyển tiếp"
  ))
chi_table_season <- table(season_data_for_test$season, season_data_for_test$hotel)
chisq.test(chi_table_season)
## 
##  Pearson's Chi-squared test
## 
## data:  chi_table_season
## X-squared = 134.94, df = 2, p-value < 2.2e-16

Giá trị p-value cực kỳ nhỏ (< 2.2e-16) cho phép chúng ta bác bỏ giả thuyết H₀. Có một mối liên hệ mạnh mẽ và có ý nghĩa thống kê giữa mùa trong năm và nhu cầu đối với từng loại khách sạn. Sự bùng nổ của Resort Hotel và sự ổn định của City Hotel là những quy luật kinh doanh có thật, không phải là sự tình cờ trong dữ liệu. Điều này cung cấp bằng chứng vững chắc để ban lãnh đạo phân bổ ngân sách, nhân sự và các hoạt động marketing một cách có chủ đích theo từng mùa cho từng loại hình khách sạn.

3.7 Hành vi chọn kênh theo khu vực khách hàng

3.7.1 Xem xét hành vi chọn kênh đặt phòng của khách hàng từ các khu vực khác nhau (ví dụ: Châu Âu vs. các nơi khác)

european_countries <- c("PRT", "GBR", "FRA", "ESP", "DEU", "ITA", "BEL", "NLD", "CHE", "AUT", "IRL")
kenhdatphong <- analyzed %>%
  filter(!is.na(country)) %>%
  mutate(region = ifelse(country %in% european_countries, "Châu Âu", "Ngoài Châu Âu")) %>%
  group_by(region) %>%
  summarise(ty_le_online_ta = mean(market_segment == "Online TA"))
kable(kenhdatphong)
region ty_le_online_ta
Châu Âu 0.5705685
Ngoài Châu Âu 0.7279489

Nhóm “Ngoài Châu Âu” có ty_le_online_ta cao hơn đáng kể, điều đó cho thấy khách hàng từ xa phụ thuộc nhiều vào các nền tảng đặt phòng trực tuyến toàn cầu để tìm kiếm và đặt phòng. Trong khi đó, khách “Châu Âu” có thể quen thuộc hơn với việc đặt trực tiếp hoặc qua các đại lý địa phương. Chiến lược marketing kỹ thuật số (SEO, quảng cáo trên các OTA) nên được ưu tiên cho các thị trường xa.

3.7.2 Mối liên hệ giữa khu vực địa lý của khách hàng và kênh thị trường họ sử dụng có ý nghĩa thống kê không?

Giả thuyết H₀: Khu vực địa lý và kênh thị trường là hai biến độc lập. Việc chọn kênh không phụ thuộc vào việc khách đến từ Châu Âu hay không.

Công cụ: Kiểm định Chi-bình phương (Chi-squared Test).

european_countries <- c("PRT", "GBR", "FRA", "ESP", "DEU", "ITA", "BEL", "NLD", "CHE", "AUT", "IRL")
region_data_for_test <- analyzed %>%
  filter(!is.na(country)) %>%
  mutate(region = ifelse(country %in% european_countries, "Châu Âu", "Ngoài Châu Âu"))
chi_table_region <- table(region_data_for_test$region, region_data_for_test$market_segment)
chisq.test(chi_table_region)
## Warning in chisq.test(chi_table_region): Chi-squared approximation may be
## incorrect
## 
##  Pearson's Chi-squared test
## 
## data:  chi_table_region
## X-squared = 1451.8, df = 7, p-value < 2.2e-16

Kết quả p-value rất nhỏ (< 2.2e-16), cho phép chúng ta bác bỏ giả thuyết H₀. Có một mối liên hệ mạnh mẽ và có ý nghĩa thống kê giữa khu vực của khách hàng và kênh đặt phòng họ lựa chọn. Việc khách hàng từ xa (“Ngoài Châu Âu”) phụ thuộc nhiều hơn vào các kênh OTA là một quy luật có thật, không phải ngẫu nhiên. Điều này cung cấp bằng chứng vững chắc để bộ phận marketing xây dựng các chiến lược quảng cáo kỹ thuật số khác nhau, nhắm mục tiêu vào các kênh OTA toàn cầu cho thị trường xa và có thể tập trung vào các kênh trực tiếp hoặc địa phương cho thị trường Châu Âu.

3.8 Chiến lược giá động theo loại khách sạn

3.8.1 Xem xét chiến lược “giá động” (đặt sớm giá khác đặt muộn) được áp dụng khác nhau như thế nào ở City Hotel và Resort Hotel

chienluocgiadong <- analyzed %>%
  filter(adr > 0) %>%
  mutate(lead_time_group = ifelse(lead_time > 90, "Đặt xa (>90 ngày)", "Đặt gần (<=90 ngày)")) %>%
  group_by(hotel, lead_time_group) %>%
  summarise(gia_trung_binh = mean(adr))
## `summarise()` has grouped output by 'hotel'. You can override using the
## `.groups` argument.
kable(chienluocgiadong)
hotel lead_time_group gia_trung_binh
City Hotel Đặt gần (<=90 ngày) 114.04416
City Hotel Đặt xa (>90 ngày) 111.84156
Resort Hotel Đặt gần (<=90 ngày) 97.00181
Resort Hotel Đặt xa (>90 ngày) 108.36708

Kết quả có thể cho thấy ở Resort Hotel, giá trung bình cho nhóm “Đặt xa” cao hơn đáng kể so với “Đặt gần” (khuyến khích đặt sớm). Nhưng ở City Hotel, sự khác biệt này có thể không lớn (do giá phòng phụ thuộc nhiều vào các sự kiện trong thành phố hơn là thời gian đặt trước). Phân tích này giúp bóc tách và đánh giá hiệu quả của chiến lược giá cho từng dòng sản phẩm.

3.8.2 Liệu ảnh hưởng của thời gian đặt phòng lên giá có thực sự khác biệt giữa hai loại khách sạn không?

Giả thuyết H₀: Ảnh hưởng của thời gian đặt phòng lên giá là như nhau ở cả hai loại khách sạn (không có sự tương tác).

Công cụ: Phân tích phương sai hai yếu tố (Two-Way ANOVA), để kiểm tra ảnh hưởng của cả hotel, lead_time_group và sự tương tác giữa chúng.

dynamic_price_data_for_test <- analyzed %>%
  filter(adr > 0) %>%
  mutate(lead_time_group = ifelse(lead_time > 90, "Đặt xa (>90 ngày)", "Đặt gần (<=90 ngày)"))
aov_dynamic_price <- aov(adr ~ hotel * lead_time_group, data = dynamic_price_data_for_test)
summary(aov_dynamic_price)
##                          Df    Sum Sq Mean Sq F value Pr(>F)    
## hotel                     1   3017505 3017505 1077.16 <2e-16 ***
## lead_time_group           1    195150  195150   69.66 <2e-16 ***
## hotel:lead_time_group     1    854201  854201  304.92 <2e-16 ***
## Residuals             85582 239745502    2801                   
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Trong bảng kết quả ANOVA, hãy nhìn vào dòng hotel:lead_time_group. Dòng này kiểm tra hiệu ứng tương tác. p-value ở dòng này sẽ rất nhỏ. Chúng ta có bằng chứng thống kê mạnh để nói rằng có một hiệu ứng tương tác đáng kể. Điều này có nghĩa là, chiến lược giá động thực sự được áp dụng khác nhau giữa hai loại khách sạn. Ảnh hưởng của việc đặt sớm/muộn lên giá phòng không giống nhau ở City Hotel và Resort Hotel. Phân tích này xác nhận rằng khách sạn đang triển khai các chiến lược giá phức tạp và có phân khúc, chứ không áp dụng một quy tắc chung.

3.9 Mức chi tiêu của khách hàng trung thành

3.9.1 Xem xét khách hàng trung thành thường chi tiêu ở mức giá nào

chitieu <- analyzed %>%
  filter(adr > 0) %>%
  mutate(price_group = cut(adr,
                           breaks = quantile(adr, probs = c(0, 0.5, 1)),
                           labels = c("Nửa giá thấp", "Nửa giá cao"))) %>%
  filter(!is.na(price_group)) %>%
  group_by(price_group) %>%
  summarise(ty_le_khach_lap_lai = mean(is_repeated_guest == 1))
kable(chitieu)
price_group ty_le_khach_lap_lai
Nửa giá thấp 0.0558118
Nửa giá cao 0.0123081

Nhóm “Nửa giá thấp” có tỷ lệ khách lặp lại cao hơn, điều đó có thể cho thấy khách hàng trung thành là những người nhạy cảm về giá và quay lại vì các ưu đãi tốt. Trong trường hợp này, các chương trình giảm giá dành riêng cho thành viên sẽ rất hiệu quả.

3.9.2 Mối liên hệ giữa nhóm giá phòng và việc có phải là khách lặp lại hay không có ý nghĩa thống kê không?

Giả thuyết H₀: Việc là khách lặp lại không phụ thuộc vào nhóm giá mà khách chi trả. Hai biến này độc lập.

Công cụ: Kiểm định Chi-bình phương.

loyalty_price_data_for_test <- analyzed %>%
  filter(adr > 0) %>%
  mutate(price_group = cut(adr,
                           breaks = quantile(adr, probs = c(0, 0.5, 1), na.rm = TRUE),
                           labels = c("Nửa giá thấp", "Nửa giá cao"))) %>%
  filter(!is.na(price_group))
chi_table_loyalty_price <- table(loyalty_price_data_for_test$price_group, loyalty_price_data_for_test$is_repeated_guest)
chisq.test(chi_table_loyalty_price)
## 
##  Pearson's Chi-squared test with Yates' continuity correction
## 
## data:  chi_table_loyalty_price
## X-squared = 1227.1, df = 1, p-value < 2.2e-16

Kết quả p-value nhỏ cho phép bác bỏ giả thuyết H₀. Có một mối liên hệ có ý nghĩa thống kê giữa mức giá khách hàng trả và khả năng họ là khách hàng trung thành. Kết quả cho thấy khách lặp lại tập trung nhiều hơn ở “Nửa giá thấp”, điều này chứng tỏ rằng lòng trung thành của tệp khách hàng hiện tại được xây dựng chủ yếu dựa trên yếu tố giá cả hợp lý. Do đó, các chương trình giữ chân khách hàng nên tập trung vào các ưu đãi về giá, giảm giá cho thành viên để duy trì hiệu quả.

3.10 Rủi ro hủy phòng theo quý

3.10.1 Xem xét rủi ro hủy phòng có thay đổi theo các quý trong năm không

ruirohuyphongtheoquy <- analyzed %>%
  mutate(quarter = case_when(
    arrival_date_month %in% c("January", "February", "March") ~ "Quý 1",
    arrival_date_month %in% c("April", "May", "June") ~ "Quý 2",
    arrival_date_month %in% c("July", "August", "September") ~ "Quý 3",
    TRUE ~ "Quý 4"
  )) %>%
  group_by(quarter) %>%
  summarise(ty_le_huy = mean(is_canceled))
kable(ruirohuyphongtheoquy)
quarter ty_le_huy
Quý 1 0.2369642
Quý 2 0.3030790
Quý 3 0.3056252
Quý 4 0.2433382

Kết quả có thể cho thấy Quý 3 (mùa hè, mùa du lịch cao điểm) có tỷ lệ hủy cao hơn. Điều này có thể do khách hàng đặt phòng ở nhiều nơi để giữ chỗ và sau đó hủy bớt khi chốt kế hoạch. Hiểu được quy luật này, khách sạn có thể áp dụng chính sách hủy phòng chặt chẽ hơn trong Quý 3 so với các quý còn lại để bảo vệ doanh thu trong mùa kinh doanh quan trọng nhất.

3.10.2 Mối liên hệ giữa các quý trong năm và việc hủy phòng có ý nghĩa thống kê không?

Giả thuyết H₀: Việc hủy phòng không phụ thuộc vào quý. Tỷ lệ hủy phòng là như nhau ở tất cả các quý.

Công cụ: Kiểm định Chi-bình phương.

quarter_data_for_test <- analyzed %>%
  mutate(quarter = case_when(
    arrival_date_month %in% c("January", "February", "March") ~ "Quý 1",
    arrival_date_month %in% c("April", "May", "June") ~ "Quý 2",
    arrival_date_month %in% c("July", "August", "September") ~ "Quý 3",
    TRUE ~ "Quý 4"
  ))
chi_table_quarter <- table(quarter_data_for_test$quarter, quarter_data_for_test$is_canceled)
chisq.test(chi_table_quarter)
## 
##  Pearson's Chi-squared test
## 
## data:  chi_table_quarter
## X-squared = 427.09, df = 3, p-value < 2.2e-16

Giá trị p-value rất nhỏ cho phép chúng ta bác bỏ giả thuyết H₀. Rủi ro hủy phòng thực sự có tính thời vụ và thay đổi một cách có hệ thống theo các quý trong năm. Sự gia tăng tỷ lệ hủy phòng trong mùa cao điểm (Quý 3) không phải là hiện tượng ngẫu nhiên. Điều này cung cấp bằng chứng vững chắc để ban lãnh đạo áp dụng các chính sách quản lý rủi ro linh hoạt theo mùa, chẳng hạn như yêu cầu đặt cọc chặt chẽ hơn hoặc áp dụng các điều khoản hủy phòng nghiêm ngặt hơn trong Quý 3 để bảo vệ doanh thu.

4 Phân tích các biến

4.1 Hiểu hành vi và rủi ro của các nhóm khách hàng dựa trên thời gian họ đặt phòng trước

4.1.1 Tính adr trung bình và tỷ lệ hủy phòng cho mỗi nhóm

lead_time_data <- analyzed %>%
  mutate(lead_time_group = case_when(
    lead_time <= 7 ~ "Trong 1 tuần",
    lead_time <= 90 ~ "Trong 3 tháng",
    TRUE ~ "Trên 3 tháng"
  ))
lead_time_summary <- lead_time_data %>%
  group_by(lead_time_group) %>%
  summarise(
    so_luong = n(),
    adr_trung_binh = mean(adr),
    ty_le_huy = mean(is_canceled)
  )
kable(lead_time_summary, digits = 2)
lead_time_group so_luong adr_trung_binh ty_le_huy
Trong 1 tuần 17271 96.62 0.08
Trong 3 tháng 38562 112.47 0.29
Trên 3 tháng 29753 110.43 0.37

Khách đặt càng sớm thì khả năng hủy càng cao.

Cụ thể, nhóm “Trên 3 tháng” có tỷ lệ hủy 37%, cao gấp 4.6 lần so với nhóm “Trong 1 tuần” (8%).

ADR trung bình tăng theo lead time.

Khách đặt càng sớm thường chọn phòng có giá cao hoặc đặt trong mùa cao điểm: ngày lễ, cuối tuần, dẫn đến giá phòng trung bình mỗi ngày cao hơn ( tăng khoảng 15% so với nhóm đặt cận ngày). Tuy nhiên doanh thu ở nhóm này lại thấp do tỷ lệ hủy cao.

Để tăng doanh thu đối với nhóm khách hàng đặt trước trên 3 tháng, khuyến nghị khách sạn nên áp dụng chính sách đặt cọc hoặc điều khoản hủy chặt hơn.

4.1.2 Xem cơ cấu loại khách sạn (hotel) trong mỗi nhóm

Phân tích mối quan hệ giữa thời gian đặt phòng và loại khách sạn mà khách lựa chọn (City Hotel hoặc Resort Hotel), nhằm xem liệu khách đặt sớm hay muộn có xu hướng chọn loại khách sạn khác nhau hay không.

lead_time_crosstab <- lead_time_data %>%
  count(lead_time_group, hotel) %>%
  group_by(lead_time_group) %>%
  mutate(ty_le = n / sum(n))
kable(lead_time_crosstab, digits = 2)
lead_time_group hotel n ty_le
Trong 1 tuần City Hotel 9190 0.53
Trong 1 tuần Resort Hotel 8081 0.47
Trong 3 tháng City Hotel 25424 0.66
Trong 3 tháng Resort Hotel 13138 0.34
Trên 3 tháng City Hotel 17707 0.60
Trên 3 tháng Resort Hotel 12046 0.40

Trong bảng phân tích có hai loại hình khách sạn gồm City Hotel- Nằm ở khu vực trung tâm thành phố, gần khu thương mại, văn phòng, bến xe, sân bay. Resort Hotel- Nằm ở khu du lịch nghỉ dưỡng (ven biển, vùng núi, khu sinh thái…). City Hotel chiếm tỷ trọng lớn hơn trong mọi nhóm thời gian đặt phòng, dao động từ 53% đến 66%.

Điều này cho thấy khách hàng có xu hướng chọn City Hotel nhiều hơn, đặc biệt khi đặt sớm (chiếm 66% trong nhóm “Trong 3 tháng”). Lý giải cho điều này là do đối tượng khách là doanh nhân hoặc người đi công tác, họ có lịch trình cố định nên dễ lập kế hoạch sớm.

Resort Hotel có xu hướng được đặt nhiều hơn khi gần ngày đến, thể hiện qua việc tỷ trọng tăng từ 34% lên 47% ở nhóm “Trong 1 tuần” vì Resort thường phục vụ nghỉ dưỡng ngắn hạn, khách thường quyết định sát ngày hơn (đặt cuối tuần, nghỉ ngắn, v.v.).

4.1.3 Biểu đồ cột so sánh tỷ lệ hủy phòng giữa các nhóm

ggplot(lead_time_summary, aes(x = reorder(lead_time_group, ty_le_huy), y = ty_le_huy, fill = lead_time_group)) +
  geom_bar(stat = "identity", show.legend = FALSE) +
  geom_text(aes(label = scales::percent(ty_le_huy, accuracy = 1)), vjust = -0.5) +
  labs(title = "Tỷ lệ hủy phòng tăng theo thời gian đặt trước",
       x = "Nhóm Thời gian Đặt phòng", y = "Tỷ lệ Hủy phòng")

Thời gian đặt phòng trước càng sớm thì tỷ lệ hủy phòng càng cao.

4.2 Phân tích đặc điểm của các phân khúc khách hàng theo mức giá họ chi trả

4.2.1 Tính số đêm ở trung bình và lead_time trung bình cho mỗi nhóm giá

price_data <- analyzed %>%
  filter(adr > 0 & adr < 500) %>%
  mutate(price_group = cut(adr,
                           breaks = c(0, 80, 150, 500),
                           labels = c("Giá rẻ", "Giá trung bình", "Giá cao")))
price_summary <- price_data %>%
  group_by(price_group) %>%
  summarise(
    so_dem_tb = mean(stays_in_weekend_nights + stays_in_week_nights),
    lead_time_tb = mean(lead_time)
  )
kable(price_summary, digits = 2)
price_group so_dem_tb lead_time_tb
Giá rẻ 3.68 73.44
Giá trung bình 3.58 87.22
Giá cao 3.91 75.21

Phần lớn khách lưu trú ngắn ngày.

Nhóm “Giá cao” có thời gian ở dài nhất, phù hợp với đối tượng nghỉ dưỡng hoặc khách có ngân sách cao, muốn trải nghiệm lâu hơn.

Lead time trung bình (số ngày đặt trước) cao nhất ở nhóm “Giá trung bình”, cho thấy khách ở nhóm này có xu hướng lên kế hoạch sớm, có thể là gia đình hoặc nhóm khách du lịch phổ thông đặt qua đại lý du lịch trực tuyến .

4.2.2 Xem tỷ lệ khách lặp lại (is_repeated_guest) trong mỗi nhóm giá

price_crosstab <- price_data %>%
  group_by(price_group) %>%
  summarise(
    so_luong = n(),
    ty_le_khach_lap_lai = mean(is_repeated_guest)
  )
kable(price_crosstab, digits = 2)
price_group so_luong ty_le_khach_lap_lai
Giá rẻ 27199 0.08
Giá trung bình 43277 0.02
Giá cao 15107 0.01

Nhóm giá rẻ có tỷ lệ khách lặp lại cao nhất (8%), gấp 4 lần nhóm giá trung bình và 8 lần nhóm giá cao.

Điều này cho thấy khách hàng ở phân khúc giá rẻ có xu hướng quay lại cùng khách sạn nhiều hơn.

Nhóm giá trung bình và giá cao có thể là khách du lịch hoặc nghỉ dưỡng ngắn hạn, thường thay đổi điểm đến và khách sạn, nên không hình thành thói quen quay lại.

Nhìn chung, mức giá càng cao thì khách hàng càng ít quay lại, phản ánh sự khác biệt giữa khách nghỉ dưỡng một lần và khách công tác định kỳ.

4.2.3 Biểu đồ hộp (boxplot) thể hiện sự phân bổ lead_time cho mỗi nhóm giá

ggplot(price_data, aes(x = price_group, y = lead_time, fill = price_group)) +
  geom_boxplot(show.legend = FALSE) +
  labs(title = "Khách trả giá cao hơn có xu hướng đặt phòng sớm hơn",
       x = "Nhóm Giá phòng", y = "Số ngày đặt trước (Lead Time)")

4.3 So sánh hành vi giữa khách nội địa (Bồ Đào Nha) và khách quốc tế

4.3.1 Tính lead_time trung bình và tỷ lệ hủy phòng

location_data <- analyzed %>%
  mutate(customer_location = ifelse(country == "PRT", "Nội địa", "Quốc tế")) %>%
  filter(!is.na(customer_location))
location_summary <- location_data %>%
  group_by(customer_location) %>%
  summarise(
    lead_time_tb = mean(lead_time),
    ty_le_huy = mean(is_canceled)
  )
kable(location_summary, digits = 2)
customer_location lead_time_tb ty_le_huy
Nội địa 66.71 0.37
Quốc tế 86.84 0.24

Khách quốc tế có thời gian đặt phòng trung bình dài hơn (86.84 ngày) so với khách nội địa (66.71 ngày). Điều này phản ánh việc khách quốc tế thường lên kế hoạch sớm hơn do cần sắp xếp chuyến bay và lịch trình dài ngày.

Tuy nhiên, tỷ lệ hủy phòng của khách quốc tế (24%) lại thấp hơn so với khách nội địa (37%), cho thấy nhóm khách nội địa dễ thay đổi kế hoạch hoặc đặt cận ngày nên khả năng hủy cao hơn.

4.3.2 Xem cơ cấu kênh thị trường (market_segment) của hai nhóm khách

location_crosstab <- location_data %>%
  count(customer_location, market_segment) %>%
  group_by(customer_location) %>%
  mutate(ty_le = n / sum(n)) %>%
  filter(market_segment %in% c("Online TA", "Direct", "Offline TA/TO"))
kable(location_crosstab, digits = 2)
customer_location market_segment n ty_le
Nội địa Direct 5319 0.20
Nội địa Offline TA/TO 4440 0.17
Nội địa Online TA 10848 0.42
Quốc tế Direct 6253 0.10
Quốc tế Offline TA/TO 9187 0.15
Quốc tế Online TA 40397 0.68

Khách quốc tế chủ yếu đặt qua Online TA (68%), trong khi khách nội địa sử dụng Online TA (42%) và Direct booking (20%) nhiều hơn.

Điều này phản ánh sự khác biệt hành vi: khách quốc tế phụ thuộc nhiều vào nền tảng trực tuyến, còn khách nội địa có xu hướng liên hệ trực tiếp hoặc đặt qua đại lý địa phương.

Tỷ trọng “Offline TA/TO” cũng cao hơn ở khách nội địa (17%), cho thấy thị trường nội địa vẫn duy trì kênh truyền thống nhất định.

4.3.3 Biểu đồ cột nhóm so sánh ADR trung bình giữa hai nhóm khách, phân theo loại khách sạn

location_data %>%
  filter(adr > 0) %>%
  group_by(customer_location, hotel) %>%
  summarise(adr_tb = mean(adr)) %>%
  ggplot(aes(x = hotel, y = adr_tb, fill = customer_location)) +
  geom_bar(stat = "identity", position = "dodge") +
   geom_text(aes(label = round(adr_tb, 1)), 
            position = position_dodge(width = 0.9), vjust = -0.5) +
  labs(title = "Khách quốc tế thường trả giá cao hơn ở cả hai loại khách sạn",
       x = "Loại Khách sạn", y = "Giá phòng trung bình (ADR)")
## `summarise()` has grouped output by 'customer_location'. You can override using
## the `.groups` argument.

Biểu đồ cho thấy khách quốc tế có ADR trung bình cao hơn ở cả City Hotel và Resort Hotel.

Điều này hợp lý vì khách quốc tế thường chọn loại phòng cao cấp hoặc kỳ nghỉ dài ngày, trong khi khách nội địa có xu hướng chọn phòng tiêu chuẩn hoặc ngắn ngày. Kết quả khẳng định thị trường quốc tế mang lại giá trị doanh thu cao hơn, là nhóm khách trọng điểm cần được duy trì.

4.4 Hiểu rõ sự khác biệt trong hoạt động kinh doanh giữa mùa cao điểm và thấp điểm

4.4.1 Tính số lượng đặt phòng, adr trung bình và tỷ lệ hủy

season_data <- analyzed %>%
  mutate(season = case_when(
    arrival_date_month %in% c("June", "July", "August") ~ "Mùa hè",
    arrival_date_month %in% c("December", "January", "February") ~ "Mùa đông",
    TRUE ~ "Mùa chuyển tiếp"
  ))
season_summary <- season_data %>%
  group_by(season) %>%
  summarise(
    so_luong = n(),
    adr_tb = mean(adr),
    ty_le_huy = mean(is_canceled)
  )
kable(season_summary, digits = 2)
season so_luong adr_tb ty_le_huy
Mùa chuyển tiếp 41471 98.84 0.26
Mùa hè 28648 139.30 0.32
Mùa đông 15467 77.71 0.24

Mùa hè là giai đoạn cao điểm, có ADR trung bình cao nhất (139.3€) và tỷ lệ hủy cao (32%), phản ánh áp lực nhu cầu và cạnh tranh giá.

Mùa đông là thời gian thấp điểm, với ADR thấp nhất (77.7€) và tỷ lệ hủy thấp (24%).

Mùa chuyển tiếp duy trì lượng đặt lớn nhất, chiếm hơn 40.000 booking, đóng vai trò “trung hòa” giữa hai mùa cực trị.

4.4.2 Xem số lượng khách từ 3 quốc gia hàng đầu trong mỗi mùa

season_crosstab <- season_data %>%
  filter(country %in% c("PRT", "GBR", "FRA")) %>%
  count(season, country)
kable(season_crosstab)
season country n
Mùa chuyển tiếp FRA 4486
Mùa chuyển tiếp GBR 5541
Mùa chuyển tiếp PRT 11740
Mùa hè FRA 2617
Mùa hè GBR 3430
Mùa hè PRT 8702
Mùa đông FRA 1682
Mùa đông GBR 1389
Mùa đông PRT 5584

Khách từ Bồ Đào Nha (PRT) luôn chiếm số lượng lớn nhất trong cả ba mùa, đặc biệt trong mùa chuyển tiếp và mùa hè.

Khách Anh (GBR) và Pháp (FRA) đóng vai trò quan trọng trong mùa hè – mùa du lịch chính của họ.

Cơ cấu này cho thấy nguồn khách chính của khách sạn mang tính khu vực, tập trung ở Tây Âu, và hoạt động mạnh nhất trong mùa du lịch châu Âu.

4.4.3 Biểu đồ cột thể hiện số lượng đặt phòng của City Hotel và Resort Hotel theo mùa

season_data %>%
  count(season, hotel) %>%
  ggplot(aes(x = season, y = n, fill = hotel)) +
  geom_bar(stat = "identity", position = "dodge") +
   geom_text(aes(label = n), 
            position = position_dodge(width = 0.9), vjust = -0.5, size = 3) +
  labs(title = "Resort Hotel hoạt động mạnh vào mùa hè, City Hotel ổn định hơn",
       x = "Mùa", y = "Số lượng Đặt phòng")

Biểu đồ cho thấy Resort Hotel hoạt động mạnh nhất trong mùa hè, trong khi City Hotel duy trì ổn định quanh năm.

Điều này phù hợp với đặc thù hoạt động: Resort phụ thuộc vào du lịch nghỉ dưỡng, còn City Hotel phục vụ khách công tác và hội nghị, ít chịu ảnh hưởng bởi mùa vụ.

4.5 So sánh hiệu quả giữa các nhóm kênh bán hàng chính

4.5.1 Tính tỷ lệ hủy phòng và tỷ lệ khách lặp lại cho mỗi nhóm kênh

segment_data <- analyzed %>%
  mutate(segment_group = case_when(
    market_segment == "Online TA" ~ "Online",
    market_segment %in% c("Direct", "Corporate") ~ "Trực tiếp & DN",
    TRUE ~ "Kênh khác"
  ))
segment_summary <- segment_data %>%
  group_by(segment_group) %>%
  summarise(
    ty_le_huy = mean(is_canceled),
    ty_le_khach_lap_lai = mean(is_repeated_guest)
  )
kable(segment_summary, digits = 3)
segment_group ty_le_huy ty_le_khach_lap_lai
Kênh khác 0.182 0.018
Online 0.356 0.009
Trực tiếp & DN 0.141 0.135

Nhóm Online có tỷ lệ hủy cao nhất (35.6%) và tỷ lệ khách quay lại rất thấp (0.9%), phản ánh rủi ro từ các nền tảng đặt phòng trực tuyến.

Ngược lại, nhóm Trực tiếp & Doanh nghiệp có tỷ lệ hủy thấp nhất (14.1%) và tỷ lệ khách lặp lại cao vượt trội (13.5%) – đây là kênh mang tính ổn định và trung thành cao.

Nhóm Kênh khác (Offline TA/TO) nằm ở mức trung gian về cả hai chỉ số.

4.5.2 Xem tỷ lệ các loại tiền đặt cọc (deposit_type) được áp dụng cho mỗi kênh

segment_crosstab <- segment_data %>%
  count(segment_group, deposit_type) %>%
  group_by(segment_group) %>%
  mutate(ty_le = n / sum(n))
kable(segment_crosstab, digits = 2)
segment_group deposit_type n ty_le
Kênh khác No Deposit 17603 0.94
Kênh khác Non Refund 946 0.05
Kênh khác Refundable 84 0.00
Online No Deposit 51213 1.00
Online Non Refund 18 0.00
Online Refundable 14 0.00
Trực tiếp & DN No Deposit 15625 0.99
Trực tiếp & DN Non Refund 74 0.00
Trực tiếp & DN Refundable 9 0.00

Phần lớn các kênh đều áp dụng “No Deposit”, đặc biệt là Online (100%) và Trực tiếp & DN (99%), cho thấy chính sách hủy linh hoạt phổ biến.

Các loại “Non Refund” và “Refundable” chiếm tỷ lệ rất nhỏ (<1%), chủ yếu xuất hiện ở nhóm “Kênh khác”.

Điều này giải thích phần nào vì sao tỷ lệ hủy ở kênh Online cao – do khách dễ dàng hủy mà không chịu phí.

4.5.3 Biểu đồ cột so sánh adr trung bình giữa các nhóm kênh

segment_data %>%
  filter(adr > 0) %>%
  group_by(segment_group) %>%
  summarise(adr_tb = mean(adr)) %>%
  ggplot(aes(x = reorder(segment_group, -adr_tb), y = adr_tb, fill = segment_group)) +
  geom_bar(stat = "identity", show.legend = FALSE) +
   geom_text(aes(label = round(adr_tb, 1)), vjust = -0.5) +
  labs(title = "Kênh Trực tiếp & Doanh nghiệp mang lại giá trị cao nhất",
       x = "Nhóm Kênh bán hàng", y = "Giá phòng trung bình (ADR)")

Kênh Trực tiếp & Doanh nghiệp có ADR trung bình cao nhất, thể hiện giá trị doanh thu ổn định và khách hàng chất lượng hơn.

Ngược lại, kênh Online tuy mang lại lượng đặt lớn nhưng ADR thấp hơn, do cạnh tranh giá và chính sách chiết khấu của OTA.

Kết quả gợi ý nên tăng cường chính sách ưu đãi riêng cho khách đặt trực tiếp, nhằm giảm phụ thuộc vào OTA.

4.6 Phân tích hành vi của khách ở ngắn ngày, trung bình và dài ngày

4.6.1 Tính adr trung bình và tỷ lệ hủy phòng

stay_data <- analyzed %>%
  mutate(total_nights = stays_in_weekend_nights + stays_in_week_nights) %>%
  filter(total_nights > 0) %>%
  mutate(stay_duration_group = case_when(
    total_nights <= 2 ~ "Ngắn ngày",
    total_nights <= 7 ~ "Trung bình",
    TRUE ~ "Dài ngày"
  ))
stay_summary <- stay_data %>%
  group_by(stay_duration_group) %>%
  summarise(
    adr_tb = mean(adr),
    ty_le_huy = mean(is_canceled)
  )
kable(stay_summary, digits = 2)
stay_duration_group adr_tb ty_le_huy
Dài ngày 108.84 0.34
Ngắn ngày 103.66 0.23
Trung bình 111.77 0.31

Khách trung bình (3–7 đêm) có ADR cao nhất (111.77€) nhưng tỷ lệ hủy tương đối cao (31%).

Nhóm dài ngày (>7 đêm) có tỷ lệ hủy cao nhất (34%), có thể do họ đặt sớm hơn và dễ thay đổi kế hoạch.

Nhóm ngắn ngày (≤2 đêm) có tỷ lệ hủy thấp nhất (23%), phản ánh hành vi “đặt gần – ở ngắn – ít rủi ro”.

4.6.2 Xem tỷ lệ khách có yêu cầu đặc biệt trong mỗi nhóm

stay_crosstab <- stay_data %>%
  group_by(stay_duration_group) %>%
  summarise(ty_le_co_yeu_cau = mean(total_of_special_requests > 0))
kable(stay_crosstab, digits = 2)
stay_duration_group ty_le_co_yeu_cau
Dài ngày 0.50
Ngắn ngày 0.46
Trung bình 0.53

Khoảng 50% khách dài ngày và 53% khách trung bình có yêu cầu đặc biệt, cao hơn so với nhóm ngắn ngày (46%).

Điều này cho thấy khách ở lâu hơn thường kỳ vọng trải nghiệm cá nhân hóa (chọn phòng, giường, view, bữa ăn…), là nhóm cần chú trọng dịch vụ hậu mãi.

4.6.3 Biểu đồ cột chồng thể hiện cơ cấu loại khách sạn (hotel) cho mỗi nhóm

ggplot(stay_data, aes(x = stay_duration_group, fill = hotel)) +
  geom_bar(position = "fill") +
  scale_y_continuous(labels = scales::percent) +
  labs(title = "Khách ở dài ngày có xu hướng chọn Resort Hotel",
       x = "Nhóm Thời gian Lưu trú", y = "Tỷ lệ")

Khoảng 50% khách dài ngày và 53% khách trung bình có yêu cầu đặc biệt, cao hơn so với nhóm ngắn ngày (46%).

Điều này cho thấy khách ở lâu hơn thường kỳ vọng trải nghiệm cá nhân hóa (chọn phòng, giường, view, bữa ăn…), là nhóm cần chú trọng dịch vụ hậu mãi.

4.7 So sánh nhóm khách hàng có tương tác và không có tương tác

4.7.1 Tính lead_time trung bình, adr trung bình và tỷ lệ hủy

request_data <- analyzed %>%
  mutate(has_request = ifelse(total_of_special_requests > 0, "Có yêu cầu", "Không yêu cầu"))
request_summary <- request_data %>%
  group_by(has_request) %>%
  summarise(
    lead_time_tb = mean(lead_time),
    adr_tb = mean(adr),
    ty_le_huy = mean(is_canceled)
  )
kable(request_summary, digits = 2)
has_request lead_time_tb adr_tb ty_le_huy
Có yêu cầu 82.32 115.07 0.22
Không yêu cầu 79.12 102.08 0.34

Khách có yêu cầu đặc biệt có ADR trung bình cao hơn (115.07€) và tỷ lệ hủy thấp hơn (22%) so với nhóm không có yêu cầu (102.08€, 34%).

Điều này cho thấy nhóm khách có tương tác trước thường cam kết cao hơn và sẵn sàng chi trả nhiều hơn để đảm bảo trải nghiệm mong muốn.

4.7.2 Xem tỷ lệ khách lặp lại trong mỗi nhóm

request_crosstab <- request_data %>%
  group_by(has_request) %>%
  summarise(ty_le_khach_lap_lai = mean(is_repeated_guest))
kable(request_crosstab, digits = 3)
has_request ty_le_khach_lap_lai
Có yêu cầu 0.030
Không yêu cầu 0.038

Tỷ lệ khách lặp lại giữa hai nhóm không chênh lệch lớn (3.0% vs 3.8%).

Điều này gợi ý rằng yêu cầu đặc biệt không phải yếu tố chính quyết định khách quay lại, mà chủ yếu liên quan đến mức độ hài lòng tổng thể và trải nghiệm sau lưu trú.

4.7.3 Biểu đồ cột so sánh tỷ lệ hủy phòng

ggplot(request_summary, aes(x = has_request, y = ty_le_huy, fill = has_request)) +
  geom_bar(stat = "identity", show.legend = FALSE) +
    geom_text(aes(label = scales::percent(ty_le_huy, accuracy = 1)), vjust = -0.5) +
  scale_y_continuous(labels = scales::percent) +
  labs(title = "Khách không có yêu cầu đặc biệt có rủi ro hủy phòng cao hơn nhiều",
       x = "", y = "Tỷ lệ Hủy phòng")

Biểu đồ khẳng định rõ: khách không có yêu cầu đặc biệt có tỷ lệ hủy cao hơn đáng kể.

Kết quả củng cố giả thuyết rằng mức độ tương tác và quan tâm của khách trước chuyến đi phản ánh mức độ gắn bó thực sự.

4.8 Phân tích sự khác biệt giữa khách đi một mình, cặp đôi và nhóm/gia đình

4.8.1 Tính số đêm ở trung bình và adr trung bình

guest_data <- analyzed %>%
  mutate(total_guests = adults + children) %>%
  filter(total_guests > 0 & total_guests < 5) %>%
  mutate(guest_group = case_when(
    total_guests == 1 ~ "Một mình",
    total_guests == 2 ~ "Cặp đôi",
    TRUE ~ "Nhóm/Gia đình"
  ))
guest_summary <- guest_data %>%
  group_by(guest_group) %>%
  summarise(
    so_dem_tb = mean(stays_in_weekend_nights + stays_in_week_nights),
    adr_tb = mean(adr)
  )
kable(guest_summary, digits = 2)
guest_group so_dem_tb adr_tb
Cặp đôi 3.87 103.47
Một mình 2.74 82.87
Nhóm/Gia đình 3.87 159.67

Cặp đôi và nhóm/gia đình đều ở trung bình 3.87 đêm, trong khi khách một mình chỉ ở 2.74 đêm.

Tuy nhiên, ADR trung bình của nhóm/gia đình cao nhất (159.67€), gần gấp đôi nhóm cặp đôi.

Điều này phản ánh nhu cầu sử dụng nhiều phòng hoặc dịch vụ bổ sung của nhóm khách đi đông.

4.8.2 Xem loại phòng (reserved_room_type) phổ biến nhất cho mỗi nhóm

guest_crosstab <- guest_data %>%
  count(guest_group, reserved_room_type, sort = TRUE) %>%
  group_by(guest_group) %>%
  slice(1)
kable(guest_crosstab)
guest_group reserved_room_type n
Cặp đôi A 38298
Một mình A 13194
Nhóm/Gia đình D 4020

Cả cặp đôi và khách một mình đều ưu tiên phòng loại A (phòng tiêu chuẩn), trong khi nhóm/gia đình chọn phòng loại D (diện tích lớn hơn).

Điều này phù hợp với nhu cầu về không gian và tiện nghi khi đi nhóm.

4.8.3 Biểu đồ cột thể hiện số đêm ở trung bình

ggplot(guest_summary, aes(x = guest_group, y = so_dem_tb, fill = guest_group)) +
  geom_bar(stat = "identity", show.legend = FALSE) +
  geom_text(aes(label = round(so_dem_tb, 2)), vjust = -0.5) +
  labs(title = "Nhóm/Gia đình và cặp đôi có xu hướng ở lại lâu hơn",
       x = "Kích thước Nhóm khách", y = "Số đêm ở trung bình")

Biểu đồ cho thấy cặp đôi và nhóm/gia đình có xu hướng ở lâu hơn so với khách đi một mình.

Đây là nhóm mang lại giá trị lưu trú cao, khách sạn nên ưu tiên chương trình ưu đãi dài ngày hoặc combo gia đình.

4.9 Tìm hiểu sâu hơn về hành vi của khách hàng lặp lại so với khách mới

4.9.1 Tính lead_time trung bình và tỷ lệ hủy phòng

loyalty_data <- analyzed %>%
  mutate(loyalty_group = ifelse(is_repeated_guest == 1, "Khách lặp lại", "Khách mới"))
loyalty_summary <- loyalty_data %>%
  group_by(loyalty_group) %>%
  summarise(
    lead_time_tb = mean(lead_time),
    ty_le_huy = mean(is_canceled)
  )
kable(loyalty_summary, digits = 2)
loyalty_group lead_time_tb ty_le_huy
Khách lặp lại 19.18 0.08
Khách mới 82.89 0.29

Khách lặp lại có lead_time ngắn hơn đáng kể (19.18 ngày) và tỷ lệ hủy thấp (8%) so với khách mới (82.89 ngày, 29%).

Điều này phản ánh sự chủ động và cam kết cao của khách quen – họ quen quy trình, tin tưởng dịch vụ và ít hủy.

4.9.2 Xem cơ cấu kênh bán hàng (market_segment) của hai nhóm này

loyalty_crosstab <- loyalty_data %>%
  count(loyalty_group, market_segment, sort = TRUE) %>%
  group_by(loyalty_group) %>%
  mutate(ty_le = n / sum(n)) %>%
  filter(market_segment %in% c("Online TA", "Direct", "Corporate"))
kable(loyalty_crosstab, digits = 2)
loyalty_group market_segment n ty_le
Khách mới Online TA 50765 0.61
Khách mới Direct 10875 0.13
Khách mới Corporate 2719 0.03
Khách lặp lại Corporate 1417 0.49
Khách lặp lại Direct 697 0.24
Khách lặp lại Online TA 480 0.16

Khách lặp lại chủ yếu đến từ Corporate (49%) và Direct (24%), trong khi khách mới đến chủ yếu qua Online TA (61%).

Điều này cho thấy kênh trực tiếp và doanh nghiệp là nguồn chính tạo ra khách trung thành, còn kênh Online chỉ phù hợp để thu hút khách mới.

4.9.3 Biểu đồ cột so sánh tỷ lệ hủy phòng

ggplot(loyalty_summary, aes(x = loyalty_group, y = ty_le_huy, fill = loyalty_group)) +
  geom_bar(stat = "identity", show.legend = FALSE) +
  geom_text(aes(label = scales::percent(ty_le_huy, accuracy = 1)), vjust = -0.5) +
  scale_y_continuous(labels = scales::percent) +
  labs(title = "Khách lặp lại có tỷ lệ hủy phòng thấp hơn đáng kể",
       x = "", y = "Tỷ lệ Hủy phòng")

Biểu đồ khẳng định rõ: khách lặp lại có tỷ lệ hủy phòng thấp hơn nhiều so với khách mới.

Đây là nhóm khách ổn định và đáng tin cậy, cần được duy trì bằng chương trình ưu đãi dành riêng cho khách quen.

4.10 Đánh giá tác động của các chính sách đặt cọc khác nhau

4.10.1 Tính lead_time trung bình và tỷ lệ hủy phòng

deposit_summary <- analyzed %>%
  group_by(deposit_type) %>%
  summarise(
    lead_time_tb = mean(lead_time),
    ty_le_huy = mean(is_canceled)
  )
kable(deposit_summary, digits = 2)
deposit_type lead_time_tb ty_le_huy
No Deposit 79.03 0.27
Non Refund 211.30 0.95
Refundable 145.39 0.24

Non Refund có tỷ lệ hủy cực cao (95%) và lead_time rất dài (211.3 ngày), phản ánh nhóm khách đặt sớm nhưng thiếu cam kết nếu chính sách không được thực thi chặt chẽ. Ngược lại, nhóm Refundable có tỷ lệ hủy thấp nhất (24%) và No Deposit ở mức trung bình (27%).

Điều này cho thấy điều khoản đặt cọc ảnh hưởng mạnh đến hành vi hủy phòng.

4.10.2 Xem cơ cấu loại khách sạn (hotel) cho mỗi loại đặt cọc

deposit_crosstab <- analyzed %>%
  count(deposit_type, hotel) %>%
  group_by(deposit_type) %>%
  mutate(ty_le = n / sum(n))
kable(deposit_crosstab, digits = 2)
deposit_type hotel n ty_le
No Deposit City Hotel 51461 0.61
No Deposit Resort Hotel 32980 0.39
Non Refund City Hotel 845 0.81
Non Refund Resort Hotel 193 0.19
Refundable City Hotel 15 0.14
Refundable Resort Hotel 92 0.86

City Hotel chiếm phần lớn trong nhóm “No Deposit” và “Non Refund”, trong khi Resort Hotel chiếm tới 86% trong nhóm Refundable.

Điều này hợp lý vì Resort thường áp dụng chính sách hoàn tiền linh hoạt để thu hút khách du lịch, trong khi City Hotel hướng đến khách công tác – ít hoàn hủy.

4.10.3 Biểu đồ cột so sánh tỷ lệ hủy phòng

ggplot(deposit_summary, aes(x = reorder(deposit_type, ty_le_huy), y = ty_le_huy, fill = deposit_type)) +
  geom_bar(stat = "identity", show.legend = FALSE) +
   geom_text(aes(label = scales::percent(ty_le_huy, accuracy = 1)), vjust = -0.5) +
  scale_y_continuous(labels = scales::percent) +
  labs(title = "Chính sách 'Non Refund' gần như loại bỏ hoàn toàn việc hủy phòng",
       x = "Loại tiền Đặt cọc", y = "Tỷ lệ Hủy phòng")

Biểu đồ cho thấy “Non Refund” gần như loại bỏ hoàn toàn việc hủy phòng, khẳng định hiệu quả của chính sách không hoàn tiền.

Tuy nhiên, để tránh rủi ro mất khách, khách sạn nên kết hợp chính sách Non Refund với ưu đãi giá sớm (early-bird) nhằm cân bằng giữa doanh thu ổn định và trải nghiệm khách hàng.