library(tidyverse)
library(gmodels)
library(vcd)
library(knitr)Bài Tập Buổi 04 — Bảng ngẫu nhiên & Kiểm định độc lập cơ bản
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
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òng và 32 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")| 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")| 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, country và children. 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")| 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")| 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"
)| 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 (%)"
)| 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"
)| 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"
)| 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 (%)"
)| 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"
)| 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"
)| 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$expected6.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() và 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ạn và loạ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.