Bài Tập Buổi 04 — Bảng ngẫu nhiên & Kiểm định độc lập cơ bản

Author

Đỗ Nguyễn Nhật Minh — 2321000336

Published

June 2, 2026

1 BÀI TẬP BUỔI 04 — BẢNG NGẪU NHIÊN & KIỂM ĐỊNH ĐỘC LẬP CHI-SQUARE

1.1 Mục tiêu phân tích

Bài phân tích sử dụng bộ dữ liệu Hotel Booking Demand để nghiên cứu mối liên hệ giữa việc khách hàng hủy phòng (is_canceled) với hai yếu tố định tính: loại khách sạn (hotel) và loại khách hàng (customer_type). Các kỹ thuật được sử dụng gồm bảng ngẫu nhiên hai chiều, tỷ lệ theo hàng, kiểm định độc lập Chi-square và hệ số đo lường độ mạnh mối liên hệ Cramér’s V.

2 Phần A — Thiết lập Quarto Project (CLO2)

2.1 1. Thiết lập chunk toàn cục và nạp thư viện

library(tidyverse)
library(gmodels)
library(vcd)
library(knitr)

3 Phần B — Nạp và Khám phá Dữ liệu (CLO1, CLO2)

3.1 2. Đọc dữ liệu bằng hai cách

Theo yêu cầu bài tập, dữ liệu có thể được đọc online từ GitHub hoặc đọc offline từ thư mục Data. Trong bài này, cách online được kích hoạt để file có thể render ngay khi máy có kết nối Internet; cách offline được trình bày đúng yêu cầu nhưng để dưới dạng chú thích.

#đọc dữ liệu onl

df <- read.csv(
  "https://raw.githubusercontent.com/rfordatascience/tidytuesday/master/data/2020/2020-02-11/hotels.csv"
)

dim(df)
[1] 119390     32

Dữ liệu gồm 119390 lượt đặt phòng32 biến.

3.2 3. Kiểm tra cấu trúc dữ liệu tổng quát

str(df)
'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" ...
glimpse(df)
Rows: 119,390
Columns: 32
$ hotel                          <chr> "Resort Hotel", "Resort Hotel", "Resort…
$ is_canceled                    <int> 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, …
$ lead_time                      <int> 342, 737, 7, 13, 14, 14, 0, 9, 85, 75, …
$ arrival_date_year              <int> 2015, 2015, 2015, 2015, 2015, 2015, 201…
$ arrival_date_month             <chr> "July", "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, 1, 1, 1, …
$ stays_in_weekend_nights        <int> 0, 0, 0, 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, 4, 4, 4, …
$ adults                         <int> 2, 2, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, …
$ children                       <int> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, …
$ babies                         <int> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, …
$ meal                           <chr> "BB", "BB", "BB", "BB", "BB", "BB", "BB…
$ country                        <chr> "PRT", "PRT", "GBR", "GBR", "GBR", "GBR…
$ market_segment                 <chr> "Direct", "Direct", "Direct", "Corporat…
$ distribution_channel           <chr> "Direct", "Direct", "Direct", "Corporat…
$ is_repeated_guest              <int> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, …
$ previous_cancellations         <int> 0, 0, 0, 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, 0, 0, 0, …
$ reserved_room_type             <chr> "C", "C", "A", "A", "A", "A", "C", "C",…
$ assigned_room_type             <chr> "C", "C", "C", "A", "A", "A", "C", "C",…
$ booking_changes                <int> 3, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, …
$ deposit_type                   <chr> "No Deposit", "No Deposit", "No Deposit…
$ agent                          <chr> "NULL", "NULL", "NULL", "304", "240", "…
$ company                        <chr> "NULL", "NULL", "NULL", "NULL", "NULL",…
$ days_in_waiting_list           <int> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, …
$ customer_type                  <chr> "Transient", "Transient", "Transient", …
$ adr                            <dbl> 0.00, 0.00, 75.00, 75.00, 98.00, 98.00,…
$ required_car_parking_spaces    <int> 0, 0, 0, 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, 0, 0, 3, …
$ reservation_status             <chr> "Check-Out", "Check-Out", "Check-Out", …
$ reservation_status_date        <chr> "2015-07-01", "2015-07-01", "2015-07-02…

