Code
knitr::opts_chunk$set(echo = TRUE, warning = FALSE, message = FALSE)knitr::opts_chunk$set(echo = TRUE, warning = FALSE, message = FALSE)(CLO2)library(tidyverse)
library(ggplot2)
library(scales)Bộ dữ liệu Telco Customer Churn là bộ dữ liệu khách hàng viễn thông thường được sử dụng trong phân tích hành vi rời bỏ dịch vụ. Dữ liệu gồm khoảng 7.043 quan sát và 21 biến, phản ánh các đặc điểm nhân khẩu học, dịch vụ khách hàng đang sử dụng, thông tin hợp đồng, phương thức thanh toán và trạng thái rời mạng (Churn). Trong bối cảnh quản trị doanh nghiệp viễn thông, việc mô tả và mã hóa dữ liệu định tính có vai trò rất quan trọng vì phần lớn các biến có dạng nhóm như giới tính, loại hợp đồng, phương thức thanh toán, dịch vụ internet, bảo mật trực tuyến hoặc trạng thái rời mạng. Nếu các biến này được phân loại, làm sạch và mã hóa đúng, doanh nghiệp có thể nhận diện nhóm khách hàng có rủi ro rời bỏ cao, từ đó xây dựng chính sách chăm sóc khách hàng, giữ chân thuê bao và tối ưu hóa doanh thu dài hạn.
(CLO1, CLO2)# Cách 1: Đọc online trực tiếp từ github đã kiểm chứng (để chạy thử nhanh)
# url <- "https://raw.githubusercontent.com/IBM/telco-customer-churn-on-icp4d/master/data/Telco-Customer-Churn.csv"
# df <- read.csv(url)
# Cách 2: Đọc offline từ thư mục Data của dự án (bắt buộc dùng khi nộp bài)
data_path <- "../../Data/Telco-Customer-Churn.csv"
if (!file.exists(data_path)) {
data_path <- "C:/Users/Dell/Downloads/Telco-Customer-Churn.csv"
}
df <- read.csv(data_path)str(df)'data.frame': 7043 obs. of 21 variables:
$ customerID : chr "7590-VHVEG" "5575-GNVDE" "3668-QPYBK" "7795-CFOCW" ...
$ gender : chr "Female" "Male" "Male" "Male" ...
$ SeniorCitizen : int 0 0 0 0 0 0 0 0 0 0 ...
$ Partner : chr "Yes" "No" "No" "No" ...
$ Dependents : chr "No" "No" "No" "No" ...
$ tenure : int 1 34 2 45 2 8 22 10 28 62 ...
$ PhoneService : chr "No" "Yes" "Yes" "No" ...
$ MultipleLines : chr "No phone service" "No" "No" "No phone service" ...
$ InternetService : chr "DSL" "DSL" "DSL" "DSL" ...
$ OnlineSecurity : chr "No" "Yes" "Yes" "Yes" ...
$ OnlineBackup : chr "Yes" "No" "Yes" "No" ...
$ DeviceProtection: chr "No" "Yes" "No" "Yes" ...
$ TechSupport : chr "No" "No" "No" "Yes" ...
$ StreamingTV : chr "No" "No" "No" "No" ...
$ StreamingMovies : chr "No" "No" "No" "No" ...
$ Contract : chr "Month-to-month" "One year" "Month-to-month" "One year" ...
$ PaperlessBilling: chr "Yes" "No" "Yes" "No" ...
$ PaymentMethod : chr "Electronic check" "Mailed check" "Mailed check" "Bank transfer (automatic)" ...
$ MonthlyCharges : num 29.9 57 53.9 42.3 70.7 ...
$ TotalCharges : num 29.9 1889.5 108.2 1840.8 151.7 ...
$ Churn : chr "No" "No" "Yes" "No" ...
head(df, 10) customerID gender SeniorCitizen Partner Dependents tenure PhoneService
1 7590-VHVEG Female 0 Yes No 1 No
2 5575-GNVDE Male 0 No No 34 Yes
3 3668-QPYBK Male 0 No No 2 Yes
4 7795-CFOCW Male 0 No No 45 No
5 9237-HQITU Female 0 No No 2 Yes
6 9305-CDSKC Female 0 No No 8 Yes
7 1452-KIOVK Male 0 No Yes 22 Yes
8 6713-OKOMC Female 0 No No 10 No
9 7892-POOKP Female 0 Yes No 28 Yes
10 6388-TABGU Male 0 No Yes 62 Yes
MultipleLines InternetService OnlineSecurity OnlineBackup
1 No phone service DSL No Yes
2 No DSL Yes No
3 No DSL Yes Yes
4 No phone service DSL Yes No
5 No Fiber optic No No
6 Yes Fiber optic No No
7 Yes Fiber optic No Yes
8 No phone service DSL Yes No
9 Yes Fiber optic No No
10 No DSL Yes Yes
DeviceProtection TechSupport StreamingTV StreamingMovies Contract
1 No No No No Month-to-month
2 Yes No No No One year
3 No No No No Month-to-month
4 Yes Yes No No One year
5 No No No No Month-to-month
6 Yes No Yes Yes Month-to-month
7 No No Yes No Month-to-month
8 No No No No Month-to-month
9 Yes Yes Yes Yes Month-to-month
10 No No No No One year
PaperlessBilling PaymentMethod MonthlyCharges TotalCharges Churn
1 Yes Electronic check 29.85 29.85 No
2 No Mailed check 56.95 1889.50 No
3 Yes Mailed check 53.85 108.15 Yes
4 No Bank transfer (automatic) 42.30 1840.75 No
5 Yes Electronic check 70.70 151.65 Yes
6 Yes Electronic check 99.65 820.50 Yes
7 Yes Credit card (automatic) 89.10 1949.40 No
8 No Mailed check 29.75 301.90 No
9 Yes Electronic check 104.80 3046.05 Yes
10 No Bank transfer (automatic) 56.15 3487.95 No
dplyr::glimpse(df)Rows: 7,043
Columns: 21
$ customerID <chr> "7590-VHVEG", "5575-GNVDE", "3668-QPYBK", "7795-CFOCW…
$ gender <chr> "Female", "Male", "Male", "Male", "Female", "Female",…
$ SeniorCitizen <int> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,…
$ Partner <chr> "Yes", "No", "No", "No", "No", "No", "No", "No", "Yes…
$ Dependents <chr> "No", "No", "No", "No", "No", "No", "Yes", "No", "No"…
$ tenure <int> 1, 34, 2, 45, 2, 8, 22, 10, 28, 62, 13, 16, 58, 49, 2…
$ PhoneService <chr> "No", "Yes", "Yes", "No", "Yes", "Yes", "Yes", "No", …
$ MultipleLines <chr> "No phone service", "No", "No", "No phone service", "…
$ InternetService <chr> "DSL", "DSL", "DSL", "DSL", "Fiber optic", "Fiber opt…
$ OnlineSecurity <chr> "No", "Yes", "Yes", "Yes", "No", "No", "No", "Yes", "…
$ OnlineBackup <chr> "Yes", "No", "Yes", "No", "No", "No", "Yes", "No", "N…
$ DeviceProtection <chr> "No", "Yes", "No", "Yes", "No", "Yes", "No", "No", "Y…
$ TechSupport <chr> "No", "No", "No", "Yes", "No", "No", "No", "No", "Yes…
$ StreamingTV <chr> "No", "No", "No", "No", "No", "Yes", "Yes", "No", "Ye…
$ StreamingMovies <chr> "No", "No", "No", "No", "No", "Yes", "No", "No", "Yes…
$ Contract <chr> "Month-to-month", "One year", "Month-to-month", "One …
$ PaperlessBilling <chr> "Yes", "No", "Yes", "No", "Yes", "Yes", "Yes", "No", …
$ PaymentMethod <chr> "Electronic check", "Mailed check", "Mailed check", "…
$ MonthlyCharges <dbl> 29.85, 56.95, 53.85, 42.30, 70.70, 99.65, 89.10, 29.7…
$ TotalCharges <dbl> 29.85, 1889.50, 108.15, 1840.75, 151.65, 820.50, 1949…
$ Churn <chr> "No", "No", "Yes", "No", "Yes", "Yes", "No", "No", "Y…
Ba hàm trên đều dùng để khám phá dữ liệu nhưng phục vụ các mục đích khác nhau. Hàm str(df) cho biết cấu trúc tổng quát của bộ dữ liệu, bao gồm số dòng, số cột, kiểu dữ liệu của từng biến và một vài giá trị đầu tiên. Hàm này phù hợp khi cần kiểm tra nhanh kiểu dữ liệu trước khi phân tích. Hàm head(df, 10) hiển thị 10 dòng đầu tiên của dữ liệu, giúp quan sát trực tiếp dữ liệu ở dạng bảng, kiểm tra tên biến, định dạng giá trị và phát hiện các vấn đề ban đầu như ký tự lạ hoặc khoảng trắng. Trong khi đó, dplyr::glimpse(df) là cách xem dữ liệu hiện đại hơn trong tidyverse, trình bày theo chiều ngang gọn gàng, rất hữu ích với dữ liệu có nhiều cột. Trong thực hành, nên dùng kết hợp cả ba: str() để kiểm tra cấu trúc, head() để xem dữ liệu thực tế, và glimpse() để có cái nhìn tổng quan dễ đọc hơn.
| Tên biến | Kiểu hiện tại trong R | Thang đo thực tế | Phân loại dữ liệu |
|---|---|---|---|
gender |
Character | Định danh (Nominal) | Định tính nhị phân (Binary qualitative) |
Contract |
Character | Thứ bậc (Ordinal) | Định tính nhiều nhóm có thứ tự |
PaymentMethod |
Character | Định danh (Nominal) | Định tính nhiều nhóm không thứ tự |
Churn |
Character | Định danh (Nominal) | Định tính nhị phân, biến mục tiêu |
Biến gender chỉ phản ánh nhóm giới tính và không có quan hệ lớn hơn hay nhỏ hơn giữa các mức, do đó thuộc thang đo định danh. Biến Contract gồm Month-to-month, One year, Two year, có thể hiểu theo mức độ cam kết thời gian tăng dần, vì vậy phù hợp với thang đo thứ bậc. Biến PaymentMethod là phương thức thanh toán, các nhóm chỉ khác nhau về loại hình chứ không có trật tự tự nhiên. Biến Churn gồm Yes và No, phản ánh khách hàng có rời mạng hay không, là biến định tính nhị phân quan trọng nhất trong bài toán.
# Kiểm tra nhanh các giá trị rỗng/khoảng trắng trong TotalCharges
sum(trimws(df$TotalCharges) == "")[1] NA
# Chuyển khoảng trắng thành NA cho TotalCharges
df$TotalCharges <- as.numeric(as.character(df$TotalCharges))
# Kiểm tra lại số lượng NA trên mỗi cột
colSums(is.na(df)) customerID gender SeniorCitizen Partner
0 0 0 0
Dependents tenure PhoneService MultipleLines
0 0 0 0
InternetService OnlineSecurity OnlineBackup DeviceProtection
0 0 0 0
TechSupport StreamingTV StreamingMovies Contract
0 0 0 0
PaperlessBilling PaymentMethod MonthlyCharges TotalCharges
0 0 0 11
Churn
0
# Loại bỏ các hàng chứa NA
df_clean <- df |> tidyr::drop_na()
# Kiểm tra kích thước dữ liệu trước và sau làm sạch
dim(df)[1] 7043 21
dim(df_clean)[1] 7032 21
Trong dữ liệu Telco, biến TotalCharges có một số giá trị là khoảng trắng " ". Đây là vấn đề nguy hiểm vì R không tự xem khoảng trắng là NA ngay từ đầu. Nếu không chuyển các khoảng trắng này thành NA, việc tính toán hoặc mô hình hóa sau đó có thể bị sai kiểu dữ liệu, ví dụ TotalCharges bị hiểu là biến ký tự thay vì biến số. Khi phân tích dữ liệu định tính, xử lý các giá trị ẩn này cũng rất quan trọng vì dữ liệu bẩn có thể làm sai bảng tần số, tỷ lệ phần trăm, kiểm định tỷ lệ và các kết luận quản trị. Sau khi chuyển TotalCharges sang dạng numeric, các khoảng trắng được nhận diện thành NA, sau đó dùng drop_na() để loại bỏ các dòng không đầy đủ.
# Chuyển sang factor cho biến Nominal
df_clean$gender <- as.factor(df_clean$gender)
df_clean$Churn <- as.factor(df_clean$Churn)
# Chuyển sang ordered factor cho biến Ordinal
df_clean$Contract <- factor(
df_clean$Contract,
levels = c("Month-to-month", "One year", "Two year"),
ordered = TRUE
)
# Kiểm tra lại các mức của biến
levels(df_clean$gender)[1] "Female" "Male"
levels(df_clean$Churn)[1] "No" "Yes"
levels(df_clean$Contract)[1] "Month-to-month" "One year" "Two year"
is.ordered(df_clean$Contract)[1] TRUE
Nếu không khai báo tham số levels cụ thể, R thường sắp xếp các mức của biến factor theo thứ tự chữ cái. Với biến Contract, cách sắp xếp chữ cái không phản ánh đúng bản chất thứ bậc của thời hạn hợp đồng. Ví dụ, về mặt kinh doanh, Month-to-month thể hiện mức cam kết thấp nhất, tiếp theo là One year, và Two year là mức cam kết cao nhất. Nếu R tự sắp xếp theo chữ cái hoặc theo thứ tự xuất hiện mà không có kiểm soát, các phân tích hoặc mô hình có sử dụng yếu tố thứ bậc có thể hiểu sai chiều tăng của mức cam kết hợp đồng, dẫn đến diễn giải sai về rủi ro rời mạng.
summary(df_clean) customerID gender SeniorCitizen Partner
Length:7032 Female:3483 Min. :0.0000 Length:7032
Class :character Male :3549 1st Qu.:0.0000 Class :character
Mode :character Median :0.0000 Mode :character
Mean :0.1624
3rd Qu.:0.0000
Max. :1.0000
Dependents tenure PhoneService MultipleLines
Length:7032 Min. : 1.00 Length:7032 Length:7032
Class :character 1st Qu.: 9.00 Class :character Class :character
Mode :character Median :29.00 Mode :character Mode :character
Mean :32.42
3rd Qu.:55.00
Max. :72.00
InternetService OnlineSecurity OnlineBackup DeviceProtection
Length:7032 Length:7032 Length:7032 Length:7032
Class :character Class :character Class :character Class :character
Mode :character Mode :character Mode :character Mode :character
TechSupport StreamingTV StreamingMovies Contract
Length:7032 Length:7032 Length:7032 Month-to-month:3875
Class :character Class :character Class :character One year :1472
Mode :character Mode :character Mode :character Two year :1685
PaperlessBilling PaymentMethod MonthlyCharges TotalCharges
Length:7032 Length:7032 Min. : 18.25 Min. : 18.8
Class :character Class :character 1st Qu.: 35.59 1st Qu.: 401.4
Mode :character Mode :character Median : 70.35 Median :1397.5
Mean : 64.80 Mean :2283.3
3rd Qu.: 89.86 3rd Qu.:3794.7
Max. :118.75 Max. :8684.8
Churn
No :5163
Yes:1869
# Bảng cơ cấu giới tính và loại hợp đồng
table(df_clean$gender)
Female Male
3483 3549
round(prop.table(table(df_clean$gender)) * 100, 2)
Female Male
49.53 50.47
table(df_clean$Contract)
Month-to-month One year Two year
3875 1472 1685
round(prop.table(table(df_clean$Contract)) * 100, 2)
Month-to-month One year Two year
55.11 20.93 23.96
Sau khi làm sạch, cơ cấu giới tính trong dữ liệu khá cân bằng giữa nam và nữ. Điều này giúp các mô tả về hành vi rời mạng ít bị lệch do mất cân đối nghiêm trọng về giới. Về loại hợp đồng, nhóm Month-to-month chiếm tỷ trọng lớn nhất, trong khi One year và Two year có quy mô nhỏ hơn. Đây là đặc điểm đáng chú ý vì khách hàng dùng hợp đồng ngắn hạn thường có mức cam kết thấp hơn, dễ thay đổi nhà cung cấp hơn và có thể đóng góp đáng kể vào tỷ lệ rời mạng chung của doanh nghiệp.
(CLO1, CLO2, CLO5)Churnfreq_churn <- table(df_clean$Churn)
prop_churn <- round(prop.table(freq_churn) * 100, 2)
print(freq_churn)
No Yes
5163 1869
print(prop_churn)
No Yes
73.42 26.58
Từ kết quả phân tích, số khách hàng rời bỏ dịch vụ (Churn = Yes) là 1.869 trên tổng 7.032 khách hàng sau làm sạch, tương ứng khoảng 26,58%. Đây là một tỷ lệ đáng chú ý trong quản trị khách hàng viễn thông. Nếu hơn một phần tư khách hàng rời bỏ dịch vụ, doanh nghiệp không chỉ mất doanh thu hiện tại mà còn phải tăng chi phí marketing để thu hút khách hàng mới thay thế. Về góc độ tài chính, tỷ lệ churn cao có thể làm suy giảm giá trị vòng đời khách hàng, giảm dòng tiền kỳ vọng và gây áp lực lên chiến lược tăng trưởng dài hạn.
n <- nrow(df_clean)
x <- freq_churn["Yes"]
ci_95 <- prop.test(x = x, n = n, conf.level = 0.95)
ci_95
1-sample proportions test with continuity correction
data: x out of n, null probability 0.5
X-squared = 1542.1, df = 1, p-value < 2.2e-16
alternative hypothesis: true p is not equal to 0.5
95 percent confidence interval:
0.2555198 0.2763077
sample estimates:
p
0.265785
Khoảng tin cậy 95% cho tỷ lệ khách hàng rời bỏ cho biết khoảng giá trị hợp lý của tỷ lệ churn trong tổng thể khách hàng tương tự. Với dữ liệu này, tỷ lệ mẫu khoảng 26,58%, và khoảng tin cậy 95% thường nằm quanh mức xấp xỉ 25,55% đến 27,63% khi dùng kiểm định xấp xỉ. Về mặt quản trị, điều này cho thấy tỷ lệ rời mạng thực tế của doanh nghiệp có khả năng dao động quanh mức hơn 1/4 tổng khách hàng, không phải là một hiện tượng nhỏ lẻ. Doanh nghiệp cần xem churn là một vấn đề chiến lược, đặc biệt cần tập trung vào các nhóm hợp đồng ngắn hạn và phương thức thanh toán có rủi ro cao.
Giả thuyết kiểm định:
test_26 <- prop.test(x = x, n = n, p = 0.26, alternative = "two.sided")
test_26
1-sample proportions test with continuity correction
data: x out of n, null probability 0.26
X-squared = 1.1933, df = 1, p-value = 0.2747
alternative hypothesis: true p is not equal to 0.26
95 percent confidence interval:
0.2555198 0.2763077
sample estimates:
p
0.265785
Nếu p-value lớn hơn 0,05, ta chưa có đủ bằng chứng thống kê để bác bỏ giả thuyết tỷ lệ churn bằng 26%. Nếu p-value nhỏ hơn hoặc bằng 0,05, ta bác bỏ (H_0) và kết luận tỷ lệ churn khác 26%. Với dữ liệu Telco sau làm sạch, tỷ lệ mẫu 26,58% khá gần với mức mục tiêu 26%, vì vậy kết luận cần dựa trực tiếp vào p-value từ prop.test(). Về mặt kinh doanh, dù kết quả có thể không quá khác biệt so với 26%, mức churn quanh 26% vẫn là một tín hiệu rủi ro cần quản trị, vì nó cho thấy cứ khoảng 4 khách hàng thì có hơn 1 khách hàng rời bỏ dịch vụ.
Contract và Churncross_tab <- table(df_clean$Contract, df_clean$Churn)
prop_tab_row <- round(prop.table(cross_tab, margin = 1) * 100, 2)
print(cross_tab)
No Yes
Month-to-month 2220 1655
One year 1306 166
Two year 1637 48
print(prop_tab_row)
No Yes
Month-to-month 57.29 42.71
One year 88.72 11.28
Two year 97.15 2.85
Kết quả bảng chéo cho thấy nhóm khách hàng sử dụng hợp đồng Month-to-month có tỷ lệ rời mạng cao nhất, khoảng 42,71%. Trong khi đó, nhóm One year có tỷ lệ churn khoảng 11,28% và nhóm Two year chỉ khoảng 2,85%. Điều này gợi ý rằng mức độ cam kết hợp đồng có quan hệ rất rõ với rủi ro rời bỏ dịch vụ. Về chiến lược marketing và chăm sóc khách hàng, doanh nghiệp nên tập trung nhiều hơn vào nhóm hợp đồng tháng, ví dụ cung cấp ưu đãi chuyển đổi sang hợp đồng dài hạn, chương trình tích điểm, cam kết giá, hoặc gói dịch vụ kèm lợi ích bổ sung để giảm khả năng rời mạng.
PaymentMethod# Chuẩn bị dữ liệu vẽ Pareto
pareto_data <- df_clean |>
count(PaymentMethod, name = "tan_so") |>
arrange(desc(tan_so)) |>
mutate(
pct = tan_so / sum(tan_so) * 100,
cum_pct = cumsum(pct)
)
pareto_data PaymentMethod tan_so pct cum_pct
1 Electronic check 2365 33.63197 33.63197
2 Mailed check 1604 22.81001 56.44198
3 Bank transfer (automatic) 1542 21.92833 78.37031
4 Credit card (automatic) 1521 21.62969 100.00000
ggplot(pareto_data, aes(x = reorder(PaymentMethod, -tan_so))) +
geom_col(aes(y = tan_so), fill = "#1E3A8A", alpha = 0.85) +
geom_line(aes(y = cum_pct * max(tan_so) / 100, group = 1),
color = "#DC2626", linewidth = 1.2) +
geom_point(aes(y = cum_pct * max(tan_so) / 100),
color = "#DC2626", size = 3) +
scale_y_continuous(
name = "Tần số khách hàng",
sec.axis = sec_axis(~ . * 100 / max(pareto_data$tan_so),
name = "Tỷ lệ tích lũy (%)")
) +
labs(title = "Biểu đồ Pareto cho Phương thức thanh toán của Khách hàng",
x = "Phương thức thanh toán",
caption = "Nguồn: IBM Telco Customer Churn") +
theme_minimal(base_size = 12) +
theme(axis.text.x = element_text(angle = 15, hjust = 1))Biểu đồ Pareto cho thấy Electronic check là phương thức thanh toán có số lượng khách hàng lớn nhất, tiếp theo là Mailed check, Bank transfer (automatic) và Credit card (automatic). Ba phương thức đầu tiên cộng dồn khoảng gần 80% khách hàng. Điều này giúp doanh nghiệp xác định các phương thức thanh toán chủ đạo để ưu tiên phân tích sâu hơn. Nếu một phương thức thanh toán vừa có tỷ trọng lớn vừa có tỷ lệ churn cao, đây sẽ là nhóm cần được ưu tiên trong chính sách kiểm soát rủi ro và chăm sóc khách hàng.
# Label Encoding cho biến Contract
df_clean$Contract_label <- as.integer(df_clean$Contract)
# One-hot Encoding cho biến PaymentMethod
# model.matrix tự động loại bỏ cột intercept nếu khai báo công thức dạng ~ biến - 1
ohe_matrix <- model.matrix(~ PaymentMethod - 1, data = df_clean)
head(df_clean |> select(Contract, Contract_label)) Contract Contract_label
1 Month-to-month 1
2 One year 2
3 Month-to-month 1
4 One year 2
5 Month-to-month 1
6 Month-to-month 1
head(ohe_matrix) PaymentMethodBank transfer (automatic) PaymentMethodCredit card (automatic)
1 0 0
2 0 0
3 0 0
4 1 0
5 0 0
6 0 0
PaymentMethodElectronic check PaymentMethodMailed check
1 1 0
2 0 1
3 0 1
4 0 0
5 1 0
6 1 0
Sử dụng Label Encoding cho một biến định danh phi thứ bậc như PaymentMethod là sai lầm nghiêm trọng khi xây dựng mô hình hồi quy, vì cách mã hóa này tạo ra một trật tự giả giữa các nhóm. Ví dụ, nếu gán Electronic check = 1, Mailed check = 2, Credit card = 3, Bank transfer = 4, mô hình có thể hiểu rằng phương thức số 4 “lớn hơn” phương thức số 1, hoặc khoảng cách giữa 1 và 2 tương đương khoảng cách giữa 3 và 4. Điều này không đúng về bản chất thống kê vì các phương thức thanh toán chỉ là các nhóm định danh, không có thứ tự tự nhiên.
Ngược lại, biến Contract có thể áp dụng mã hóa thứ bậc vì các mức hợp đồng có ý nghĩa cam kết tăng dần: Month-to-month thấp nhất, One year cao hơn, và Two year cao nhất. Khi đó, Label Encoding có thể phản ánh phần nào mức độ cam kết của khách hàng. Tuy nhiên, trong mô hình hồi quy, vẫn cần cân nhắc mục tiêu phân tích: nếu muốn mô hình linh hoạt hơn, vẫn có thể dùng biến giả cho Contract; nếu muốn nhấn mạnh xu hướng thứ bậc của thời hạn hợp đồng, ordered factor hoặc mã hóa thứ bậc là hợp lý.
Prompt đã sử dụng:
Tôi đang học phân tích dữ liệu định tính bằng R cho bài toán dự báo khách hàng rời bỏ dịch vụ viễn thông (Telco Customer Churn). Hãy tạo cho tôi một tình huống thực tế trong đó Giám đốc Kinh doanh yêu cầu giải quyết bài toán: (1) Phân tích xem phương thức thanh toán (PaymentMethod) và loại hợp đồng (Contract) ảnh hưởng như thế nào đến tỷ lệ rời mạng (Churn). (2) Cho biết tại sao việc áp dụng One-hot encoding lại cần loại bỏ 1 cột cơ sở để tránh bẫy đa cộng tuyến (Dummy Variable Trap). (3) Viết đoạn code R mẫu chạy hồi quy logistic đơn giản glm(Churn ~ Contract + PaymentMethod, family = binomial) và cách giải thích hệ số hồi quy của các nhóm so với nhóm cơ sở.
Tóm tắt kết quả hội thoại:
AI giải thích rằng trong bối cảnh doanh nghiệp viễn thông, Giám đốc Kinh doanh có thể yêu cầu phân tích các yếu tố làm tăng xác suất khách hàng rời mạng để thiết kế chính sách giữ chân khách hàng. Hai nhóm biến quan trọng là Contract và PaymentMethod. Loại hợp đồng phản ánh mức độ cam kết của khách hàng: hợp đồng tháng thường linh hoạt hơn nên khả năng rời bỏ cao hơn, trong khi hợp đồng một năm hoặc hai năm thể hiện cam kết dài hạn nên churn thấp hơn. Phương thức thanh toán cũng có thể liên quan đến hành vi khách hàng, đặc biệt nếu một nhóm thanh toán có tỷ lệ churn cao bất thường. AI cũng nhấn mạnh khi dùng one-hot encoding trong hồi quy cần chọn một nhóm làm cơ sở để tránh Dummy Variable Trap, vì nếu đưa tất cả biến giả cùng với intercept vào mô hình sẽ gây đa cộng tuyến hoàn hảo. Mã R phù hợp là dùng glm(Churn ~ Contract + PaymentMethod, family = binomial, data = df_clean) và diễn giải hệ số theo odds so với nhóm cơ sở.
# Ví dụ mô hình logistic đơn giản theo gợi ý từ Prompt 1
df_model <- df_clean |>
mutate(Churn_binary = ifelse(Churn == "Yes", 1, 0))
logit_model <- glm(
Churn_binary ~ Contract + PaymentMethod,
data = df_model,
family = binomial
)
summary(logit_model)
Call:
glm(formula = Churn_binary ~ Contract + PaymentMethod, family = binomial,
data = df_model)
Coefficients:
Estimate Std. Error z value Pr(>|z|)
(Intercept) -2.14933 0.08505 -25.272 <2e-16 ***
Contract.L -2.08911 0.10766 -19.404 <2e-16 ***
Contract.Q 0.11233 0.09155 1.227 0.2198
PaymentMethodCredit card (automatic) -0.06531 0.10694 -0.611 0.5414
PaymentMethodElectronic check 0.78012 0.08697 8.970 <2e-16 ***
PaymentMethodMailed check -0.19612 0.10038 -1.954 0.0507 .
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
(Dispersion parameter for binomial family taken to be 1)
Null deviance: 8143.4 on 7031 degrees of freedom
Residual deviance: 6550.2 on 7026 degrees of freedom
AIC: 6562.2
Number of Fisher Scoring iterations: 6
exp(coef(logit_model)) (Intercept) Contract.L
0.1165617 0.1237972
Contract.Q PaymentMethodCredit card (automatic)
1.1188800 0.9367807
PaymentMethodElectronic check PaymentMethodMailed check
2.1817435 0.8219160
Prompt đã sử dụng:
Trong bộ dữ liệu viễn thông thực tế, tỷ lệ khách hàng rời mạng (Churn = Yes) thường chỉ chiếm tỷ lệ nhỏ (khoảng 26%) so với nhóm ở lại (Churn = No). Hiện tượng này gọi là Mất cân bằng dữ liệu (Imbalanced Data). Hãy giải thích cho tôi: (1) Tại sao khi dữ liệu định tính bị mất cân bằng nghiêm trọng, các mô hình phân loại truyền thống lại có xu hướng dự báo sai lệch (thiên vị nhóm đa số)? (2) Có các kỹ thuật phổ biến nào trong R để xử lý vấn đề này (ví dụ: SMOTE, Down-sampling, Up-sampling)? (3) Cách đánh giá hiệu quả mô hình lúc này bằng ROC/AUC thay vì chỉ dùng Accuracy.
Tóm tắt kết quả hội thoại:
AI giải thích rằng khi dữ liệu phân loại bị mất cân bằng, mô hình có xu hướng ưu tiên dự báo nhóm đa số vì điều đó giúp đạt accuracy cao nhưng không nhất thiết hữu ích trong kinh doanh. Trong bài toán churn, nếu phần lớn khách hàng là Churn = No, mô hình có thể dự báo hầu hết khách hàng là không rời mạng và vẫn có accuracy tương đối cao, nhưng lại bỏ sót nhóm khách hàng rời mạng - nhóm mà doanh nghiệp cần phát hiện nhất. Các kỹ thuật xử lý phổ biến gồm down-sampling nhóm đa số, up-sampling nhóm thiểu số và SMOTE để tạo thêm quan sát tổng hợp cho nhóm thiểu số. AI cũng nhấn mạnh không nên chỉ đánh giá bằng accuracy mà cần xem thêm sensitivity, specificity, precision, recall, F1-score và đặc biệt là ROC/AUC. ROC/AUC giúp đánh giá khả năng phân biệt giữa hai nhóm ở nhiều ngưỡng xác suất khác nhau, phù hợp hơn với dữ liệu mất cân bằng.
Prompt tự chấm đã sử dụng:
Tôi vừa hoàn thành bài tập R về phân loại dữ liệu định tính, bảng tần số, xử lý khoảng trắng ẩn, mã hóa biến và Pareto chart trong môn Phân tích Dữ liệu Định tính. Hãy đóng vai AI-Grader đánh giá bài làm của tôi theo các tiêu chí thang điểm 10: tính đúng đắn thống kê, chất lượng diễn giải, tiêu chuẩn dữ liệu thực tế và hiểu biết mã hóa.
Kết quả tự chấm và nhận xét tóm tắt:
Bài làm được AI-Grader đánh giá khoảng 9,2/10. Về tính đúng đắn thống kê, bài sử dụng đúng các hàm cơ bản như table(), prop.table(), prop.test(), dplyr::count() và ggplot2 để vẽ Pareto. Về chất lượng diễn giải, bài không chỉ trình bày con số mà còn liên hệ với rủi ro rời mạng, doanh thu, giữ chân khách hàng và chiến lược marketing. Về tiêu chuẩn dữ liệu thực tế, bài đã xử lý đúng vấn đề khoảng trắng ẩn trong TotalCharges bằng cách chuyển sang numeric để tạo NA, sau đó loại bỏ bằng drop_na(). Về hiểu biết mã hóa, bài phân biệt rõ biến định danh và biến thứ bậc, giải thích vì sao PaymentMethod không nên dùng label encoding và vì sao Contract có thể được xem là biến có thứ tự. Điểm cần cải thiện là có thể bổ sung thêm bảng trình bày đẹp hơn và phân tích sâu hơn tỷ lệ churn theo từng phương thức thanh toán.
Bài tập Buổi 02 đã thực hiện đầy đủ các yêu cầu chính: thiết lập Quarto, nạp dữ liệu, khám phá cấu trúc dữ liệu, phân loại thang đo biến định tính, xử lý missing values, chuyển đổi factor, thống kê mô tả biến Churn, tính khoảng tin cậy, kiểm định tỷ lệ, lập bảng chéo, vẽ Pareto chart, thực hiện mã hóa biến và phản biện về phương pháp mã hóa. Kết quả cho thấy tỷ lệ khách hàng rời mạng trong bộ dữ liệu Telco là khoảng 26,58%, trong đó nhóm hợp đồng tháng có rủi ro rời mạng cao nhất. Đây là cơ sở quan trọng để doanh nghiệp ưu tiên chính sách giữ chân khách hàng ở các nhóm có mức cam kết thấp.