CHƯƠNG 1: BỘ DỮ LIỆU CUSTOMER CHURN DATASET FOR LIFE INSURANCE INDUSTRY
## Warning: package 'readxl' was built under R version 4.4.3
Đoạn mã R được sử dụng để nhập bộ dữ liệu Customen từ tệp Excel vào môi trường làm việc của R để chuẩn bị cho quá trình phân tích
Bộ dữ liệu được sử dụng trong tiểu luận này được thu thập từ nền tảng Kaggle, một kho lưu trữ dữ liệu trực tuyến phổ biến, được sử dụng rộng rãi trong các nghiên cứu học thuật và dự án phân tích dữ liệu. Bộ dữ liệu phản ánh thông tin của khách hàng trong lĩnh vực bảo hiểm nhân thọ, bao gồm nhiều đặc điểm nhân khẩu học, hành vi tiêu dùng và tình trạng rời bỏ dịch vụ (churn).
Việc sử dụng bộ dữ liệu này trong nghiên cứu nhằm phân tích và xác định các yếu tố có ảnh hưởng đáng kể đến khả năng rời bỏ của khách hàng, từ đó xây dựng mô hình dự báo churn có độ chính xác cao. Kết quả phân tích không chỉ giúp doanh nghiệp nhận diện nhóm khách hàng có nguy cơ rời bỏ, mà còn là cơ sở để đề xuất các chính sách chăm sóc, duy trì và phát triển khách hàng hiệu quả hơn. Ngoài ra, nghiên cứu cũng mang ý nghĩa thực tiễn khi thể hiện ứng dụng của phân tích dữ liệu trong ngành bảo hiểm nhân thọ, góp phần nâng cao hiệu quả hoạt động và năng lực cạnh tranh của doanh nghiệp trên thị trường.
## Warning: package 'knitr' was built under R version 4.4.3
variable_meaning <- data.frame(
Variable = c(
"Index",
"Customer Name",
"Customer Address",
"Company Name",
"Claim Reason",
"Data confidentiality",
"Claim Amount",
"Category Premium",
"Premium/Amount Ratio",
"Claim Request output",
"BMI",
"Churn"
),
Meaning = c(
"Số thứ tự của khách hàng trong bộ dữ liệu",
"Tên khách hàng",
"Địa chỉ khách hàng",
"Tên công ty bảo hiểm quản lý hợp đồng",
"Lý do khách hàng yêu cầu bồi thường",
"Mức độ bảo mật dữ liệu của khách hàng",
"Số tiền khách hàng yêu cầu bồi thường",
"Mức phí bảo hiểm của khách hàng",
"Tỷ lệ giữa phí bảo hiểm và số tiền yêu cầu bồi thường",
"Kết quả xử lý yêu cầu bồi thường ",
"Chỉ số khối cơ thể",
"Khách hàng có rời công ty bảo hiểm hay không"
),
stringsAsFactors = FALSE
)
kable(variable_meaning, col.names = c("Variable", "Meaning"))| Variable | Meaning |
|---|---|
| Index | Số thứ tự của khách hàng trong bộ dữ liệu |
| Customer Name | Tên khách hàng |
| Customer Address | Địa chỉ khách hàng |
| Company Name | Tên công ty bảo hiểm quản lý hợp đồng |
| Claim Reason | Lý do khách hàng yêu cầu bồi thường |
| Data confidentiality | Mức độ bảo mật dữ liệu của khách hàng |
| Claim Amount | Số tiền khách hàng yêu cầu bồi thường |
| Category Premium | Mức phí bảo hiểm của khách hàng |
| Premium/Amount Ratio | Tỷ lệ giữa phí bảo hiểm và số tiền yêu cầu bồi thường |
| Claim Request output | Kết quả xử lý yêu cầu bồi thường |
| BMI | Chỉ số khối cơ thể |
| Churn | Khách hàng có rời công ty bảo hiểm hay không |
# Tạo đối tượng tibble chứa thông tin tóm tắt
library(tibble)
data_types_summary <- tibble(
Ten_Cot = c("Index", "Customer Name", "Customer Address", "Company Name",
"Claim Reason", "Data confidentiality", "Claim Amount",
"Category Premium", "Premium/Amount Ratio", "Claim Request output",
"BMI", "Churn"),
Loai_Du_lieu_trong_R = c("num", "chr", "chr", "chr", "chr",
"chr", "num", "num", "num", "chr",
"num", "chr")
)
# Tải gói knitr và hiển thị bảng
library(knitr)
kable(data_types_summary,
caption = "Bảng Tóm Tắt Loại Dữ liệu",
col.names = c("Tên Cột", "Loại Dữ liệu trong R (R Type)")
)| Tên Cột | Loại Dữ liệu trong R (R Type) |
|---|---|
| Index | num |
| Customer Name | chr |
| Customer Address | chr |
| Company Name | chr |
| Claim Reason | chr |
| Data confidentiality | chr |
| Claim Amount | num |
| Category Premium | num |
| Premium/Amount Ratio | num |
| Claim Request output | chr |
| BMI | num |
| Churn | chr |
->Có 5 biến định lương (num) và 7 biến định tính(chr).
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 0 50000 100000 100000 149999 199999
-> Đây là biến số thứ tự, nó không mang ý nghĩa thống kê
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 1 245 1390 1120 1844 2299
-> Giá trị Trung vị (Median = 1390) cao hơn đáng kể so với Giá trị Trung bình (Mean = 1120). Sự chênh lệch này (\(\text{Median} > \text{Mean}\)) chỉ ra rằng phân bố dữ liệu bị lệch trái.
Kết luận: Phần lớn các yêu cầu bồi thường đều có giá trị ở mức cao (gần \(1390\) đến \(2299\)). Giá trị Trung bình bị kéo xuống thấp hơn do sự tồn tại của một lượng nhỏ các yêu cầu có giá trị rất thấp (gần \(1\)). Điều này cho thấy công ty bảo hiểm phải xử lý một lượng lớn các yêu cầu bồi thường có giá trị lớn.
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 399 1875 14390 8964 14390 14390
-> Phân tích thống kê mô tả cho biến Category Premium cho thấy một sự phân bố cực kỳ bất thường và lệch trái nghiêm trọng. Sự trùng khớp giữa Giá trị Trung vị (\(\text{Median} = 14390\)), Tứ phân vị thứ ba (\(\mathbf{3^{rd} Qu.} = 14390\)) và Giá trị Cao nhất (\(\mathbf{Max.} = 14390\)) chỉ ra rằng phần lớn (ít nhất \(\mathbf{75\%}\) ) các khách hàng đều đóng mức phí bảo hiểm cao nhất là \(14390\).
Giá trị Trung bình (\(\mathbf{Mean} = 8964\)) thấp hơn Trung vị rất nhiều, bị kéo xuống bởi một lượng nhỏ các khách hàng đóng phí thấp (chỉ từ \(\mathbf{399}\) đến \(1875\)). Điều này gợi ý rằng mô hình phí bảo hiểm đang có tính tập trung cao vào mức phí tối đa, hoặc hầu hết khách hàng thuộc nhóm phí cao nhất.
# Chuyển đổi biến từ kiểu character sang kiểu numeric
df$'Premium/Amount Ratio' <- as.numeric(df$'Premium/Amount Ratio')
# Tóm tắt biến Premium/Amount Ratio
summary(df$`Premium/Amount Ratio`)## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 0.002506 0.106741 0.125122 0.125024 0.143155 0.248120
-> Giá trị Trung vị (Median = 0.125122) và Giá trị Trung bình (Mean = 0.125024) gần như bằng nhau (\(\text{Median} \approx \text{Mean}\)). Điều này chỉ ra rằng sự phân bố tỷ lệ là rất đối xứng (không bị lệch đáng kể).
Khoảng phân tán (Range): Tỷ lệ nằm trong khoảng hẹp, từ tối thiểu \(\mathbf{0.0025}\) đến tối đa \(\mathbf{0.248}\).
Kết luận: Phần lớn các quan sát có tỷ lệ phí bảo hiểm trên số tiền yêu cầu bồi thường tập trung rất sát mức \(\mathbf{12.5\%}\) (\(0.125\)). Điều này cho thấy có một sự ổn định hoặc tính đồng nhất cao trong cách công ty bảo hiểm định giá phí so với rủi ro tiềm ẩn (số tiền bồi thường).
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 18.00 20.00 23.00 23.01 26.00 28.00
-> Biến BMI có phân bố rất đối xứng, được xác định bởi sự trùng khớp giữa Giá trị Trung bình (\(\text{Mean} = 23.01\)) và Giá trị Trung vị (\(\text{Median} = 23.00\)).BMI của khách hàng dao động trong khoảng từ \(\mathbf{18.00}\) (thấp nhất) đến \(\mathbf{28.00}\) (cao nhất). Phần lớn chỉ số BMI tập trung chặt chẽ xung quanh mức \(23.00\), với \(50\%\) số khách hàng có BMI nằm trong khoảng \(\mathbf{20.00}\) đến \(\mathbf{26.00}\) (khoảng tứ phân vị).
Kết quả này cho thấy, về tổng thể, khách hàng trong bộ dữ liệu có chỉ số khối cơ thể tương đối đồng nhất và nằm chủ yếu trong phạm vi cân nặng khỏe mạnh đến thừa cân nhẹ, không có trường hợp BMI quá cực đoan.
Các biến Customer Name, Customer Address và Company Name không mang ý nghĩa thống kê
##
## Medical Other Phone Travel
## 109863 30187 30016 29934
-> Medical (109863): Có 109863 trường hợp yêu cầu bồi thường liên quan đến lý do Y tế/Sức khỏe.
Other (30187): Có 30187 trường hợp yêu cầu bồi thường thuộc các lý do Khác (không được phân loại cụ thể).
Phone (30016): Có 30016 trường hợp yêu cầu bồi thường liên quan đến Điện thoại (có thể là hư hỏng, mất mát thiết bị, hoặc các vấn đề liên quan đến dịch vụ).
Travel (29934): Có 29934 trường hợp yêu cầu bồi thường liên quan đến Du lịch (có thể là hủy chuyến, mất hành lý, trễ giờ, v.v.).
Lý do phổ biến nhất là Medical, chiếm số lượng áp đảo so với các lý do khác. Điều này cho thấy các yêu cầu bồi thường y tế là vấn đề lớn nhất trong dữ liệu này.
Ba loại còn lại (Other, Phone, Travel) có số lượng khá tương đương nhau, dao động quanh mức 30000 trường hợp.
##
## High Low Medium Very low
## 109863 29934 30016 30187
-> Dữ liệu cho thấy có một sự phân cực rõ rệt: phần lớn các bản ghi được coi là có mức độ bảo mật Cao, trong khi ba mức độ còn lại (Low, Medium, Very low) có số lượng bản ghi tương đương nhau và thấp hơn đáng kể so với mức High.
##
## No Yes
## 192994 7006
-> Kết quả cho thấy một sự mất cân bằng rất lớn trong dữ liệu.
Số lượng yêu cầu bị từ chối (No) (199994) lớn hơn rất nhiều so với số lượng yêu cầu được chấp thuận (Yes) (7006).
##
## No Yes
## 72728 127272
Kết quả cho thấy số lượng khách hàng rời bỏ (Yes) (127272) lớn hơn đáng kể so với số lượng khách hàng duy trì (72728).
Điều này chỉ ra rằng công ty đang đối mặt với một tỷ lệ khách hàng rời bỏ rất cao.
## Index Customer Name Customer_Address
## 0 0 0
## Company Name Claim Reason Data confidentiality
## 0 0 0
## Claim Amount Category Premium Premium/Amount Ratio
## 0 0 0
## Claim Request output BMI Churn
## 0 0 0
Bộ dữ liệu không có giá trị bị thiếu
## Warning: package 'dplyr' was built under R version 4.4.3
##
## Attaching package: 'dplyr'
## The following objects are masked from 'package:stats':
##
## filter, lag
## The following objects are masked from 'package:base':
##
## intersect, setdiff, setequal, union
## Warning: package 'readr' was built under R version 4.4.3
# 1. Tách Bang/Tiểu bang
data_step_1 <- data_start %>%
mutate(State = str_extract(Customer_Address, pattern = "(?<=,\\s)[A-Z]{2}(?=\\s\\d{5})|(?<=,\\s)[A-Z]{2}(?=\\d{5})")) %>%
mutate(State = ifelse(is.na(State), "Unknown", State))
data_step_1# 2. Làm sạch Cột (Xóa cột Address và Index)
data_step_2 <- data_step_1 %>%
select(-Index, -Customer_Address)
data_step_2# Sử dụng str_to_title() để chuẩn hóa văn bản
data_step_3 <- data_step_2 %>%
mutate('Company Name' = str_to_title(`Company Name`))
data_step_3# 4. Đổi tên cột (Chuẩn hóa)
data_step_4 <- data_step_3 %>%
rename(Customer_Name = 'Customer Name',
Company_Name='Company Name',
Claim_Request_Output = 'Claim Request output',
Data_Confidentiality = 'Data confidentiality',
Claim_Reason = 'Claim Reason',
Claim_Amount = 'Claim Amount',
Category_Premium = 'Category Premium')
data_step_4# 5. Chuyển kiểu dữ liệu (Claim Amount sang Integer)
data_step_5 <- data_step_4 %>%
mutate(Claim_Amount = as.integer(Claim_Amount))
data_step_5# 6. Mã hóa Nhị phân cho Churn
data_step_6 <- data_step_5 %>%
mutate(Churn_Encoded = ifelse(Churn == "Yes", 1, 0))
data_step_6# 7. Mã hóa Thứ bậc cho Data_Confidentiality
confidentiality_levels <- c("Very low", "Low", "Medium", "High", "Very high")
data_step_7 <- data_step_6 %>%
mutate(Data_Conf_Encoded = factor(Data_Confidentiality,
levels = confidentiality_levels,
labels = 1:5,
ordered = TRUE))
data_step_7# 9. PHÂN NHÓM BMI (Tạo biến phân loại)
# Dựa trên tiêu chuẩn phân loại BMI:
# < 18.5: Dưới cân; 18.5 - 24.9: Bình thường; 25.0 - 29.9: Thừa cân; >= 30: Béo phì
data_step_9 <- data_step_8 %>%
mutate(BMI_Status = cut(BMI,
breaks = c(0, 18.5, 25, 30, Inf), # Các điểm cắt
labels = c("Underweight", "Normal", "Overweight", "Obese"), # Tên của 4 nhóm
include.lowest = TRUE,
right = FALSE))
data_step_9## # A tibble: 3 × 2
## BMI_Status n
## <fct> <int>
## 1 Underweight 18293
## 2 Normal 108979
## 3 Overweight 72728
# 10. Mã hóa cho Claim Reason
reason_dummies <- model.matrix(~ Claim_Reason - 1, data = data_step_9) %>% as_tibble()
data_step_10 <- bind_cols(data_step_9, reason_dummies)
data_step_10 <- data_step_10 %>% rename_with(~ gsub("Claim_Reason", "Reason_", .x), starts_with("Claim_Reason"))
data_step_10-> Thao tác data_step_10 (One-Hot Encoding) có mục đích mã hóa cột lý do yêu cầu bồi thường (Claim_Reason) từ dữ liệu chữ thành dữ liệu số (1 và 0), bằng cách tạo ra các cột nhị phân mới (ví dụ: Reason_Medical, Reason_Travel) để dữ liệu có thể được sử dụng trong các mô hình học máy mà không ngụ ý bất kỳ thứ bậc nào giữa các lý do khác nhau.
## Warning: package 'ggplot2' was built under R version 4.4.3
## corrplot 0.95 loaded
## Warning: package 'scales' was built under R version 4.4.3
##
## Attaching package: 'scales'
## The following object is masked from 'package:readr':
##
## col_factor
churn_rate <- dff %>%
summarise(
Total_Customers = n(),
Churned_Customers = sum(Churn_Encoded),
Churn_Rate = mean(Churn_Encoded) * 100
)
churn_rateggplot(dff, aes(x = Churn, fill = Churn)) +
geom_bar() + # Layer 1: Đồ thị thanh
labs(title = "Phân Bố Khách Hàng Rời Bỏ (Churn)",
x = "Trạng thái Rời bỏ",
y = "Số lượng Khách hàng") + # Layer 2: Tiêu đề và nhãn
scale_fill_manual(values = c("Yes" = "#E41A1C", "No" = "#377EB8")) + # Layer 3: Đổi màu
geom_text(stat = 'count', aes(label = after_stat(count)), vjust = -0.5, size = 4) + # Layer 4: Gắn nhãn số lượng
theme_minimal() # Layer 5: Chủ đề tối giản# 3. Biểu đồ Histogram cho BMI
ggplot(dff, aes(x = BMI)) +
geom_histogram(binwidth = 1, fill = "#4DAF4A", color = "white") + # Layer 1: Histogram, binwidth = 1
labs(title = "Phân Bố Chỉ Số Khối Cơ Thể (BMI)",
x = "Chỉ số BMI",
y = "Tần suất") + # Layer 2: Tiêu đề và nhãn
geom_vline(aes(xintercept = mean(BMI)), color = "red", linetype = "dashed", linewidth = 1) + # Layer 3: Đường trung bình
annotate("text", x = mean(dff$BMI) + 2, y = 30000, label = paste("Mean =", round(mean(dff$BMI), 2)), color = "red") + # Layer 4: Ghi chú trung bình
theme_classic() # Layer 5: Chủ đề cổ điển# 4. Biểu đồ Hộp (Box Plot) Claim Amount theo Churn
ggplot(dff, aes(x = Churn, y = Claim_Amount, fill = Churn)) +
geom_boxplot() + # Layer 1: Biểu đồ hộp
labs(title = "Phân Bố Số Tiền Yêu Cầu Bồi Thường theo Trạng thái Churn",
x = "Khách hàng Rời bỏ",
y = "Số tiền Bồi thường (Claim Amount)") + # Layer 2: Tiêu đề và nhãn
coord_flip() + # Layer 3: Xoay trục
scale_fill_manual(values = c("Yes" = "#E41A1C", "No" = "#377EB8")) + # Layer 4: Đổi màu
theme_bw() # Layer 5: Chủ đề đen trắng# 5. Tỷ lệ Churn theo Data_Confidentiality
churn_by_conf <- dff %>%
group_by(Data_Confidentiality) %>%
summarise(Churn_Rate = mean(Churn_Encoded) * 100) %>%
mutate(Data_Confidentiality = factor(Data_Confidentiality, levels = c("Very low", "Low", "Medium", "High"))) # Đảm bảo thứ tự
ggplot(churn_by_conf, aes(x = Data_Confidentiality, y = Churn_Rate)) +
geom_col(fill = "#FF7F00") + # Layer 1: Đồ thị cột
labs(title = "Tỷ lệ Churn theo Mức độ Bảo mật Dữ liệu",
x = "Mức độ Bảo mật",
y = "Tỷ lệ Churn (%)") + # Layer 2: Tiêu đề và nhãn
geom_text(aes(label = paste0(round(Churn_Rate, 2), "%")), vjust = -0.5, size = 4) + # Layer 3: Gắn nhãn tỷ lệ
scale_y_continuous(limits = c(0, max(churn_by_conf$Churn_Rate) * 1.1)) + # Layer 4: Điều chỉnh trục Y
theme_minimal() # Layer 5: Chủ đề tối giản# 7. Tỷ lệ Churn theo Claim Request Output
churn_by_claim_output <- dff %>%
group_by(Claim_Request_Output) %>%
summarise(Churn_Rate = mean(Churn_Encoded) * 100)
ggplot(churn_by_claim_output, aes(x = Claim_Request_Output, y = Churn_Rate, fill = Claim_Request_Output)) +
geom_col() + # Layer 1: Đồ thị cột
labs(title = "Tỷ lệ Churn khi Yêu cầu Bồi thường được/bị Chấp thuận",
x = "Kết quả Yêu cầu Bồi thường",
y = "Tỷ lệ Churn (%)") + # Layer 2: Tiêu đề và nhãn
scale_fill_manual(values = c("No" = "#E41A1C", "Yes" = "#4DAF4A")) + # Layer 3: Đổi màu
geom_text(aes(label = paste0(round(Churn_Rate, 2), "%")), vjust = -0.5, size = 4) + # Layer 4: Gắn nhãn tỷ lệ
theme_bw() # Layer 5: Chủ đề đen trắng# 8.Số tiền Yêu cầu Bồi thường Trung bình theo Lý do Bồi thường (Sử dụng cột đã mã hóa)
# Tính toán bồi thường trung bình cho từng lý do
mean_claim_by_reason_fixed <- dff %>%
summarise(
Medical = mean(Claim_Amount[Reason_Medical == 1]),
Other = mean(Claim_Amount[Reason_Other == 1]),
Phone = mean(Claim_Amount[Reason_Phone == 1]),
Travel = mean(Claim_Amount[Reason_Travel == 1])
) %>%
# Chuyển đổi từ dạng rộng sang dạng dài để vẽ biểu đồ
tidyr::pivot_longer(cols = everything(),
names_to = "Claim_Reason_Fixed",
values_to = "Mean_Claim_Amount")
mean_claim_by_reason_fixed # Trực quan hóa
ggplot(mean_claim_by_reason_fixed, aes(x = Claim_Reason_Fixed, y = Mean_Claim_Amount, fill = Claim_Reason_Fixed)) +
geom_bar(stat = "identity") + # Layer 1: Đồ thị cột
labs(title = "Số tiền Bồi thường trung bình theo Lý do yêu cầu bồi thường ",
x = "Lý do Yêu cầu Bồi thường",
y = "Số tiền Bồi thường Trung bình") + # Layer 2: Tiêu đề và nhãn
scale_fill_brewer(palette = "Set1") + # Layer 3: Bảng màu
geom_text(aes(label = round(Mean_Claim_Amount, 0)), vjust = -0.5, size = 4) + # Layer 4: Gắn nhãn giá trị
theme_classic() + # Layer 5: Chủ đề cổ điển
theme(axis.text.x = element_text(angle = 45, hjust = 1)) # Xoay nhãn x# Chọn lọc và đảm bảo dữ liệu là số
cor_data <- dff %>%
select(Claim_Amount, Category_Premium, `Premium/Amount Ratio`,
BMI, Claim_Premium_Ratio, Churn_Encoded) %>%
# Bắt buộc chuyển đổi tất cả sang numeric để tránh lỗi 'x' must be numeric
mutate(across(everything(), as.numeric))
# Tính Ma trận tương quan
# Cột 'Premium/Amount Ratio' có dấu cách nên cần dùng backticks (`)
M <- cor(cor_data, use = "complete.obs")
# CHUYỂN SANG BẢNG VÀ HIỂN THỊ KẾT QUẢ
correlation_table <- M %>%
as.data.frame() %>%
round(3) # Làm tròn 3 chữ số thập phân
# In bảng ra console
print(correlation_table)## Claim_Amount Category_Premium Premium/Amount Ratio BMI
## Claim_Amount 1.000 0.959 0.160 0.001
## Category_Premium 0.959 1.000 -0.001 0.001
## Premium/Amount Ratio 0.160 -0.001 1.000 -0.003
## BMI 0.001 0.001 -0.003 1.000
## Claim_Premium_Ratio 0.160 -0.001 1.000 -0.003
## Churn_Encoded 0.001 0.000 0.001 -0.836
## Claim_Premium_Ratio Churn_Encoded
## Claim_Amount 0.160 0.001
## Category_Premium -0.001 0.000
## Premium/Amount Ratio 1.000 0.001
## BMI -0.003 -0.836
## Claim_Premium_Ratio 1.000 0.001
## Churn_Encoded 0.001 1.000
# 11. Tỷ lệ Churn theo BMI_Status
churn_by_bmi <- dff %>%
group_by(BMI_Status) %>%
summarise(Churn_Rate = mean(Churn_Encoded) * 100)
ggplot(churn_by_bmi, aes(x = BMI_Status, y = Churn_Rate, fill = BMI_Status)) +
geom_col() + # Layer 1: Đồ thị cột
labs(title = "Tỷ lệ Churn theo Tình trạng BMI",
x = "Tình trạng BMI",
y = "Tỷ lệ Churn (%)") + # Layer 2: Tiêu đề và nhãn
scale_fill_brewer(palette = "Dark2") + # Layer 3: Bảng màu
geom_text(aes(label = paste0(round(Churn_Rate, 2), "%")), vjust = -0.5, size = 4) + # Layer 4: Gắn nhãn tỷ lệ
theme_void() + # Layer 5: Chủ đề trống
theme(plot.title = element_text(hjust = 0.5)) # Căn giữa tiêu đề# 12. Biểu đồ Violin cho Category Premium theo Churn
ggplot(dff, aes(x = Churn, y = Category_Premium, fill = Churn)) +
geom_violin(trim = FALSE) + # Layer 1: Biểu đồ Violin (trim=FALSE để giữ toàn bộ mật độ)
geom_boxplot(width = 0.1, color = "black", alpha = 0.8) + # Layer 2: Thêm biểu đồ hộp để thấy tứ phân vị
labs(title = "Phân Bố Phí Bảo hiểm theo Trạng thái Churn",
x = "Trạng thái Churn",
y = "Phí Bảo hiểm (Category Premium)") + # Layer 3: Tiêu đề và nhãn
scale_fill_manual(values = c("Yes" = "#E41A1C", "No" = "#377EB8")) + # Layer 4: Đổi màu
theme_minimal() # Layer 5: Chủ đề tối giảnclaim_data_long <- dff %>%
select(Claim_Amount, starts_with("Reason_")) %>%
# Ép kiểu tất cả các cột Reason_... về số
mutate(across(starts_with("Reason_"), as.numeric)) %>%
# Chuyển đổi từ dạng rộng sang dạng dài
tidyr::pivot_longer(
cols = starts_with("Reason_"),
names_to = "Claim_Reason_Fixed",
values_to = "Is_Claim"
) %>%
# Lọc ra chỉ những hàng có yêu cầu bồi thường (Is_Claim = 1)
filter(Is_Claim == 1) %>%
# Làm sạch nhãn
mutate(Claim_Reason_Fixed = gsub("Reason_", "", Claim_Reason_Fixed))## Warning: There was 1 warning in `mutate()`.
## ℹ In argument: `across(starts_with("Reason_"), as.numeric)`.
## Caused by warning:
## ! NAs introduced by coercion
# In bảng thống kê số lượng Claim
print(claim_data_long %>% count(Claim_Reason_Fixed) %>% rename(Num_Claims = n))## # A tibble: 4 × 2
## Claim_Reason_Fixed Num_Claims
## <chr> <int>
## 1 Medical 109863
## 2 Other 30187
## 3 Phone 30016
## 4 Travel 29934
ggplot(claim_data_long, aes(x = Claim_Reason_Fixed, y = Claim_Amount, fill = Claim_Reason_Fixed)) +
geom_boxplot(outlier.shape = NA) + # Layer 1: Biểu đồ hộp (ẩn outlier để gọn)
labs(title = "Phân Bố Claim Amount theo Lý do Yêu cầu",
x = "Lý do Bồi thường",
y = "Số tiền Bồi thường (Claim Amount)") + # Layer 2: Tiêu đề và nhãn
scale_fill_brewer(palette = "Set2") + # Layer 3: Bảng màu
theme_bw() + # Layer 4: Chủ đề đen trắng
theme(legend.position = "none") # Layer 5: Ẩn chú giải# 1. Tính toán Tỷ lệ Churn bằng cách Chuyển đổi dữ liệu và Nhóm
churn_rate_by_reason_fixed <- dff %>%
select(Churn_Encoded, starts_with("Reason_")) %>%
#ÉP KIỂU SỐ
mutate(across(starts_with("Reason_"), as.numeric)) %>%
# Chuyển đổi từ dạng rộng sang dạng dài
tidyr::pivot_longer(
cols = starts_with("Reason_"),
names_to = "Claim_Reason_Fixed",
values_to = "Is_Claim"
) %>%
# Lọc (chỉ giữ lại những hàng có yêu cầu bồi thường) và Làm sạch nhãn
filter(Is_Claim == 1) %>%
mutate(Claim_Reason_Fixed = gsub("Reason_", "", Claim_Reason_Fixed)) %>%
# Nhóm và Tính toán tỷ lệ Churn
group_by(Claim_Reason_Fixed) %>%
summarise(
Churn_Rate = mean(Churn_Encoded) * 100
) %>%
arrange(desc(Churn_Rate))## Warning: There was 1 warning in `mutate()`.
## ℹ In argument: `across(starts_with("Reason_"), as.numeric)`.
## Caused by warning:
## ! NAs introduced by coercion
## # A tibble: 4 × 2
## Claim_Reason_Fixed Churn_Rate
## <chr> <dbl>
## 1 Travel 64.1
## 2 Medical 63.6
## 3 Other 63.6
## 4 Phone 63.3
ggplot(churn_rate_by_reason_fixed,
aes(x = reorder(Claim_Reason_Fixed, -Churn_Rate), y = Churn_Rate, fill = Claim_Reason_Fixed)) +
geom_col() + # Layer 1: Đồ thị cột
labs(title = "Tỷ lệ Churn theo Lý do Yêu cầu Bồi thường ",
x = "Lý do Claim",
y = "Tỷ lệ Churn (%)") + # Layer 2: Tiêu đề và nhãn
scale_fill_brewer(palette = "Accent") + # Layer 3: Bảng màu
geom_text(aes(label = paste0(round(Churn_Rate, 2), "%")), vjust = -0.5) + # Layer 4: Gắn nhãn tỷ lệ
theme_classic() + # Layer 5: Chủ đề cổ điển
theme(legend.position = "none")# 15. Top 10 Bang/Tiểu bang
top_10_states <- dff %>%
count(State, sort = TRUE) %>%
head(10)
ggplot(top_10_states, aes(x = reorder(State, n), y = n, fill = State)) +
geom_col() + # Layer 1: Đồ thị cột
labs(title = "Top 10 Bang/Tiểu bang có nhiều khách hàng",
x = "Bang/Tiểu bang",
y = "Số lượng Khách hàng") + # Layer 2: Tiêu đề và nhãn
coord_flip() + # Layer 3: Xoay trục
scale_fill_hue(l = 50) + # Layer 4: Bảng màu sắc thái
geom_text(aes(label = n), hjust = -0.1, size = 4) + # Layer 5: Gắn nhãn số lượng
theme_light()# Biểu đồ Lưới Nhỏ 4 Biến (Small Multiples Scatter Plot)
ggplot(dff, aes(x = Category_Premium, y = Claim_Amount)) +
geom_point(aes(color = Claim_Premium_Ratio), alpha = 0.3) + # Layer 1: Biểu đồ phân tán (màu theo Ratio)
scale_color_viridis_c() + # Layer 2: Bảng màu Viridis cho Ratio
facet_grid(Claim_Request_Output ~ Churn, scales = "free") + # Layer 3: Chia theo 2 biến (4 ô)
geom_smooth(method = "lm", color = "red", se = FALSE, linewidth = 1) + # Layer 4: Đường xu hướng
labs(title = "Tương tác Premium vs Claim, phân tách theo Churn và Claim Output",
x = "Phí Bảo hiểm (Category Premium)", y = "Số tiền Bồi thường (Claim Amount)") + # Layer 5: Tiêu đề và nhãn
theme_bw()## `geom_smooth()` using formula = 'y ~ x'
# Median Claim Amount theo Top 5 Bang
top_5_states_list <- (dff %>% count(State, sort = TRUE) %>% head(5))$State
median_claim_by_state <- dff %>%
filter(State %in% top_5_states_list) %>%
group_by(State) %>%
summarise(Median_Claim_Amount = median(Claim_Amount))
ggplot(median_claim_by_state, aes(x = reorder(State, Median_Claim_Amount), y = Median_Claim_Amount, fill = State)) +
geom_col() + # Layer 1: Đồ thị cột
labs(title = "Số tiền Bồi thường theo trung vị trong Top 5 Bang",
x = "Bang/Tiểu bang",
y = "Số tiền Bồi thường theo trung vị") + # Layer 2: Tiêu đề và nhãn
coord_flip() + # Layer 3: Xoay trục
scale_fill_brewer(palette = "Pastel1") + # Layer 4: Bảng màu Pastel
geom_text(aes(label = round(Median_Claim_Amount, 0)), hjust = -0.1, size = 4) + # Layer 5: Gắn nhãn giá trị
theme_minimal()# Phân bố Claim Amount theo BMI Status và Churn (Faceted Density Plot)
ggplot(dff, aes(x = Claim_Amount, fill = Churn)) +
geom_density(alpha = 0.6) + # Layer 1: Biểu đồ mật độ (alpha để thấy sự chồng lấp)
facet_wrap(~ BMI_Status, scales = "free_y", ncol = 2) + # Layer 2: Chia đồ thị theo BMI_Status, trục Y độc lập
labs(title = "Phân Bố Claim Amount theo Tình trạng BMI và Trạng thái Churn",
x = "Số tiền Bồi thường (Claim Amount)",
y = "Mật độ") + # Layer 3: Tiêu đề và nhãn
scale_fill_manual(values = c("Yes" = "#E41A1C", "No" = "#377EB8")) + # Layer 4: Đổi màu theo Churn
theme_bw() # Layer 5: Chủ đề đen trắng# Tỷ lệ Churn chi tiết theo BMI Status
churn_by_bmi_detail <- dff %>%
group_by(BMI_Status) %>%
summarise(
Total_Customers = n(),
Churned_Customers = sum(Churn_Encoded),
Churn_Rate = mean(Churn_Encoded) * 100
) %>%
arrange(desc(Churn_Rate))
print(churn_by_bmi_detail)## # A tibble: 3 × 4
## BMI_Status Total_Customers Churned_Customers Churn_Rate
## <fct> <int> <dbl> <dbl>
## 1 Underweight 18293 18293 100
## 2 Normal 108979 108979 100
## 3 Overweight 72728 0 0
# Tỷ lệ Churn theo BMI Status (Lollipop Chart)
churn_by_bmi_plot <- dff %>%
group_by(BMI_Status) %>%
summarise(Churn_Rate = mean(Churn_Encoded) * 100, .groups = 'drop')
ggplot(churn_by_bmi_plot, aes(x = BMI_Status, y = Churn_Rate)) +
geom_segment(aes(x = BMI_Status, xend = BMI_Status, y = 0, yend = Churn_Rate),
color = "gray", linewidth = 1) + # Layer 1: Đường nối
geom_point(aes(color = BMI_Status), size = 5) + # Layer 2: Điểm (Lollipop)
labs(title = "Tỷ lệ Churn theo Tình trạng BMI",
x = "Tình trạng BMI",
y = "Tỷ lệ Churn (%)") + # Layer 3: Tiêu đề và nhãn
scale_color_brewer(palette = "Set1") + # Layer 4: Bảng màu
geom_text(aes(label = paste0(round(Churn_Rate, 1), "%")), vjust = -1, size = 3) + # Layer 5: Gắn nhãn tỷ lệ
theme_minimal()CHƯƠNG 2: PHÂN TÍCH BÁO CÁO TÀI CHÍNH ABS
options(repos = c(CRAN = "https://cloud.r-project.org"))
install.packages(c(
"readxl",
"dplyr",
"DT",
"knitr",
"ggplot2",
"scales",
"janitor",
"magrittr",
"kableExtra"
))## Warning: packages 'readxl', 'dplyr', 'knitr', 'ggplot2', 'scales' are in use
## and will not be installed
## Installing packages into 'C:/Users/DELL/AppData/Local/R/win-library/4.4'
## (as 'lib' is unspecified)
## package 'DT' successfully unpacked and MD5 sums checked
## package 'janitor' successfully unpacked and MD5 sums checked
## package 'magrittr' successfully unpacked and MD5 sums checked
## Warning: cannot remove prior installation of package 'magrittr'
## Warning in file.copy(savedcopy, lib, recursive = TRUE): problem copying
## C:\Users\DELL\AppData\Local\R\win-library\4.4\00LOCK\magrittr\libs\x64\magrittr.dll
## to
## C:\Users\DELL\AppData\Local\R\win-library\4.4\magrittr\libs\x64\magrittr.dll:
## Permission denied
## Warning: restored 'magrittr'
## package 'kableExtra' successfully unpacked and MD5 sums checked
##
## The downloaded binary packages are in
## C:\Users\DELL\AppData\Local\Temp\RtmpA70Gt8\downloaded_packages
# Đọc và hiển thị tên của tất cả các sheet trong file
sheets <- excel_sheets("D:/thầy Tường/bctcabs.xlsx")
print(sheets)## [1] "bctc abs"
# Đọc dữ liệu từ sheet đầu tiên
bc_raw <- read_excel ("D:/thầy Tường/bctcabs.xlsx", sheet = sheets[1]) # Xem cấu trúc của data frame:tổng số quan sát (dòng), tổng số biến (cột), tên của từng cột và quan trọng nhất là kiểu dữ liệu (data type) của cột đó (ví dụ: numeric, character, dttm).
str(bc_raw)## tibble [10 × 11] (S3: tbl_df/tbl/data.frame)
## $ Năm/Biến : num [1:10] 2015 2016 2017 2018 2019 ...
## $ Tài sản ngắn hạn : num [1:10] 1.22e+13 1.07e+13 1.37e+13 1.47e+13 1.92e+13 ...
## $ Tiền và các khoản tương đương tiền : num [1:10] 7.94e+12 3.44e+12 2.64e+12 4.47e+12 4.12e+12 ...
## $ Các khoản đầu tư tài chính ngắn hạn : num [1:10] 9.43e+11 3.17e+12 6.56e+12 7.54e+12 1.24e+13 ...
## $ Tài sản dài hạn : num [1:10] 9.38e+12 8.48e+12 8.33e+12 7.68e+12 7.80e+12 ...
## $ Tổng tài sản : num [1:10] 2.16e+13 1.92e+13 2.20e+13 2.24e+13 2.70e+13 ...
## $ Tổng nợ phải trả : num [1:10] 7.51e+12 6.76e+12 7.59e+12 6.25e+12 6.89e+12 ...
## $ Vốn chủ sở hữu : num [1:10] 1.41e+13 1.24e+13 1.44e+13 1.61e+13 2.01e+13 ...
## $ Doanh thu thuần về bán hàng
## và cung cấp dịch vụ: num [1:10] 2.71e+13 3.06e+13 3.44e+13 3.59e+13 3.79e+13 ...
## $ Lợi nhuận gộp về bán hàng
## và cung cấp dịch vụ : num [1:10] 7.56e+12 8.27e+12 8.87e+12 8.08e+12 9.55e+12 ...
## $ Doanh thu hoạt động tài chính : num [1:10] 4.19e+11 7.51e+11 5.06e+11 6.30e+11 8.90e+11 ...
## Số dòng: 10
## Số cột: 11
## Năm/Biến
## "numeric"
## Tài sản ngắn hạn
## "numeric"
## Tiền và các khoản tương đương tiền
## "numeric"
## Các khoản đầu tư tài chính ngắn hạn
## "numeric"
## Tài sản dài hạn
## "numeric"
## Tổng tài sản
## "numeric"
## Tổng nợ phải trả
## "numeric"
## Vốn chủ sở hữu
## "numeric"
## Doanh thu thuần về bán hàng\r\nvà cung cấp dịch vụ
## "numeric"
## Lợi nhuận gộp về bán hàng\r\nvà cung cấp dịch vụ
## "numeric"
## Doanh thu hoạt động tài chính
## "numeric"
## [1] 2015 2016 2017 2018 2019 2020 2021 2022 2023 2024
Mục đích là để kiểm tra tính nhất quán (consistency) và phạm vi (scope) của biến. Trong phân tích, nếu có lỗi nhập liệu (ví dụ: “Tiền mặt” và “Tiền mặt” - thừa dấu cách), R sẽ hiểu đây là hai nhóm (factors) riêng biệt. Điều này dẫn đến việc “pha loãng” dữ liệu, khiến các phép thống kê gộp nhóm (group_by) hoặc đếm tần suất (count) bị chia tách và cho ra kết quả sai lệch. Thao tác này giúp phát hiện sớm các lỗi trên để tiến hành chuẩn hóa dữ liệu.
# Hàm duplicated được sử dụng để rà soát toàn bộ bộ dữ liệu và xác định các hàng (quan sát) bị trùng lặp y hệt (giống nhau trên tất cả các biến).
dup_rows <- sum(duplicated(bc_raw))
cat('Số hàng trùng lặp toàn bộ:', dup_rows, "\n")## Số hàng trùng lặp toàn bộ: 0
# gọi thư viện
library(knitr)
# lập bảng giải thích các biến
variable_description <- data.frame(
Ten_Bien = names(bc_raw),
Ten_TiengAnh = c(
"Year",
"Current Assets",
"Cash and Cash Equivalents",
"Short-term Financial Investments",
"Non-current Assets",
"Total Assets",
"Total Liabilities",
"Owner’s Equity",
"Net Revenue from Sales and Services",
"Gross Profit from Sales and Services",
"Financial Income"),
Mo_Ta = c(
"Biến chỉ năm tài chính (2015–2020).",
"Tổng giá trị tài sản ngắn hạn có thể chuyển đổi thành tiền trong vòng một năm.",
"Tiền mặt và các khoản tương đương tiền của doanh nghiệp.",
"Các khoản đầu tư tài chính ngắn hạn như cổ phiếu, trái phiếu ngắn hạn.",
"Tổng tài sản dài hạn (nhà xưởng, thiết bị, bất động sản…).",
"Tổng giá trị tài sản mà doanh nghiệp sở hữu.",
"Tổng nghĩa vụ tài chính mà doanh nghiệp phải thanh toán.",
"Nguồn vốn thuộc cổ đông – phần chênh giữa tài sản và nợ phải trả.",
"Doanh thu thuần từ hoạt động bán hàng và cung cấp dịch vụ.",
"Khoản lợi nhuận sau khi trừ giá vốn hàng bán.",
"Doanh thu từ các hoạt động tài chính như lãi tiền gửi, cổ tức, đầu tư."))
# in bảng giải thích
kable(variable_description, caption = "Bảng giải thích các biến trong dữ liệu")| Ten_Bien | Ten_TiengAnh | Mo_Ta |
|---|---|---|
| Năm/Biến | Year | Biến chỉ năm tài chính (2015–2020). |
| Tài sản ngắn hạn | Current Assets | Tổng giá trị tài sản ngắn hạn có thể chuyển đổi thành tiền trong vòng một năm. |
| Tiền và các khoản tương đương tiền | Cash and Cash Equivalents | Tiền mặt và các khoản tương đương tiền của doanh nghiệp. |
| Các khoản đầu tư tài chính ngắn hạn | Short-term Financial Investments | Các khoản đầu tư tài chính ngắn hạn như cổ phiếu, trái phiếu ngắn hạn. |
| Tài sản dài hạn | Non-current Assets | Tổng tài sản dài hạn (nhà xưởng, thiết bị, bất động sản…). |
| Tổng tài sản | Total Assets | Tổng giá trị tài sản mà doanh nghiệp sở hữu. |
| Tổng nợ phải trả | Total Liabilities | Tổng nghĩa vụ tài chính mà doanh nghiệp phải thanh toán. |
| Vốn chủ sở hữu | Owner’s Equity | Nguồn vốn thuộc cổ đông – phần chênh giữa tài sản và nợ phải trả. |
| Doanh thu thuần về bán hàng | ||
| và cung cấp dịch vụ | Net Revenue from Sales and Services | Doanh thu thuần từ hoạt động bán hàng và cung cấp dịch vụ. |
| Lợi nhuận gộp về bán hàng | ||
| và cung cấp dịch vụ | Gross Profit from Sales and Services | Khoản lợi nhuận sau khi trừ giá vốn hàng bán. |
| Doanh thu hoạt động tài chính | Financial Income | Doanh thu từ các hoạt động tài chính như lãi tiền gửi, cổ tức, đầu tư. |
## Năm/Biến Tài sản ngắn hạn Tiền và các khoản tương đương tiền
## Min. :2015 Min. :1.071e+13 Min. :2.637e+12
## 1st Qu.:2017 1st Qu.:1.394e+13 1st Qu.:3.485e+12
## Median :2020 Median :1.934e+13 Median :4.093e+12
## Mean :2020 Mean :1.913e+13 Mean :4.252e+12
## 3rd Qu.:2022 3rd Qu.:2.452e+13 3rd Qu.:4.475e+12
## Max. :2024 Max. :2.686e+13 Max. :7.936e+12
## Các khoản đầu tư tài chính ngắn hạn Tài sản dài hạn Tổng tài sản
## Min. :9.428e+11 Min. :7.503e+12 Min. :1.919e+13
## 1st Qu.:6.805e+12 1st Qu.:7.627e+12 1st Qu.:2.210e+13
## Median :1.347e+13 Median :7.830e+12 Median :2.717e+13
## Mean :1.159e+13 Mean :8.061e+12 Mean :2.719e+13
## 3rd Qu.:1.688e+13 3rd Qu.:8.361e+12 3rd Qu.:3.270e+13
## Max. :1.941e+13 Max. :9.378e+12 Max. :3.447e+13
## Tổng nợ phải trả Vốn chủ sở hữu
## Min. :6.160e+12 Min. :1.243e+13
## 1st Qu.:6.791e+12 1st Qu.:1.484e+13
## Median :7.550e+12 Median :2.065e+13
## Mean :7.650e+12 Mean :1.954e+13
## 3rd Qu.:8.402e+12 3rd Qu.:2.398e+13
## Max. :9.874e+12 Max. :2.549e+13
## Doanh thu thuần về bán hàng\r\nvà cung cấp dịch vụ
## Min. :2.637e+13
## 1st Qu.:2.859e+13
## Median :3.122e+13
## Mean :3.176e+13
## 3rd Qu.:3.484e+13
## Max. :3.790e+13
## Lợi nhuận gộp về bán hàng\r\nvà cung cấp dịch vụ Doanh thu hoạt động tài chính
## Min. :7.558e+12 Min. :4.186e+11
## 1st Qu.:8.130e+12 1st Qu.:6.605e+11
## Median :8.683e+12 Median :9.321e+11
## Mean :8.762e+12 Mean :8.880e+11
## 3rd Qu.:9.261e+12 3rd Qu.:1.085e+12
## Max. :1.077e+13 Max. :1.433e+12
Tính các giá trị Min và Max, đánh giá Độ lệch (Skewness) bằng cách so sánh Mean (Trung bình) và Median (Trung vị), ta có thể đánh giá sơ bộ độ lệch của phân phối. Nếu Mean chênh lệch đáng kể so với Median, dữ liệu có khả năng bị lệch (skewed), vi phạm giả định về phân phối chuẩn của nhiều mô hình, và có thể đòi hỏi các phép biến đổi (víT dụ: lấy logarit) trước khi phân tích.
# gọi thư viện
library(readxl)
# đọc lại và xem dữ liệu gốc
bc <- read_excel("D:/thầy Tường/bctcabs.xlsx", sheet = 1)
names(bc)## [1] "Năm/Biến"
## [2] "Tài sản ngắn hạn"
## [3] "Tiền và các khoản tương đương tiền"
## [4] "Các khoản đầu tư tài chính ngắn hạn"
## [5] "Tài sản dài hạn"
## [6] "Tổng tài sản"
## [7] "Tổng nợ phải trả"
## [8] "Vốn chủ sở hữu"
## [9] "Doanh thu thuần về bán hàng\r\nvà cung cấp dịch vụ"
## [10] "Lợi nhuận gộp về bán hàng\r\nvà cung cấp dịch vụ"
## [11] "Doanh thu hoạt động tài chính"
## Warning: package 'janitor' was built under R version 4.4.3
##
## Attaching package: 'janitor'
## The following objects are masked from 'package:stats':
##
## chisq.test, fisher.test
## [1] "nam_bien"
## [2] "tai_san_ngan_han"
## [3] "tien_va_cac_khoan_tuong_duong_tien"
## [4] "cac_khoan_dau_tu_tai_chinh_ngan_han"
## [5] "tai_san_dai_han"
## [6] "tong_tai_san"
## [7] "tong_no_phai_tra"
## [8] "von_chu_so_huu"
## [9] "doanh_thu_thuan_ve_ban_hang_va_cung_cap_dich_vu"
## [10] "loi_nhuan_gop_ve_ban_hang_va_cung_cap_dich_vu"
## [11] "doanh_thu_hoat_dong_tai_chinh"
# Đếm tổng số ô bị trống (giá trị NA - Not Available) trong toàn bộ bảng dữ liệu
total_na <- sum(is.na(bc_raw))
cat('Tổng số giá trị NA:', total_na, "\n")## Tổng số giá trị NA: 0
##
## Attaching package: 'magrittr'
## The following object is masked from 'package:tidyr':
##
## extract
# Sử dụng hàm mutate (từ gói dplyr) để tạo một cột mới tên là TyLeLoiNhuanGop, với giá trị được tính bằng cách lấy cột "lợi nhuận gộp về bán hàng và cung cấp dịch vụ" chia cho cột "doanh thu thuần về bán hàng và cung cấp dịch vụ"
bc <- bc %>%
mutate(TyLeLoiNhuanGop = loi_nhuan_gop_ve_ban_hang_va_cung_cap_dich_vu /
doanh_thu_thuan_ve_ban_hang_va_cung_cap_dich_vu)
names(bc)## [1] "Nam"
## [2] "tai_san_ngan_han"
## [3] "tien_va_cac_khoan_tuong_duong_tien"
## [4] "cac_khoan_dau_tu_tai_chinh_ngan_han"
## [5] "tai_san_dai_han"
## [6] "tong_tai_san"
## [7] "tong_no_phai_tra"
## [8] "von_chu_so_huu"
## [9] "doanh_thu_thuan_ve_ban_hang_va_cung_cap_dich_vu"
## [10] "loi_nhuan_gop_ve_ban_hang_va_cung_cap_dich_vu"
## [11] "doanh_thu_hoat_dong_tai_chinh"
## [12] "TyLeLoiNhuanGop"
# Tạo cột mới quy mô. Dùng hàm case_when để gán nhãn "Nhỏ", "Vừa", "Lớn" dựa trên các ngưỡng giá trị của cột tổng tài sản
bc <- bc %>%
mutate(QuyMo = case_when( tong_tai_san < 1e9 ~ "Nhỏ", tong_tai_san < 5e9 ~ "Vừa", TRUE ~ "Lớn" ))
# in bảng thống kê tần suất từng nhóm quy mô
table(bc$QuyMo)##
## Lớn
## 10
cols_to_num <- c("tai_san_ngan_han", "tong_tai_san", "von_chu_so_huu",
"doanh_thu_thuan_ve_ban_hang_va_cung_cap_dich_vu",
"loi_nhuan_gop_ve_ban_hang_va_cung_cap_dich_vu")
bc[cols_to_num] <- lapply(bc[cols_to_num], as.numeric) ## tibble [10 × 13] (S3: tbl_df/tbl/data.frame)
## $ Nam : int [1:10] 2015 2016 2017 2018 2019 2020 2021 2022 2023 2024
## $ tai_san_ngan_han : num [1:10] 1.22e+13 1.07e+13 1.37e+13 1.47e+13 1.92e+13 ...
## $ tien_va_cac_khoan_tuong_duong_tien : num [1:10] 7.94e+12 3.44e+12 2.64e+12 4.47e+12 4.12e+12 ...
## $ cac_khoan_dau_tu_tai_chinh_ngan_han : num [1:10] 9.43e+11 3.17e+12 6.56e+12 7.54e+12 1.24e+13 ...
## $ tai_san_dai_han : num [1:10] 9.38e+12 8.48e+12 8.33e+12 7.68e+12 7.80e+12 ...
## $ tong_tai_san : num [1:10] 2.16e+13 1.92e+13 2.20e+13 2.24e+13 2.70e+13 ...
## $ tong_no_phai_tra : num [1:10] 7.51e+12 6.76e+12 7.59e+12 6.25e+12 6.89e+12 ...
## $ von_chu_so_huu : num [1:10] 1.41e+13 1.24e+13 1.44e+13 1.61e+13 2.01e+13 ...
## $ doanh_thu_thuan_ve_ban_hang_va_cung_cap_dich_vu: num [1:10] 2.71e+13 3.06e+13 3.44e+13 3.59e+13 3.79e+13 ...
## $ loi_nhuan_gop_ve_ban_hang_va_cung_cap_dich_vu : num [1:10] 7.56e+12 8.27e+12 8.87e+12 8.08e+12 9.55e+12 ...
## $ doanh_thu_hoat_dong_tai_chinh : num [1:10] 4.19e+11 7.51e+11 5.06e+11 6.30e+11 8.90e+11 ...
## $ TyLeLoiNhuanGop : num [1:10] 0.278 0.27 0.257 0.225 0.252 ...
## $ QuyMo : chr [1:10] "Lớn" "Lớn" "Lớn" "Lớn" ...
## Nam tai_san_ngan_han tien_va_cac_khoan_tuong_duong_tien
## Min. :2015 Min. :1.071e+13 Min. :2.637e+12
## 1st Qu.:2017 1st Qu.:1.394e+13 1st Qu.:3.485e+12
## Median :2020 Median :1.934e+13 Median :4.093e+12
## Mean :2020 Mean :1.913e+13 Mean :4.252e+12
## 3rd Qu.:2022 3rd Qu.:2.452e+13 3rd Qu.:4.475e+12
## Max. :2024 Max. :2.686e+13 Max. :7.936e+12
## cac_khoan_dau_tu_tai_chinh_ngan_han tai_san_dai_han tong_tai_san
## Min. :9.428e+11 Min. :7.503e+12 Min. :1.919e+13
## 1st Qu.:6.805e+12 1st Qu.:7.627e+12 1st Qu.:2.210e+13
## Median :1.347e+13 Median :7.830e+12 Median :2.717e+13
## Mean :1.159e+13 Mean :8.061e+12 Mean :2.719e+13
## 3rd Qu.:1.688e+13 3rd Qu.:8.361e+12 3rd Qu.:3.270e+13
## Max. :1.941e+13 Max. :9.378e+12 Max. :3.447e+13
## tong_no_phai_tra von_chu_so_huu
## Min. :6.160e+12 Min. :1.243e+13
## 1st Qu.:6.791e+12 1st Qu.:1.484e+13
## Median :7.550e+12 Median :2.065e+13
## Mean :7.650e+12 Mean :1.954e+13
## 3rd Qu.:8.402e+12 3rd Qu.:2.398e+13
## Max. :9.874e+12 Max. :2.549e+13
## doanh_thu_thuan_ve_ban_hang_va_cung_cap_dich_vu
## Min. :2.637e+13
## 1st Qu.:2.859e+13
## Median :3.122e+13
## Mean :3.176e+13
## 3rd Qu.:3.484e+13
## Max. :3.790e+13
## loi_nhuan_gop_ve_ban_hang_va_cung_cap_dich_vu doanh_thu_hoat_dong_tai_chinh
## Min. :7.558e+12 Min. :4.186e+11
## 1st Qu.:8.130e+12 1st Qu.:6.605e+11
## Median :8.683e+12 Median :9.321e+11
## Mean :8.762e+12 Mean :8.880e+11
## 3rd Qu.:9.261e+12 3rd Qu.:1.085e+12
## Max. :1.077e+13 Max. :1.433e+12
## TyLeLoiNhuanGop QuyMo
## Min. :0.2249 Length:10
## 1st Qu.:0.2607 Class :character
## Median :0.2835 Mode :character
## Mean :0.2774
## 3rd Qu.:0.2969
## Max. :0.3079
## Trung bình Tổng tài sản: 2.719308e+13
## Trung vị Tổng tài sản: 2.716872e+13
## Độ lệch chuẩn Tổng tài sản: 5.731623e+12
## Phương sai Tổng tài sản: 3.28515e+25
## Hệ số biến thiên CV: 0.2107751
## [1] 1.919287e+13
## [1] 3.446508e+13
## tong_tai_san tong_no_phai_tra von_chu_so_huu
## Min. :1.919e+13 Min. :6.160e+12 Min. :1.243e+13
## 1st Qu.:2.210e+13 1st Qu.:6.791e+12 1st Qu.:1.484e+13
## Median :2.717e+13 Median :7.550e+12 Median :2.065e+13
## Mean :2.719e+13 Mean :7.650e+12 Mean :1.954e+13
## 3rd Qu.:3.270e+13 3rd Qu.:8.402e+12 3rd Qu.:2.398e+13
## Max. :3.447e+13 Max. :9.874e+12 Max. :2.549e+13
bc %>% group_by(Nam) %>% summarise(Tong_DT = sum(doanh_thu_thuan_ve_ban_hang_va_cung_cap_dich_vu, na.rm = TRUE))bc %>% group_by(Nam) %>%
summarise (LoiNhuanTB = mean(loi_nhuan_gop_ve_ban_hang_va_cung_cap_dich_vu, na.rm = TRUE))bc <- bc %>% mutate(TyLeNoTaiSan = tong_no_phai_tra / tong_tai_san)
bc[, c("Nam", "tong_tai_san", "tong_no_phai_tra", "TyLeNoTaiSan")]bc$ROA <- bc$loi_nhuan_gop_ve_ban_hang_va_cung_cap_dich_vu / bc$tong_tai_san * 100
bc[, c("Nam", "ROA")]bc$ROE <- bc$loi_nhuan_gop_ve_ban_hang_va_cung_cap_dich_vu / bc$von_chu_so_huu * 100
bc[, c("Nam", "ROE")]## [1] 33.17722
## [1] 47.08056
## [1] 0.7445376
## [1] 8.761549e+12
bc <- bc %>%
arrange(Nam) %>%
mutate(TangTruong_TS = (tong_tai_san - lag(tong_tai_san)) / lag(tong_tai_san) * 100)
bc[, c("Nam", "tong_tai_san", "TangTruong_TS")]tonghop <- bc %>%
summarise(
TB_TS = mean(tong_tai_san, na.rm = TRUE),
TB_TN = mean(tong_no_phai_tra, na.rm = TRUE),
TB_VCSH = mean(von_chu_so_huu, na.rm = TRUE),
TB_ROA = mean(ROA, na.rm = TRUE),
TB_ROE = mean(ROE, na.rm = TRUE) )
print(tonghop)## # A tibble: 1 × 5
## TB_TS TB_TN TB_VCSH TB_ROA TB_ROE
## <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 2.72e13 7.65e12 1.95e13 33.2 47.1
## Warning: package 'ggplot2' is in use and will not be installed
library(ggplot2)
library(scales)
ggplot(bc, aes(x = Nam, y = tong_tai_san)) +
geom_line(linewidth = 1.2, color = "blue") +
geom_point(size = 3, color = "red") +
geom_text(aes(label = round(tong_tai_san,0)), vjust = -0.6, size = 3.2) +
labs(title = "Xu hướng Tổng tài sản (2015–2024)",
x = "Năm", y = "Tổng tài sản (tỷ đồng)") +
scale_x_continuous(breaks = 2015:2024) +
scale_y_continuous(labels = comma) +
theme_minimal(base_size = 13)ggplot(bc, aes(x = Nam, y = tai_san_ngan_han)) +
geom_area(fill = "lightblue", alpha = 0.4) +
geom_line(color = "darkblue", linewidth = 1.2) +
geom_point(color = "red", size = 3) +
geom_text(aes(label = round(tai_san_ngan_han,0)), vjust = -0.6) +
labs(title = "Tài sản ngắn hạn (2015–2024)", x = "Năm", y = "Giá trị (tỷ đồng)") +
scale_x_continuous(breaks = 2015:2024) +
theme_light()ggplot(bc, aes(x = Nam, y = tai_san_dai_han)) +
geom_col(fill = "steelblue") +
geom_text(aes(label = round(tai_san_dai_han,0)), vjust = -0.6, color = "black") +
geom_line(aes(y = tai_san_dai_han), color = "red", linewidth = 1) +
labs(title = "Tài sản dài hạn (2015–2024)", x = "Năm", y = "Tỷ đồng") +
scale_x_continuous(breaks = 2015:2024) +
theme_bw()library(scales)
ggplot(bc, aes(x = Nam, y = TyLeNoTaiSan)) +
geom_area(fill = "pink", alpha = 0.5) +
geom_line(color = "darkred", linewidth = 1.2) +
geom_point(color = "black", size = 3) +
geom_text(aes(label = round(TyLeNoTaiSan,2)), vjust = -0.6) +
labs(title = "Tỷ lệ Nợ/Tổng tài sản (2015–2024)",
x = "Năm", y = "Tỷ lệ (%)") +
scale_x_continuous(breaks = 2015:2024) +
scale_y_continuous(labels = percent_format(scale = 1)) +
theme_minimal()ggplot(bc, aes(x = Nam, y = doanh_thu_thuan_ve_ban_hang_va_cung_cap_dich_vu)) +
geom_line(color = "darkgreen", linewidth = 1.2) +
geom_point(color = "orange", size = 3) +
geom_text(aes(label = round(doanh_thu_thuan_ve_ban_hang_va_cung_cap_dich_vu,0)), vjust = -0.6) +
labs(title = "Doanh thu thuần (2015–2024)", x = "Năm", y = "Tỷ đồng") +
scale_x_continuous(breaks = 2015:2024) +
theme_classic()ggplot(bc, aes(x = Nam, y = loi_nhuan_gop_ve_ban_hang_va_cung_cap_dich_vu)) +
geom_col(fill = "seagreen3") +
geom_text(aes(label = round(loi_nhuan_gop_ve_ban_hang_va_cung_cap_dich_vu, 0)), vjust = -0.6) +
labs(title = "Lợi nhuận gộp qua các năm", x = "Năm", y = "Tỷ đồng") +
scale_x_continuous(breaks = 2015:2024) +
theme_minimal()ggplot(bc, aes(x = Nam, y = ROA)) +
geom_line(color = "purple", linewidth = 1.2) +
geom_point(color = "darkorange", size = 3) +
geom_text(aes(label = round(ROA, 2)), vjust = -0.6) +
labs(title = "Tỷ suất sinh lời trên tài sản (ROA)", x = "Năm", y = "%") +
scale_x_continuous(breaks = 2015:2024) +
theme_bw()ggplot(bc, aes(x = Nam, y = ROE)) +
geom_line(color = "darkcyan", linewidth = 1.2) +
geom_point(color = "black", size = 3) +
geom_text(aes(label = round(ROE, 2)), vjust = -0.6) +
labs(title = "Tỷ suất sinh lời trên vốn chủ sở hữu (ROE)", x = "Năm", y = "%") +
scale_x_continuous(breaks = 2015:2024) +
theme_light()bc_clean <- na.omit(bc)
ggplot(bc_clean, aes(x = Nam, y = TangTruong_TS)) +
geom_col(fill = "darkseagreen") +
geom_text(aes(label = paste0(round(TangTruong_TS,1), "%")), vjust = -0.6) +
labs(title = "Tăng trưởng tài sản (%)", x = "Năm", y = "%") +
scale_x_continuous(breaks = 2015:2024) +
theme_minimal()ggplot(bc, aes(x = Nam, y = TyLeLoiNhuanGop)) +
geom_line(color = "red", linewidth = 1.2) +
geom_point(color = "black", size = 3) +
geom_text(aes(label = paste0(round(TyLeLoiNhuanGop,1), "%")), vjust = -0.6) +
labs(title = "Tỷ lệ lợi nhuận gộp (%)", x = "Năm", y = "%") +
scale_x_continuous(breaks = 2015:2024) +
theme_classic()ggplot(bc, aes(x = Nam)) +
geom_line(aes(y = ROA, color = "ROA"), linewidth = 1.2) +
geom_line(aes(y = ROE, color = "ROE"), linewidth = 1.2, linetype = "dashed") +
geom_point(aes(y = ROA, color = "ROA"), size = 3) +
geom_point(aes(y = ROE, color = "ROE"), size = 3) +
labs(title = "So sánh ROA và ROE (2015–2024)",
x = "Năm", y = "Tỷ lệ (%)",
subtitle = "Hai chỉ tiêu phản ánh hiệu quả sinh lời khác nhau",
color = "Chỉ tiêu") +
scale_x_continuous(breaks = 2015:2024) +
theme_minimal(base_size = 13)ggplot(bc, aes(x = Nam)) +
geom_col(aes(y = TyLeNoTaiSan * 100), fill = "lightblue", alpha = 0.6) +
geom_line(aes(y = ROE, color = "ROE"), linewidth = 1.2) +
geom_point(aes(y = ROE, color = "ROE"), size = 3) +
scale_y_continuous(sec.axis = sec_axis(~ ., name = "Tỷ lệ nợ (%)")) +
labs(title = "Ảnh hưởng của Tỷ lệ nợ đến ROE (2015–2024)",
subtitle = "Tỷ lệ nợ tăng cao thường kéo ROE biến động mạnh",
x = "Năm", y = "ROE (%)", color = "") +
scale_x_continuous(breaks = 2015:2024) +
theme_light()ggplot(bc, aes(x = Nam)) +
geom_line(aes(y = doanh_thu_thuan_ve_ban_hang_va_cung_cap_dich_vu/1000, color = "Doanh thu"), linewidth = 1.2) +
geom_line(aes(y = loi_nhuan_gop_ve_ban_hang_va_cung_cap_dich_vu/1000, color = "Lợi nhuận gộp"), linewidth = 1.2, linetype = "dashed") +
geom_point(aes(y = doanh_thu_thuan_ve_ban_hang_va_cung_cap_dich_vu/1000, color = "Doanh thu"), size = 3) +
geom_point(aes(y = loi_nhuan_gop_ve_ban_hang_va_cung_cap_dich_vu/1000, color = "Lợi nhuận gộp"), size = 3) +
labs(title = "Doanh thu và Lợi nhuận gộp (2015–2024)",
subtitle = "Quan hệ giữa doanh thu thuần và lợi nhuận gộp",
x = "Năm", y = "Giá trị (nghìn tỷ đồng)", color = "Chỉ tiêu") +
scale_x_continuous(breaks = 2015:2024) +
theme_minimal()ggplot(bc, aes(x = Nam)) +
geom_line(aes(y = tong_tai_san/1000, color = "Tổng tài sản"), linewidth = 1.2) +
geom_line(aes(y = von_chu_so_huu/1000, color = "Vốn chủ sở hữu"), linewidth = 1.2, linetype = "dashed") +
geom_point(aes(y = tong_tai_san/1000, color = "Tổng tài sản"), size = 3) +
geom_point(aes(y = von_chu_so_huu/1000, color = "Vốn chủ sở hữu"), size = 3) +
labs(title = "Tổng tài sản và Vốn chủ sở hữu (2015–2024)",
x = "Năm", y = "Giá trị (nghìn tỷ đồng)", color = "Chỉ tiêu") +
scale_x_continuous(breaks = 2015:2024) +
theme_bw()ggplot(bc, aes(x = Nam)) +
geom_area(aes(y = tai_san_ngan_han/1000, fill = "Ngắn hạn"), alpha = 0.4) +
geom_area(aes(y = tai_san_dai_han/1000, fill = "Dài hạn"), alpha = 0.4) +
geom_line(aes(y = tai_san_ngan_han/1000, color = "Ngắn hạn"), linewidth = 1.2) +
geom_line(aes(y = tai_san_dai_han/1000, color = "Dài hạn"), linewidth = 1.2) +
labs(title = "Tài sản ngắn hạn vs dài hạn (2015–2024)",
x = "Năm", y = "Giá trị (nghìn tỷ đồng)", color = "Loại tài sản") +
scale_x_continuous(breaks = 2015:2024) +
theme_minimal()ggplot(bc, aes(x = Nam, y = tong_tai_san/1000, color = QuyMo)) +
geom_line(linewidth = 1.2) +
geom_point(size = 3) +
labs(title = "Ảnh hưởng của Quy mô doanh nghiệp đến Tổng tài sản",
subtitle = "Nhóm quy mô Lớn - Vừa - Nhỏ",
x = "Năm", y = "Tổng tài sản (nghìn tỷ đồng)", color = "Quy mô") +
scale_x_continuous(breaks = 2015:2024) +
theme_light()ggplot(bc, aes(x = Nam, y = doanh_thu_thuan_ve_ban_hang_va_cung_cap_dich_vu/1000, color = QuyMo)) +
geom_line(linewidth = 1.2) +
geom_point(size = 3) +
labs(title = "Ảnh hưởng của Quy mô đến Doanh thu thuần (2015–2024)",
x = "Năm", y = "Doanh thu (nghìn tỷ đồng)", color = "Quy mô") +
scale_x_continuous(breaks = 2015:2024) +
theme_minimal()ggplot(bc, aes(x = Nam, y = ROA, color = QuyMo)) +
geom_line(linewidth = 1.2) +
geom_point(size = 3) +
labs(title = "Ảnh hưởng của Quy mô doanh nghiệp đến ROA",
x = "Năm", y = "ROA (%)", color = "Quy mô") +
scale_x_continuous(breaks = 2015:2024) +
theme_classic()ggplot(bc, aes(x = Nam)) +
geom_col(aes(y = TyLeNoTaiSan * 100), fill = "skyblue", alpha = 0.6) +
geom_line(aes(y = ROA, color = "ROA"), linewidth = 1.2) +
geom_point(aes(y = ROA, color = "ROA"), size = 3) +
labs(title = "Tỷ lệ nợ và ROA (2015–2024)",
subtitle = "Phản ánh tác động của đòn bẩy tài chính đến hiệu quả sinh lời",
x = "Năm", y = "Tỷ lệ (%)", color = "") +
scale_x_continuous(breaks = 2015:2024) +
theme_minimal()bc_clean <- na.omit(bc)
ggplot(bc_clean, aes(x = Nam)) +
geom_col(aes(y = TangTruong_TS), fill = "lightgreen", alpha = 0.6) +
geom_line(aes(y = ROE, color = "ROE"), linewidth = 1.2) +
geom_point(aes(y = ROE, color = "ROE"), size = 3) +
labs(title = "Tăng trưởng tài sản và ROE (2015–2024)",
subtitle = "Mức tăng tài sản thường ảnh hưởng đến hiệu suất sinh lời vốn chủ",
x = "Năm", y = "Tỷ lệ (%)", color = "") +
scale_x_continuous(breaks = 2015:2024) +
theme_light()ggplot(bc, aes(x = ROA)) +
geom_histogram(fill = "skyblue", color = "black", bins = 8) +
labs(title = "Phân phối ROA", x = "ROA (%)", y = "Tần suất") +
theme_light()ggplot(bc, aes(x = ROE)) +
geom_density(fill = "pink", alpha = 0.5) +
labs(title = "Phân phối ROE", x = "ROE (%)", y = "Mật độ") +
theme_minimal()ggplot(bc, aes(x = factor(Nam), y = ROA)) +
geom_boxplot(fill = "#9be09b", color = "skyblue", width = 0.6) +
labs(title = "Phân bố ROA theo năm", x = "Năm", y = "ROA (%)") +
theme_minimal(base_size = 13) +
theme( panel.border = element_rect(color = "black", fill = NA, linewidth = 0.8))ggplot(bc) +
geom_line(aes(x = Nam, y = doanh_thu_thuan_ve_ban_hang_va_cung_cap_dich_vu, color = "Doanh thu")) +
geom_line(aes(x = Nam, y = loi_nhuan_gop_ve_ban_hang_va_cung_cap_dich_vu, color = "Lợi nhuận")) +
scale_color_manual(values = c("Doanh thu" = "blue", "Lợi nhuận" = "red")) +
labs(title = "Doanh thu và Lợi nhuận gộp (2015–2024)", x = "Năm", y = "Tỷ đồng", color = "Chỉ tiêu") +
scale_x_continuous(breaks = 2015:2024) +
theme_minimal()ggplot(bc) +
geom_line(aes(x = Nam, y = tong_tai_san/1000, color = "Tổng tài sản"), linewidth = 1.2) +
geom_line(aes(x = Nam, y = doanh_thu_thuan_ve_ban_hang_va_cung_cap_dich_vu/1000, color = "Doanh thu"), linewidth = 1.2) +
geom_line(aes(x = Nam, y = loi_nhuan_gop_ve_ban_hang_va_cung_cap_dich_vu/1000, color = "Lợi nhuận gộp"), linewidth = 1.2) +
labs(title = "Tổng hợp: Tài sản, Doanh thu, Lợi nhuận (2015–2024)",
subtitle = "Đơn vị: nghìn tỷ đồng", x = "Năm", y = "Giá trị (nghìn tỷ đồng)", color = "Chỉ tiêu") +
scale_x_continuous(breaks = 2015:2024) +
theme_minimal()