3.3 4. Xác định các biến định tính

# Các biến dạng ký tự trong dữ liệu
character_variables <- names(df)[sapply(df, is.character)]

# Hai biến nhị phân dạng 0/1 cũng mang ý nghĩa định tính
binary_categorical_variables <- c("is_canceled", "is_repeated_guest")

character_variables
 [1] "hotel"                   "arrival_date_month"     
 [3] "meal"                    "country"                
 [5] "market_segment"          "distribution_channel"   
 [7] "reserved_room_type"      "assigned_room_type"     
 [9] "deposit_type"            "agent"                  
[11] "company"                 "customer_type"          
[13] "reservation_status"      "reservation_status_date"
binary_categorical_variables
[1] "is_canceled"       "is_repeated_guest"

Trong phân tích chính, ba biến định tính được sử dụng là:

Biến Loại biến định tính Ý nghĩa
hotel Nominal Loại khách sạn: City Hotel hoặc Resort Hotel
is_canceled Nominal nhị phân Đặt phòng có bị hủy hay không
customer_type Nominal Nhóm khách hàng đặt phòng

3.4 5. Kiểm tra và xử lý giá trị thiếu

na_summary <- data.frame(
  Variable = names(df),
  Missing = colSums(is.na(df))
) |>
  filter(Missing > 0) |>
  arrange(desc(Missing))

kable(na_summary, caption = "Các biến có giá trị thiếu trong dữ liệu gốc")
Các biến có giá trị thiếu trong dữ liệu gốc
Variable Missing
children children 4
# Kiểm tra riêng ba biến sử dụng trong phân tích
analysis_na <- data.frame(
  Variable = c("hotel", "is_canceled", "customer_type"),
  Missing = colSums(is.na(df[c("hotel", "is_canceled", "customer_type")]))
)

kable(analysis_na, caption = "Giá trị thiếu ở các biến dùng cho phân tích")
Giá trị thiếu ở các biến dùng cho phân tích
Variable Missing
hotel hotel 0
is_canceled is_canceled 0
customer_type customer_type 0
# Chỉ loại bỏ NA trên các biến thực sự tham gia phân tích
# nhằm tránh làm mất quan sát do NA ở các biến không liên quan như company, agent.
df_analysis <- df |>
  drop_na(hotel, is_canceled, customer_type)

nrow(df_analysis)
[1] 119390

Dữ liệu có NA ở một số biến như company, agent, countrychildren. Tuy nhiên, ba biến trọng tâm hotel, is_canceled, customer_type không có giá trị thiếu, nên dữ liệu phân tích vẫn giữ nguyên 119390 quan sát. Việc không xóa NA ở những biến không sử dụng giúp tránh làm giảm cỡ mẫu không cần thiết.

3.5 6. Chuyển is_canceled thành factor có nhãn

df_analysis <- df_analysis |>
  mutate(
    is_canceled = factor(
      is_canceled,
      levels = c(0, 1),
      labels = c("Not Canceled", "Canceled")
    ),
    hotel = factor(hotel),
    customer_type = factor(customer_type)
  )

levels(df_analysis$is_canceled)
[1] "Not Canceled" "Canceled"    
table(df_analysis$is_canceled)

Not Canceled     Canceled 
       75166        44224 

3.6 7. Bảng tần số một chiều

hotel_frequency <- table(df_analysis$hotel)
customer_frequency <- table(df_analysis$customer_type)

hotel_frequency

  City Hotel Resort Hotel 
       79330        40060 
customer_frequency

       Contract           Group       Transient Transient-Party 
           4076             577           89613           25124 
hotel_frequency_table <- as.data.frame(hotel_frequency) |>
  rename(Hotel = Var1, Frequency = Freq) |>
  mutate(Percentage = scales::percent(Frequency / sum(Frequency), accuracy = 0.01))

customer_frequency_table <- as.data.frame(customer_frequency) |>
  rename(Customer_Type = Var1, Frequency = Freq) |>
  mutate(Percentage = scales::percent(Frequency / sum(Frequency), accuracy = 0.01))

kable(hotel_frequency_table, caption = "Bảng tần số loại khách sạn")
Bảng tần số loại khách sạn
Hotel Frequency Percentage
City Hotel 79330 66.45%
Resort Hotel 40060 33.55%
kable(customer_frequency_table, caption = "Bảng tần số loại khách hàng")
Bảng tần số loại khách hàng
Customer_Type Frequency Percentage
Contract 4076 3.41%
Group 577 0.48%
Transient 89613 75.06%
Transient-Party 25124 21.04%

4 Phần C — Phân tích Thống kê Chính (CLO2, CLO5)

4.1 8. Bảng chéo giữa loại khách sạn và tình trạng hủy phòng

hotel_cancel_table <- table(df_analysis$hotel, df_analysis$is_canceled)
hotel_cancel_table
              
               Not Canceled Canceled
  City Hotel          46228    33102
  Resort Hotel        28938    11122
kable(
  addmargins(hotel_cancel_table),
  caption = "Bảng chéo giữa loại khách sạn và tình trạng hủy phòng"
)
Bảng chéo giữa loại khách sạn và tình trạng hủy phòng
Not Canceled Canceled Sum
City Hotel 46228 33102 79330
Resort Hotel 28938 11122 40060
Sum 75166 44224 119390

4.2 9. Tỷ lệ phần trăm theo hàng

hotel_row_percent <- prop.table(hotel_cancel_table, margin = 1) * 100
round(hotel_row_percent, 2)
              
               Not Canceled Canceled
  City Hotel          58.27    41.73
  Resort Hotel        72.24    27.76
kable(
  round(hotel_row_percent, 2),
  caption = "Tỷ lệ hủy phòng theo từng loại khách sạn (%)"
)
Tỷ lệ hủy phòng theo từng loại khách sạn (%)
Not Canceled Canceled
City Hotel 58.27 41.73
Resort Hotel 72.24 27.76

Kết quả tỷ lệ theo hàng cho thấy:

  • City Hotel: tỷ lệ hủy phòng khoảng 41,73%.
  • Resort Hotel: tỷ lệ hủy phòng khoảng 27,76%.

Sự khác biệt ban đầu quan sát được là City Hotel có tỷ lệ hủy phòng cao hơn Resort Hotel khoảng 13,96 điểm phần trăm.

4.3 10. Kiểm định độc lập Chi-square cho hotel × is_canceled

4.3.1 Giả thuyết kiểm định

  • (H_0): Loại khách sạn và tình trạng hủy phòng độc lập với nhau.
  • (H_1): Loại khách sạn và tình trạng hủy phòng có mối liên hệ với nhau.
# Dữ liệu rất lớn và tần số kỳ vọng đều lớn, sử dụng Pearson Chi-square
# không hiệu chỉnh liên tục để thống nhất với việc tính Cramér's V.
hotel_chisq <- chisq.test(hotel_cancel_table, correct = FALSE)
hotel_chisq

    Pearson's Chi-squared test

data:  hotel_cancel_table
X-squared = 2225.5, df = 1, p-value < 2.2e-16

Kết quả kiểm định cho bảng hotel × is_canceled:

  • (^2 )
  • (df = 1)
  • (p < 2.2 ^{-16})

Do P-value rất nhỏ hơn mức ý nghĩa 5%, ta bác bỏ (H_0). Có bằng chứng thống kê cho thấy loại khách sạn có mối liên hệ với quyết định hủy phòng.

4.4 11. Kiểm tra tần số kỳ vọng

hotel_expected <- hotel_chisq$expected

kable(
  round(hotel_expected, 2),
  caption = "Tần số kỳ vọng cho bảng hotel × is_canceled"
)
Tần số kỳ vọng cho bảng hotel × is_canceled
Not Canceled Canceled
City Hotel 49944.88 29385.12
Resort Hotel 25221.12 14838.88
min(hotel_expected)
[1] 14838.88
all(hotel_expected >= 5)
[1] TRUE

Tần số kỳ vọng nhỏ nhất xấp xỉ 14.838,88, lớn hơn 5 rất nhiều. Như vậy, điều kiện áp dụng kiểm định Chi-square được đảm bảo tốt.

4.5 12. Bảng chéo thứ hai: customer_type × is_canceled

customer_cancel_table <- table(df_analysis$customer_type, df_analysis$is_canceled)
customer_cancel_table
                 
                  Not Canceled Canceled
  Contract                2814     1262
  Group                    518       59
  Transient              53099    36514
  Transient-Party        18735     6389
kable(
  addmargins(customer_cancel_table),
  caption = "Bảng chéo giữa loại khách hàng và tình trạng hủy phòng"
)
Bảng chéo giữa loại khách hàng và tình trạng hủy phòng
Not Canceled Canceled Sum
Contract 2814 1262 4076
Group 518 59 577
Transient 53099 36514 89613
Transient-Party 18735 6389 25124
Sum 75166 44224 119390
customer_row_percent <- prop.table(customer_cancel_table, margin = 1) * 100

kable(
  round(customer_row_percent, 2),
  caption = "Tỷ lệ hủy phòng theo từng loại khách hàng (%)"
)
Tỷ lệ hủy phòng theo từng loại khách hàng (%)
Not Canceled Canceled
Contract 69.04 30.96
Group 89.77 10.23
Transient 59.25 40.75
Transient-Party 74.57 25.43

Từ bảng tỷ lệ theo hàng, nhóm Transient có tỷ lệ hủy phòng cao nhất, khoảng 40,75%; trong khi nhóm Group có tỷ lệ hủy thấp nhất, khoảng 10,23%.

4.6 13. Kiểm định Chi-square cho customer_type × is_canceled

4.6.1 Giả thuyết kiểm định

  • (H_0): Loại khách hàng và tình trạng hủy phòng độc lập với nhau.
  • (H_1): Loại khách hàng và tình trạng hủy phòng có mối liên hệ với nhau.
customer_chisq <- chisq.test(customer_cancel_table, correct = FALSE)
customer_chisq

    Pearson's Chi-squared test

data:  customer_cancel_table
X-squared = 2222.5, df = 3, p-value < 2.2e-16
kable(
  round(customer_chisq$expected, 2),
  caption = "Tần số kỳ vọng cho bảng customer_type × is_canceled"
)
Tần số kỳ vọng cho bảng customer_type × is_canceled
Not Canceled Canceled
Contract 2566.18 1509.82
Group 363.27 213.73
Transient 56418.89 33194.11
Transient-Party 15817.66 9306.34
min(customer_chisq$expected)
[1] 213.7302
all(customer_chisq$expected >= 5)
[1] TRUE

Kết quả kiểm định cho bảng customer_type × is_canceled:

  • (^2 )
  • (df = 3)
  • (p < 2.2 ^{-16})

Tần số kỳ vọng nhỏ nhất xấp xỉ 213,73, lớn hơn 5. Do đó, điều kiện Chi-square tiếp tục được thỏa mãn và có bằng chứng cho thấy loại khách hàng có mối liên hệ với tình trạng hủy phòng.

4.7 14. Tính Cramér’s V và so sánh độ mạnh mối liên hệ

P-value cho biết liệu có bằng chứng về mối liên hệ hay không, nhưng không phản ánh đầy đủ độ mạnh của mối liên hệ. Vì vậy, sử dụng thêm Cramér’s V.

hotel_assoc <- assocstats(hotel_cancel_table)
customer_assoc <- assocstats(customer_cancel_table)

hotel_assoc
                    X^2 df P(> X^2)
Likelihood Ratio 2278.1  1        0
Pearson          2225.5  1        0

Phi-Coefficient   : 0.137 
Contingency Coeff.: 0.135 
Cramer's V        : 0.137 
customer_assoc
                    X^2 df P(> X^2)
Likelihood Ratio 2339.2  3        0
Pearson          2222.5  3        0

Phi-Coefficient   : NA 
Contingency Coeff.: 0.135 
Cramer's V        : 0.136 
cramer_comparison <- data.frame(
  Yeu_to = c("Loại khách sạn (hotel)", "Loại khách hàng (customer_type)"),
  Cramers_V = c(hotel_assoc$cramer, customer_assoc$cramer)
) |>
  arrange(desc(Cramers_V))

kable(
  cramer_comparison |>
    mutate(Cramers_V = round(Cramers_V, 4)),
  caption = "So sánh hệ số Cramér's V"
)
So sánh hệ số Cramér’s V
Yeu_to Cramers_V
Loại khách sạn (hotel) 0.1365
Loại khách hàng (customer_type) 0.1364

Kết quả dự kiến:

Yếu tố Cramér’s V
Loại khách sạn (hotel) 0,1365
Loại khách hàng (customer_type) 0,1364

Cả hai Cramér’s V đều xấp xỉ 0,136, cho thấy mối liên hệ có ý nghĩa thống kê nhưng độ mạnh ở mức yếu. Loại khách sạn có Cramér’s V nhỉnh hơn rất nhỏ so với loại khách hàng; chênh lệch này không đáng kể về mặt thực tiễn.Loại khách sạn và loại khách hàng đều có liên hệ đáng kể về mặt thống kê với tình trạng hủy phòng. Tuy nhiên, mức độ liên hệ tương đối yếu, cho thấy doanh nghiệp không nên chỉ dựa vào hai biến này để dự đoán hoặc kiểm soát hành vi hủy phòng. Cần kết hợp thêm các yếu tố khác như thời gian đặt trước, giá phòng, tiền đặt cọc, kênh đặt phòng và số lần hủy trước đó.

5 Phần D — Trực quan hóa và Báo cáo (CLO2, CLO5)

5.1 15. Biểu đồ cột chồng theo tỷ lệ hủy phòng

ggplot(df_analysis, aes(x = hotel, fill = is_canceled)) +
  geom_bar(position = "fill") +
  scale_y_continuous(labels = scales::percent_format(accuracy = 1)) +
  scale_fill_manual(
    values = c("Not Canceled" = "#2A9D8F", "Canceled" = "#E76F51"),
    name = "Booking Status"
  ) +
  labs(
    title = "Tỷ lệ hủy phòng theo loại khách sạn",
    subtitle = "Nguồn dữ liệu: Hotel Booking Demand",
    x = "Loại khách sạn",
    y = "Tỷ lệ đặt phòng"
  ) +
  theme_minimal(base_size = 12) +
  theme(
    plot.title = element_text(face = "bold"),
    legend.position = "top"
  )

5.2 16. Diễn giải kết quả và đề xuất kinh doanh

Bảng chéo và biểu đồ cột chồng cho thấy tỷ lệ hủy phòng có sự khác biệt rõ giữa hai loại khách sạn. Cụ thể, City Hotel có tỷ lệ hủy khoảng 41,73%, cao hơn đáng kể so với 27,76%Resort Hotel. Kiểm định độc lập Chi-square cho kết quả (p < 2.2 ^{-16}), nhỏ hơn mức ý nghĩa 5%, nên bác bỏ giả thuyết hai biến độc lập. Nói cách khác, trong bộ dữ liệu này, loại khách sạn có mối liên hệ có ý nghĩa thống kê với tình trạng hủy phòng. Tuy nhiên, Cramér’s V của mối liên hệ này chỉ khoảng 0,1365, cho thấy mức độ liên hệ thực tế còn yếu; do cỡ mẫu rất lớn, một khác biệt tỷ lệ vừa phải cũng có thể dẫn đến P-value cực nhỏ. Vì vậy, nhà quản lý không nên kết luận rằng loại khách sạn là nguyên nhân duy nhất gây hủy phòng. Về quản trị, City Hotel nên ưu tiên các chính sách giảm hủy như yêu cầu đặt cọc hợp lý ở giai đoạn cao điểm, gửi nhắc lịch và xác nhận trước ngày nhận phòng, đồng thời phân tích sâu hơn các yếu tố như thời gian đặt trước, kênh phân phối và loại khách hàng. Với nhóm Transient, là nhóm có tỷ lệ hủy cao nhất, khách sạn có thể thử nghiệm ưu đãi đổi ngày thay vì hủy hoàn toàn để bảo vệ doanh thu và công suất phòng.

6 Phần E — Prompt AI Nâng cao

6.1 Prompt 1 — Tình huống thực tế theo ngành

Prompt đã sử dụng:

Tôi đang học về Bảng ngẫu nhiên và Kiểm định Chi-square trong môn Phân tích Dữ liệu Định tính.
Hãy tạo cho tôi một bài toán thực tế trong lĩnh vực Ngân hàng (Ví dụ: Sự độc lập giữa Trình độ học vấn và Khả năng vỡ nợ). Bài toán phải có: (1) bảng chéo 2 chiều giả định, (2) phân tích bằng lời về tần số quan sát vs kỳ vọng, (3) gợi ý code R để chạy chisq.test().

Tóm tắt kết quả trao đổi với AI:

AI đề xuất bài toán ngân hàng kiểm tra mối liên hệ giữa trình độ học vấn và tình trạng vỡ nợ. Bảng giả định có thể gồm ba nhóm học vấn và hai trạng thái vỡ nợ:

Trình độ học vấn Không vỡ nợ Vỡ nợ Tổng
THPT trở xuống 120 30 150
Cao đẳng/Đại học 260 40 300
Sau đại học 135 15 150
Tổng 515 85 600

AI giải thích rằng nếu hai biến độc lập, số trường hợp vỡ nợ kỳ vọng ở mỗi nhóm được tính từ tổng hàng và tổng cột. Nếu số vỡ nợ quan sát của nhóm THPT cao hơn đáng kể giá trị kỳ vọng, đây là dấu hiệu hai biến có thể liên hệ. Code R gợi ý:

bank_table <- matrix(
  c(120, 30,
    260, 40,
    135, 15),
  nrow = 3,
  byrow = TRUE,
  dimnames = list(
    Education = c("High school or below", "College/University", "Postgraduate"),
    Default = c("No default", "Default")
  )
)

bank_test <- chisq.test(bank_table)
bank_test
bank_test$expected

6.2 Prompt 2 — Phản biện kỹ thuật và lỗi thường gặp

Prompt đã sử dụng:

Khi tôi chạy chisq.test() trong R trên một tập dữ liệu rất lớn (n > 50,000), tôi thấy P-value luôn cực kỳ nhỏ (p < 0.001) dù nhìn bằng mắt thường thì sự khác biệt tỷ lệ giữa 2 nhóm không nhiều. 
Hãy giải thích:
(1) Tại sao cỡ mẫu (n) quá lớn lại làm P-value luôn nhỏ?
(2) Khái niệm 'Ý nghĩa thống kê' (Statistical significance) khác với 'Ý nghĩa thực tiễn' (Practical significance) trong trường hợp này như thế nào?
(3) Tôi nên dùng chỉ số nào bổ sung (như Cramér's V) để đo lường độ mạnh của mối liên hệ?

Tóm tắt kết quả trao đổi với AI:

AI giải thích rằng thống kê Chi-square tăng khi độ lệch giữa tần số quan sát và kỳ vọng tăng, nhưng với cỡ mẫu rất lớn, ngay cả sai khác tỷ lệ nhỏ cũng có thể tạo ra thống kê kiểm định lớn và P-value cực nhỏ. Vì vậy, ý nghĩa thống kê chỉ cho thấy dữ liệu không phù hợp với giả thuyết độc lập, trong khi ý nghĩa thực tiễn yêu cầu xem mức độ khác biệt có đủ lớn để thay đổi quyết định kinh doanh hay không. AI khuyến nghị báo cáo thêm Cramér’s V để đánh giá độ mạnh mối liên hệ. Trong bài hiện tại, dù hai P-value đều cực nhỏ, Cramér’s V chỉ khoảng 0,136, nên kết luận phù hợp là mối liên hệ tồn tại nhưng tương đối yếu.

7 Phần F — AI-Grader Prompt và Tự đánh giá

7.1 Prompt đánh giá

Tôi vừa hoàn thành bài tập R về Lập bảng ngẫu nhiên và Kiểm định Chi-square. Hãy đánh giá code và kết quả của tôi theo 4 tiêu chí sau (thang điểm 10):

1. Tính đúng đắn thống kê (CLO2): Việc tính tỷ lệ theo hàng (`prop.table(..., margin=1)`) có đúng không? Thống kê Chi-square tính ra có chính xác không?
2. Kiểm tra điều kiện (CLO2): Có thực hiện bước trích xuất tần số kỳ vọng (Expected Frequencies) để kiểm tra điều kiện lớn hơn 5 không?
3. Chất lượng diễn giải (CLO5): Có phân biệt được ý nghĩa của P-value và Cramér's V khi đưa ra kết luận kinh doanh không?
4. Trực quan hóa (CLO2): Biểu đồ cột có chuẩn `position = "fill"` để so sánh tỷ lệ không?

CODE R VÀ OUTPUT CỦA BÀI LÀM:

1. Lập bảng chéo và tính tỷ lệ theo hàng cho hotel × is_canceled

Code R:

hotel_cancel_table <- table(df_analysis$hotel, df_analysis$is_canceled)
hotel_cancel_table

hotel_row_percent <- prop.table(hotel_cancel_table, margin = 1) * 100
round(hotel_row_percent, 2)

Output:

            Not Canceled  Canceled

City Hotel             46228     33102
Resort Hotel           28938     11122

            Not Canceled  Canceled

City Hotel             58.27     41.73
Resort Hotel           72.24     27.76

2. Chạy kiểm định Chi-square cho hotel × is_canceled

Code R:

hotel_chisq <- chisq.test(hotel_cancel_table, correct = FALSE)
hotel_chisq

Output:

Pearson's Chi-squared test

data:  hotel_cancel_table
X-squared = 2225.5, df = 1, p-value < 2.2e-16

3. Kiểm tra tần số kỳ vọng của kiểm định hotel × is_canceled

Code R:

hotel_chisq$expected
min(hotel_chisq$expected)
all(hotel_chisq$expected >= 5)

Output:
            Not Canceled  Canceled

City Hotel          49944.88  29385.12
Resort Hotel        25221.12  14838.88

[1] 14838.88
[1] TRUE

4. Lập bảng chéo và tính tỷ lệ theo hàng cho customer_type × is_canceled

Code R:

customer_cancel_table <- table(df_analysis$customer_type, df_analysis$is_canceled)
customer_cancel_table

customer_row_percent <- prop.table(customer_cancel_table, margin = 1) * 100
round(customer_row_percent, 2)

Output:
             Not Canceled  Canceled

Contract                 2814      1262
Group                     518        59
Transient               53099     36514
Transient-Party         18735      6389

             Not Canceled  Canceled

Contract                69.04     30.96
Group                   89.77     10.23
Transient               59.25     40.75
Transient-Party         74.57     25.43

5. Chạy kiểm định Chi-square và kiểm tra điều kiện cho customer_type × is_canceled

Code R:

customer_chisq <- chisq.test(customer_cancel_table, correct = FALSE)
customer_chisq

customer_chisq$expected
min(customer_chisq$expected)
all(customer_chisq$expected >= 5)

Output:

Pearson's Chi-squared test

data:  customer_cancel_table
X-squared = 2222.5, df = 3, p-value < 2.2e-16


             Not Canceled  Canceled

Contract              2566.18   1509.82
Group                  363.27    213.73
Transient            56418.89  33194.11
Transient-Party      15817.66   9306.34

[1] 213.73
[1] TRUE

6. Tính Cramér's V để đánh giá độ mạnh của mối liên hệ

Code R:

hotel_assoc <- assocstats(hotel_cancel_table)
customer_assoc <- assocstats(customer_cancel_table)

hotel_assoc$cramer
customer_assoc$cramer

Output:

Cramer's V của hotel × is_canceled:
[1] 0.1365313

Cramer's V của customer_type × is_canceled:
[1] 0.1364386

7. Vẽ biểu đồ so sánh tỷ lệ hủy phòng theo loại khách sạn

Code R:

ggplot(df_analysis, aes(x = hotel, fill = is_canceled)) +
geom_bar(position = "fill") +
scale_y_continuous(labels = scales::percent_format(accuracy = 1)) +
labs(
title = "Tỷ lệ hủy phòng theo loại khách sạn",
x = "Loại khách sạn",
y = "Tỷ lệ đặt phòng",
fill = "Tình trạng đặt phòng"
) +
theme_minimal()

NHẬN XÉT ĐỂ AI-GRADER ĐÁNH GIÁ:

Bài làm đã sử dụng đúng bảng chéo và tính đúng tỷ lệ theo hàng bằng prop.table(..., margin = 1). Kết quả cho thấy City Hotel có tỷ lệ hủy phòng 41,73%, cao hơn Resort Hotel với 27,76%. Kiểm định Chi-square cho cả hai bảng đều có p-value < 2.2e-16, cho thấy có mối liên hệ có ý nghĩa thống kê giữa tình trạng hủy phòng với loại khách sạn và loại khách hàng. Tần số kỳ vọng nhỏ nhất của cả hai kiểm định đều lớn hơn 5, nên điều kiện áp dụng Chi-square được đảm bảo. Tuy nhiên, Cramér's V của cả hai mối liên hệ chỉ xấp xỉ 0,136, cho thấy mức độ liên hệ tương đối yếu về mặt thực tiễn dù kết quả có ý nghĩa thống kê. Biểu đồ sử dụng đúng geom_bar(position = "fill") để so sánh tỷ lệ hủy phòng giữa các loại khách sạn.

7.2 Tự đánh giá theo rubric

Tiêu chí Minh chứng trong bài Điểm tự đánh giá
Tính đúng đắn thống kê Tạo đúng hai bảng chéo; tính tỷ lệ theo hàng bằng prop.table(..., margin = 1); chạy chisq.test() cho cả hai bảng. 2.5/2.5
Kiểm tra điều kiện Trích xuất $expected; kiểm tra min()all(expected >= 5) cho cả hai kiểm định. 2.5/2.5
Chất lượng diễn giải Kết luận dựa trên P-value; bổ sung Cramér’s V để tránh đánh đồng ý nghĩa thống kê với ý nghĩa thực tiễn; đề xuất quản trị phù hợp. 2.5/2.5
Trực quan hóa Dùng geom_bar(position = "fill"), nhãn trục, tiêu đề, chú giải và tỷ lệ phần trăm rõ ràng. 2.5/2.5
Tổng điểm tự đánh giá Bài đáp ứng đầy đủ các yêu cầu trong đề. 10.0/10.0

7.3 Kết luận chung

Bộ dữ liệu Hotel Booking Demand cung cấp bằng chứng thống kê rất mạnh rằng cả loại khách sạnloại khách hàng đều liên hệ với tình trạng hủy phòng. Tuy vậy, hệ số Cramér’s V cho thấy độ mạnh mối liên hệ ở mức yếu, nên quyết định kinh doanh cần kết hợp thêm các biến giải thích khác thay vì chỉ dựa vào loại khách sạn hoặc loại khách hàng.