Em xin gửi lời cảm ơn chân thành đến Th.S Trần Mạnh Tường đã tận tình giúp đỡ, chia sẻ những kinh nghiệm của thầy đến với chúng em. Thầy đã tận tậm góp ý, chỉ giạy, đưa ra hướng nghiên cứu, các công cụ phụ trợ và cách tiếp cận đến môn học để em có thể hoàn thiện bài báo cáo này một cách chỉnh chu và tốt đẹp nhất.

Trong quá trình học tập và viết bài tiểu luận, em đã nỗ lực rất nhiều để tìm hiểu về đề tài và mô hình mình sử dụng. Tuy nhiên, kiến thức và kinh nghiệm còn có hạn chế nên việc sai sót là điều khó tránh khỏi. Nên em rất mong nhận được các ý kiến đánh giá của thầy cho bài tiểu luận này để bản thân em có được cơ hội học hỏi và trau dồi nhiều hơn, để có thể hoàn thiện tốt trong tương lai.

Em xin chân thành cảm ơn!

Em xin cam đoan rằng bài báo cáo tiểu luận này là kết quả của quá trình nghiên cứu, tìm hiểu và tổng hợp kiến thức do chính bản thân em thực hiện. Toàn bộ các số liệu, kết quả phân tích và nội dung trình bày trong bài đều trung thực, được trích dẫn rõ ràng từ những nguồn tài liệu đáng tin cậy và tuân thủ đúng các quy định về trích dẫn học thuật.

Em hoàn toàn chịu trách nhiệm về tính chính xác, trung thực và minh bạch của các nội dung được nêu trong bài tiểu luận. Bài làm này không sao chép hay vi phạm bản quyền của bất kỳ cá nhân hay tổ chức nào.

Em cũng xin khẳng định rằng toàn bộ kết quả nghiên cứu trong bài đều được em trình bày một cách công khai, minh bạch và trung thực, nhằm phục vụ cho mục đích học tập và nghiên cứu khoa học.

GIỚI THIỆU CHUNG VỀ BỘ DỮ LIỆU

Trong bối cảnh ngành bảo hiểm nhân thọ ngày càng cạnh tranh gay gắt, việc duy trì khách hàng cũ được xem là một trong những yếu tố then chốt giúp doanh nghiệp tồn tại và phát triển bền vững. Cùng với sự phát triển mạnh mẽ của khoa học dữ liệu và công nghệ phân tích hành vi, các tổ chức bảo hiểm ngày càng chú trọng đến việc phân tích, dự báo khả năng rời bỏ của khách hàng (customer churn) nhằm đưa ra chiến lược giữ chân hiệu quả.

Để phục vụ mục tiêu này, bộ dữ liệu “Customer Churn Dataset for Life Insurance Industry” được công bố trên nền tảng Kaggle đã trở thành một trong những nguồn dữ liệu tiêu biểu được sử dụng rộng rãi trong các nghiên cứu ứng dụng về lĩnh vực tài chính – bảo hiểm. Kaggle, với vai trò là một cộng đồng toàn cầu chuyên về phân tích dữ liệu và học máy, cung cấp các tập dữ liệu mở chất lượng cao, cho phép các nhà nghiên cứu, sinh viên và chuyên gia thực hành các kỹ thuật thống kê, mô hình dự báo và khai phá dữ liệu trong môi trường thực tế.

Bộ dữ liệu này được thiết kế với mục tiêu mô phỏng một hệ thống khách hàng trong lĩnh vực bảo hiểm nhân thọ, nơi các yếu tố như thông tin hợp đồng, hành vi bồi thường, đặc điểm cá nhân và mức độ gắn bó với công ty bảo hiểm được tổng hợp và ghi nhận. Dữ liệu phản ánh thực tiễn kinh doanh của doanh nghiệp bảo hiểm trong việc quản lý danh mục khách hàng và dự đoán xu hướng rời bỏ, từ đó giúp tối ưu hóa chiến lược chăm sóc khách hàng, nâng cao chất lượng dịch vụ, đồng thời tăng hiệu quả hoạt động kinh doanh.

PHẦN 1: THÔNG TIN CƠ BẢN VỀ BỘ DỮ LIỆU

Đọc dữ liệu

library(readxl)
# Xác định đường dẫn đến tệp dữ liệu
df <- read_excel("D:/thầy Tường/Customer.xlsx")

Đây là bước “nạp” dữ liệu thô vào môi trường R để chuẩn bị phân tích

Xác định số dòng

# Định nghĩa các biến
n_rows <- nrow(df)
# Tạo data frame tạm thời
df_rows <- data.frame(Thong_Tin = "Số dòng ",Gia_Tri = scales::comma(n_rows))

# In ra kết quả
kable( df_rows, caption = "Số dòng dữ liệu", col.names = c("Thông tin", "Giá trị"),
align = "lc",row.names = FALSE)
Số dòng dữ liệu
Thông tin Giá trị
Số dòng 200,000

Kết quả: có 200.000 dòng

Xác định số cột

# Số cột
n_cols <- ncol(df)

# Tạo data frame tạm thời
df_cols <- data.frame( Thong_Tin = "Số cột", Gia_Tri = n_cols)

# In ra kết quả
kable(df_cols, caption = "Số cột dữ liệu", col.names = c("Thông tin", "Giá trị"),
align = "lc", row.names = FALSE)
Số cột dữ liệu
Thông tin Giá trị
Số cột 12

Kết quả: có 12 cột

Giải thích ý nghĩa các biến

# lập bảng giải thích các biến.
variable_meaning <- data.frame(
Ten_Bien = names(df),
Mo_Ta = c(
"Số thứ tự của khách hàng",
"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"))

# In bảng giải thích.
kable(variable_meaning, col.names = c("Tên Biến", "Mô Tả"),
caption = "Giải thích các biến trong dữ liệu")
Giải thích các biến trong dữ liệu
Tên Biến Mô Tả
Index Số thứ tự của khách hàng
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

Kiểm tra kiểu dữ liệu mỗi cột

# Gọi thư viện.
library(knitr)

# Lập bảng kiểu dữ liệu.
Kieu_du_lieu<-data.frame( Ten_Bien = names(df),Kieu_du_lieu = sapply(df, class))

# In bảng giải thích.
kable(Kieu_du_lieu, col.names = c("Tên Biến", "Kiểu Dữ Liệu"),
caption = "Thống kê kiểu dữ liệu các biến",row.names = FALSE)
Thống kê kiểu dữ liệu các biến
Tên Biến Kiểu Dữ Liệu
Index numeric
Customer Name character
Customer_Address character
Company Name character
Claim Reason character
Data confidentiality character
Claim Amount numeric
Category Premium numeric
Premium/Amount Ratio character
Claim Request output character
BMI numeric
Churn character

Kết quả: Có 5 biến định định lượng (num) và 7 biến định tính (chr).

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

# Lập bảng dữ liệu bị thiếu
Du_Lieu_Bi_Thieu<-data.frame( Ten_Bien = names(df),N_A = colSums(is.na(df)))

# In bảng giữ liệu.
kable(Du_Lieu_Bi_Thieu, col.names = c("Tên Biến", "Dữ Liệu Bị thiếu"),
caption = "Thống kê dữ liệu bị thiếu",row.names = FALSE)
Thống kê dữ liệu bị thiếu
Tên Biến Dữ Liệu Bị thiếu
Index 0
Customer Name 0
Customer_Address 0
Company Name 0
Claim Reason 0
Data confidentiality 0
Claim Amount 0
Category Premium 0
Premium/Amount Ratio 0
Claim Request output 0
BMI 0
Churn 0

Kết quả: Bộ dữ liệu không có giá trị bị thiếu.

Ý nghĩa: Đây là bước đánh giá tính đầy đủ (completeness) của dữ liệu. Dữ liệu khuyết là một vấn đề nghiêm trọng trong phân tích. Hầu hết các mô hình thống kê (như hồi quy) sẽ tự động loại bỏ toàn bộ hàng (listwise deletion) nếu hàng đó chứa dù chỉ một giá trị NA. Nếu dữ liệu khuyết ở mức cao, điều này sẽ làm giảm kích thước mẫu hiệu dụng (effective sample size), từ đó làm giảm sức mạnh thống kê (statistical power) của mô hình.

Hàng Dữ liệu bị trùng lặp

# Đếm số hàng trùng lặp (Lệnh của bạn)
Kiem_Tra_Trung_lap <- sum(duplicated(df))

# Tắt ký hiệu khoa học mặc định của R (tùy chọn)
options(scipen = 999) 

# Tạo data frame tạm thời 
df_trung_lap <- data.frame( Thong_Tin = "Số hàng trùng lặp",
                            
  # Định dạng số bằng scales::comma để hiển thị rõ ràng 
  Gia_Tri = scales::comma(Kiem_Tra_Trung_lap) )

# In ra bảng kable
kable( df_trung_lap, caption = "Dữ liệu trùng lặp", col.names = c("Thông tin", "Giá trị"),
       align = "lr", booktabs = TRUE,row.names = FALSE)
Dữ liệu trùng lặp
Thông tin Giá trị
Số hàng trùng lặp 0

Kết quả: Bộ dữ liệu không có hàng nào trùng lặp.

Thống kê mô tả các biến định lượng

Thống kê mô tả biến Claim Amount

# thống kê mô tả biến claim Amount
knitr::kable(data.frame( Chi_tieu = names(summary(df$`Claim Amount`)),
              Gia_tri = as.numeric(summary(df$`Claim Amount`))),
             
# Đặt Đặt tiêu đề
  caption = "Thống kê biến claim amout",

# Đặt tên cột, Làm tròn số, căn chỉnh lề
  col.names = c("Chỉ tiêu thống kê", "Giá trị"),digits = 2,align = "lr")
Thống kê biến claim amout
Chỉ tiêu thống kê Giá trị
Min. 1.00
1st Qu. 245.00
Median 1390.00
Mean 1120.48
3rd Qu. 1844.00
Max. 2299.00

Kết luận: Các yêu cầu bồi thường phần lớn đề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.

Thống kê mô tả biến Category Premium

# Thống kê mô tả biến Category Premium
knitr::kable(data.frame( Chi_tieu = names(summary(df$`Category Premium`)),
              Gia_tri = as.numeric(summary(df$`Category Premium`))),
             
# Đặt Đặt tiêu đề
  caption = "Thống kê biến categogy premium",
# Đặt tên cột, Làm tròn số, căn chỉnh lề
  col.names = c("Chỉ tiêu thống kê", "Giá trị"),digits = 2,align = "lr")
Thống kê biến categogy premium
Chỉ tiêu thống kê Giá trị
Min. 399.00
1st Qu. 1875.00
Median 14390.00
Mean 8963.78
3rd Qu. 14390.00
Max. 14390.00

Kết luận: Phần lớn khách hàng đều đóng mức phí bảo hiểm cao nhất (khoảng 14.390$). Giá trị trung bình thấp hơn trung vị do ảnh hưởng của một nhóm nhỏ khách hàng có mức phí thấp, cho thấy sự tập trung mạnh vào nhóm khách hàng cao cấp.

Thống kê mô tả biến ‘Premium/Amount Ratio’

# 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
knitr::kable(data.frame( Chi_tieu = names(summary(df$`Premium/Amount Ratio`)),
              Gia_tri = as.numeric(summary(df$`Premium/Amount Ratio`))),
             
# Đặt Đặt tiêu đề
  caption = "Thống kê biến 'premium/amount ratio'",
# Đặt tên cột, Làm tròn số, căn chỉnh lề
  col.names = c("Chỉ tiêu thống kê", "Giá trị"),digits = 2,align = "lr")
Thống kê biến ‘premium/amount ratio’
Chỉ tiêu thống kê Giá trị
Min. 0.00
1st Qu. 0.11
Median 0.13
Mean 0.13
3rd Qu. 0.14
Max. 0.25

Kết luận: Phân tích cho thấy tỷ lệ phí bảo hiểm trên số tiền bồi thường có phân bố đối xứng và ổn định, khi Giá trị Trung vịTrung bình gần như bằng nhau (≈ 0.13). Phần lớn các quan sát tập trung quanh mức 13%, phản ánh tính đồng nhất cao trong chính sách định giá phí của công ty và cho thấy sự kiểm soát rủi ro bồi thường hiệu quả.

Thống kê mô tả biến BMI

# Thống kê mô tả biến BMI
knitr::kable(data.frame( Chi_tieu = names(summary(df$BMI)),
              Gia_tri = as.numeric(summary(df$BMI))),
# Đặt Đặt tiêu đề
  caption = "Thống kê biến 'BMI'",
# Đặt tên cột, Làm tròn số, căn chỉnh lề
  col.names = c("Chỉ tiêu thống kê", "Giá trị"),digits = 2,align = "lr")
Thống kê biến ‘BMI’
Chỉ tiêu thống kê Giá trị
Min. 18.00
1st Qu. 20.00
Median 23.00
Mean 23.01
3rd Qu. 26.00
Max. 28.00

Kết luận: Biến BMIphân bố rất đối xứng, khi Giá trị Trung bìnhTrung vị gần như trùng nhau (≈ 23). Phần lớn khách hàng có BMI trong khoảng 20–26, cho thấy sự đồng nhất cao và hầu hết đều thuộc nhóm cân nặng khỏe mạnh đến thừa cân nhẹ, không có giá trị cực đoan đáng kể.

Thống kê mô tả các biến định tính

Các biến Customer Name, Customer Address và Company Name không mang ý nghĩa thống kê.

Đếm tần suất của biến Claim Reason

# Tạo bảng tần suất cho biến Claim Reason
claim_table <- table(df$`Claim Reason`)
# In bảng đẹp
kable(claim_table, caption = "Tần suất biến claim reason",
      col.names = c("Lý do yêu cầu", "Tần suất"), align = "lc")
Tần suất biến claim reason
Lý do yêu cầu Tần suất
Medical 109863
Other 30187
Phone 30016
Travel 29934

Nhận Xét: 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ế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.

Đếm tần suất của biến Data confidentiality

# Tạo bảng tần suất cho biến Data confidentiality
conf_table <- table(df$`Data confidentiality`)
# In bảng đẹp
kable(conf_table, caption = "Tần suất biến data confidentiality",
      col.names = c("Mức độ bảo mật", "Tần suất"),align = "lc")
Tần suất biến data confidentiality
Mức độ bảo mật Tần suất
High 109863
Low 29934
Medium 30016
Very low 30187

Kết quả: 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.

Đếm tần suất biến Claim Request output

# Tạo bảng tần suất cho biến Claim Request output
claim_output_table <- table(df$`Claim Request output`)
# In bảng đẹp
kable(claim_output_table, caption = "Tần suất biến claim request output",
      col.names = c("Kết quả yêu cầu", "Tần suất"),align = "lc")
Tần suất biến claim request output
Kết quả yêu cầu Tần suất
No 192994
Yes 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).

Đếm tần suất của các giá trị trong biến Churn

# Tạo bảng tần suất cho biến Churn
churn_table <- table(df$Churn)
# In bảng đẹp
kable(churn_table,caption = "Tần suất biến churn",
      col.names = c("Trạng thái rời bỏ (Churn)", "Tần suất"),align = "lc")
Tần suất biến churn
Trạng thái rời bỏ (Churn) Tần suất
No 72728
Yes 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).

Nhận xét: Đ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.

PHẦN 2: XỬ LÝ VÀ MÃ HÓA

Sao chép dữ liệu gốc để thao tác

df_clean <- df

Chuẩn hóa tên cột

# Chuyển thành chữ thường, xóa ký tự đặc biệt, thay khoảng trắng bằng "_"
df_clean <- clean_names(df_clean)
# Tạo bảng kết quả
kable( data.frame( STT = seq_along(names(df_clean)), Ten_cot = names(df_clean)),
       caption = "Kết quả chuẩn hóa",col.names = c("STT", "Tên cột"), align ="l")
Kết quả chuẩn hóa
STT Tên cột
1 index
2 customer_name
3 customer_address
4 company_name
5 claim_reason
6 data_confidentiality
7 claim_amount
8 category_premium
9 premium_amount_ratio
10 claim_request_output
11 bmi
12 churn

Ý nghĩa:Chuẩn hóa tên cột nhằm thay khoảng trắng và ký tự đặc biệt bằng dấu gạch dưới, giúp tên biến đồng nhất, dễ gọi và tránh lỗi khi xử lý dữ liệu trong R.

Loại bỏ cột không cần thiết

# Cập nhật Data Frame "df_clean"
df_clean <- df_clean %>%
  # Loại bỏ các cột không cần thiết
  select(-c(index, customer_name, customer_address, company_name))
# Tạo bảng kết quả
kable(data.frame(STT = seq_along(names(df_clean)),Ten_cot = names(df_clean)),
      caption = "Danh sách cột sau loại bỏ",
      col.names = c("STT", "Tên cột"),align = "l")
Danh sách cột sau loại bỏ
STT Tên cột
1 claim_reason
2 data_confidentiality
3 claim_amount
4 category_premium
5 premium_amount_ratio
6 claim_request_output
7 bmi
8 churn

Ý nghĩa:Đoạn mã trên loại bỏ các cột không cần thiết như index, customer_name, customer_address và company_name để giữ lại những biến quan trọng, giúp dữ liệu gọn, dễ phân tích và tránh nhiễu thông tin.

Mã hóa biến nhị phân “Claim Request output”

# Chuyển đổi 'claim_request_output' (Yes/No) thành biến số (1/0)
df_clean$claim_output_encoded <- ifelse(df_clean$claim_request_output == "Yes", 1, 0)

# xem 10 hàng đầu
kable(head(df_clean[, c("claim_request_output", "claim_output_encoded")],10),
      caption = "10 dòng đầu sau loại bỏ")
10 dòng đầu sau loại bỏ
claim_request_output claim_output_encoded
No 0
No 0
No 0
No 0
No 0
No 0
No 0
No 0
No 0
No 0

Ý nghĩa: Việc chuyển biến “Yes/No” thành dạng nhị phân (1/0) giúp biến định tính trở thành biến định lượng, từ đó có thể tính toán thống kê như tỷ lệ, trung bình hay xác suất. Mã hóa này cho phép phân tích mối quan hệ với các biến khác, sử dụng trong các mô hình hồi quy và dự đoán. Giá trị trung bình của biến nhị phân thể hiện trực tiếp tỷ lệ xảy ra của sự kiện “Yes” trong tập dữ liệu. Mã hóa tương tự cho biến “Churn”.

Mã hóa biến nhị phân “Churn”

# chuyển đổi 'churn' (Yes/No) thành biến số (1/0)
df_clean$churn_encoded <- ifelse(df_clean$churn == "Yes", 1, 0)
# xem 10 hàng đầu
kable(head(df_clean[, c("churn", "churn_encoded")], 10), 
      caption = "10 dòng đầu sau mã hóa")
10 dòng đầu sau mã hóa
churn churn_encoded
Yes 1
Yes 1
Yes 1
Yes 1
Yes 1
No 0
Yes 1
No 0
No 0
No 0

Mã hóa biến thứ bậc “Data confidentiality”

# Xác định thứ tự mức độ
levels_order <- c("Very low", "Low", "Medium", "High")
# Tạo biến factor có thứ tự
df_clean$data_confid_ordinal <- factor(df_clean$data_confidentiality,
                                       levels = levels_order,
                                       ordered = TRUE)
# Xem danh sách 10 hàng đầu tiên
kable(head( data.frame( data_confidentiality = df_clean$data_confidentiality,
                        mã_số = as.integer(df_clean$data_confid_ordinal)), 10),
      caption = "10 dòng đầu sau mã hóa")
10 dòng đầu sau mã hóa
data_confidentiality mã_số
Low 2
High 4
Medium 3
Medium 3
Medium 3
High 4
High 4
Very low 1
Medium 3
High 4

Ý nghĩa: Việc mã hóa giúp phần mềm hiểu rõ thứ tự các mức độ (“Very low” → “High”), từ đó khi vẽ biểu đồ cột hoặc hộp, các mức sẽ sắp xếp đúng logic thay vì ngẫu nhiên.

Mã hóa biến danh nghĩa “Claim Reason” (One-Hot Encoding)

# Thao tác này sẽ tự động tạo ra 4 cột nhị phân mới (0/1)
df_clean <- dummy_cols(df_clean, select_columns = "claim_reason",
                       remove_first_dummy = FALSE) # Giữ tất cả các cột
# Xem 5 dòng đầu để thấy các cột mới
df_clean %>%
  # Chọn cột gốc và tất cả các cột nhị phân mới bắt đầu bằng 'claim_reason_'
  select(claim_reason, starts_with("claim_reason_")) %>%
  # Lấy 10 dòng đầu tiên để kiểm tra
  head(n = 10) %>%
  # In kable
  kable( caption = "4 cột nhị phân vừa tạo", font_size = 9,scale_down = FALSE )
4 cột nhị phân vừa tạo
claim_reason claim_reason_Medical claim_reason_Other claim_reason_Phone claim_reason_Travel
Travel 0 0 0 1
Medical 1 0 0 0
Phone 0 0 1 0
Phone 0 0 1 0
Phone 0 0 1 0
Medical 1 0 0 0
Medical 1 0 0 0
Other 0 1 0 0
Phone 0 0 1 0
Medical 1 0 0 0

Kết quả: Dataset sau khi mã hóa có thêm 4 cột mới (vì có 4 loại lý do khiếu nại). Nó sẽ được dùng trong mô hình hồi quy, phân loại… vì mô hình chỉ hiểu được số, không hiểu chuỗi ký tự. Nếu khách hàng có claim_reason = “Travel”, thì claim_reason_Travel = 1, các cột khác bằng 0. Nếu claim_reason = “Phone”, thì claim_reason_Phone = 1, các cột khác bằng 0. Và tương tự cho các nhóm khác.

Chuyển đổi kiểu dữ liệu cho premium_amount_ratio

# Chuyển đổi cột 'premium_amount_ratio' từ 'character' sang 'numeric'
df_clean$premium_amount_ratio <- as.numeric(df_clean$premium_amount_ratio)

Tạo biến “Lợi nhuận gộp” (Profitability)

# Tạo một biến mới thể hiện lợi nhuận (hoặc lỗ) từ mỗi hợp đồng
df_clean <- df_clean %>%
  # (Lợi nhuận = Phí thu vào (premium) - Tiền chi ra (claim))
  mutate(profitability = category_premium - claim_amount)
# Xem Thống kê mô tả cho biến
data.frame( "Chỉ tiêu" = names(summary(df_clean$profitability)),
            "Giá trị" = as.numeric(summary(df_clean$profitability))) %>%
  # In bảng thống kê mô tả
  knitr::kable( caption = "Thống kê mô tả biến profitability",
                col.names = c("Chỉ tiêu", "Giá trị"), digits = 2,align = "lr")
Thống kê mô tả biến profitability
Chỉ tiêu Giá trị
Min. 300.00
1st Qu. 1652.00
Median 12180.00
Mean 7843.31
3rd Qu. 12637.00
Max. 13090.00

Ý Nghĩa: Biến premium_amount_ratio biểu thị tỷ lệ giữa phí bảo hiểm và số tiền yêu cầu bồi thường — đây là giá trị số học, cần được xử lý như số để có thể tính toán, thống kê, và trực quan hóa (ví dụ: trung bình, phương sai, biểu đồ, mô hình hồi quy…).

Tạo biến “Tỷ lệ tổn thất” (Loss Ratio)

# Cập nhật "df_clean" 
df_clean <- df_clean %>%
  # Tạo cột mới "loss_ratio" (Tỷ lệ Tổn thất)
  mutate(loss_ratio = claim_amount / category_premium)
# Xem thống kê mô tả cho biến
data.frame( "Chỉ tiêu" = names(summary(df_clean$loss_ratio)),
            "Giá trị" = as.numeric(summary(df_clean$loss_ratio))) %>%
 # In bảng thống kê mô tả
  knitr::kable( caption = "Thống kê mô tả biến loss ratio",
                col.names = c("Chỉ tiêu", "Giá trị"),digits = 4,align = "lr")
Thống kê mô tả biến loss ratio
Chỉ tiêu Giá trị
Min. 0.0025
1st Qu. 0.1067
Median 0.1251
Mean 0.1250
3rd Qu. 0.1432
Max. 0.2481

Ý nghĩa: Biến loss_ratio (tỷ lệ tổn thất) được tạo nhằm phản ánh mối quan hệ giữa số tiền bồi thường và phí bảo hiểm, qua đó đánh giá hiệu quả hoạt động và mức độ rủi ro trong kinh doanh bảo hiểm.

Xử lý giá trị vô cực (Infinity) (nếu có)

# Kiểm tra xem có trường hợp nào 'category_premium' = 0 (gây ra Inf) không
so_luong_inf <- sum(is.infinite(df_clean$loss_ratio))
# Tạo bảng kết luận
kable( data.frame( Noi_dung = "Số dòng vi phạm", So_luong = so_luong_inf),
       caption = "Kết luận kiểm tra 'loss_ratio'",
       col.names = c("Nội dung", "Số lượng"),align = "l")
Kết luận kiểm tra ‘loss_ratio’
Nội dung Số lượng
Số dòng vi phạm 0

Phân tổ một số biến

Phân tổ biến BMI

# Cập nhật Data Frame "df_clean"
df_clean <- df_clean %>%
# Tạo một cột mới có tên là "bmi_group"
 mutate(bmi_group = cut(bmi, breaks = c(17.9, 24.9, 28.1), 
                        labels = c("Normal (<25)", "Overweight (25-28)"),
                       right = TRUE,include.lowest = TRUE))
# Xem số lượng khách hàng trong mỗi nhóm BMI
data.frame(table(df_clean$bmi_group)) %>%
  kable(caption = "Thống kê số lượng khách hàng theo nhóm BMI",
        col.names = c("Nhóm BMI", "Số lượng"),align = "lr",booktabs = TRUE )
Thống kê số lượng khách hàng theo nhóm BMI
Nhóm BMI Số lượng
Normal (<25) 127272
Overweight (25-28) 72728

Kết quả: Dựa trên ‘summary’ (Min=18, Max=28), ta chia thành 2 nhóm: Dưới 25 là Bình thường / Thừa cân nhẹ, trên 25 là Thừa cân/Béo phì.

Phân tổ biến “Claim Amount” (theo Phân vị)

# Tính toán các điểm Tứ phân vị
quantiles_claim <- quantile(df_clean$claim_amount, probs = c(0, 0.25, 0.5, 0.75, 1))
# Cập nhật Data Frame "df_clean"
df_clean <- df_clean %>%
# Tạo một cột mới có tên là "claim_group"
 mutate(claim_group = cut(claim_amount, breaks = quantiles_claim,
                         labels = c("Low", "Medium", "High", "Very High"), include.lowest = TRUE))
# In bảng thống kê
data.frame(table(df_clean$claim_group)) %>%
  kable( caption = "Thống kê số lượng khách hàng theo nhóm claim amount",
         col.names = c("Nhóm Claim Amount", "Số lượng"), align = "lr", booktabs = TRUE)
Thống kê số lượng khách hàng theo nhóm claim amount
Nhóm Claim Amount Số lượng
Low 50043
Medium 50027
High 49942
Very High 49988

Ý nghĩa: Việc phân tổ biến Claim Amount theo các tứ phân vị giúp chia khách hàng thành 4 nhóm mức độ bồi thường (Low, Medium, High, Very High), qua đó dễ dàng so sánh và phân tích hành vi, rủi ro giữa các nhóm khách hàng khác nhau.

Phân tổ biến “Category Premium”

# Cập nhật Data Frame "df_clean"
df_clean <- df_clean %>%
  # Tạo một cột mới có tên là premium_group 
  mutate(premium_group = cut(category_premium,breaks = c(398, 1875, 8964, 14391),
          labels = c("Low (dưới Q1)", "Medium (Q1-Mean)", "High (trên Mean)"),
          include.lowest = TRUE))
# Xem số lượng khách hàng trong mỗi nhóm Phí bảo hiểm
data.frame(table(df_clean$premium_group)) %>%
  kable( caption = "Thống kê số lượng khách hàng theo nhóm Phí Bảo Hiểm",
         col.names = c("Nhóm Phí Bảo Hiểm", "Số lượng"),align = "lr", booktabs = TRUE)
Thống kê số lượng khách hàng theo nhóm Phí Bảo Hiểm
Nhóm Phí Bảo Hiểm Số lượng
Low (dưới Q1) 60203
Medium (Q1-Mean) 29934
High (trên Mean) 109863

Ý nghĩa: Việc phân nhóm biến Category Premium giúp chia khách hàng theo mức phí bảo hiểm (thấp, trung bình, cao), từ đó dễ dàng so sánh và phân tích mối quan hệ giữa mức phí và hành vi hoặc rủi ro của khách hàng.

PHẦN 3: PHÂN TÍCH VÀ TRỰC QUAN HÓA DỮ LIỆU

Biểu đồ 1: Tỷ lệ rời bỏ Chur tổng thể

# Khởi tạo biểu đồ và gán trục X, trục Y
ggplot(df_clean, aes(x = factor(churn_encoded, labels = c("Ở lại", "Rời bỏ")))) +
  # Thêm Lớp Biểu đồ Cột 
  geom_bar(aes(fill = factor(churn_encoded, labels = c("Ở lại", "Rời bỏ"))),
           show.legend = FALSE) +
  # Thêm Nhãn Số lượng
  geom_text(stat = 'count', aes(label = ..count..), vjust = -0.5, size = 5 ) +
  # Tùy chỉnh Màu sắc
  scale_fill_manual(values = c("Ở lại" = "skyblue", "Rời bỏ" = "tomato")) +
  # Mở rộng trục y (tăng giới hạn trên 15%) để nhãn số không bị cắt
  scale_y_continuous(expand = expansion(mult = c(0, 0.15))) +
  # Thêm Tiêu đề và Nhãn
  labs(title = "TỶ LỆ KHÁCH RỜI BỎ (CHURN) TỔNG THỂ", x ="Trạng thái Khách hàng", y = "Số lượng")


Kết quả: Biểu đồ cho thấy sự Mất cân bằng Lớp (Class Imbalance) rõ rệt. Lớp đa số (majority class) là “Rời bỏ” và lớp thiểu số (minority class) là “Ở lại”. Nhận xét: Tỷ lệ khách hàng rời bỏ (Churn Rate) là rất cao, chiếm khoảng 63.6% (127,272 / (127,272 + 72,728)). Đây là một vấn đề nghiêm trọng, cho thấy công ty đang mất đi phần lớn khách hàng của mình.

Biểu đồ 2: Tỷ lệ Chấp thuận Yêu cầu

# Khởi tạo biểu đồ và gán trục X, trục Y
ggplot(df_clean,aes(x=factor(claim_output_encoded,labels = c("Bị từ chối","Được chấp thuận")))) +
  # Thêm lớp Biểu đồ Cột
  geom_bar(aes(fill = factor(claim_output_encoded, labels = c("Bị từ chối", "Được chấp thuận"))),
           show.legend = FALSE) +
  #Thêm nhãn số lượng trên đỉnh cột
  geom_text(stat = 'count', aes(label = ..count..), vjust = -0.5, size = 3.5) +
  # Tùy chỉnh màu
  scale_fill_manual(values = c("Bị từ chối"="tomato","Được chấp thuận"="skyblue")) +
  # Mở rộng trục Y
  scale_y_continuous(expand = expansion(mult = c(0, 0.15))) +
  # Thêm tiêu đề và nhãn trục
  labs(title = "TỶ LỆ CHẤP THUẬN YÊU CẦU BỒI THƯỜNG", x = "Kết quả Yêu cầu", y = "Số lượng")


Kết quả: Dữ liệu bị thiên lệch cực kỳ nghiêm trọng. Lớp đa số (majority class) là “Bị từ chối” (192,994 quan sát) và lớp thiểu số (minority class) là “Được chấp thuận” (chỉ 7,006 quan sát).

Biểu đồ 3: Tỷ trọng các Lý do Yêu cầu (Claim Reason)

# Tạo một data frame mới "df_gop_b3"
df_gop_b3 <- df_clean %>%
  # Lọc ra chỉ 4 cột có tên bắt đầu bằng "claim_reason_"
  select(starts_with("claim_reason_")) %>%
  # Biến 4 cột "rộng" (wide) thành 2 cột "dài" (long)
  pivot_longer(cols = everything(),
    # Tên cột mới (claim_reason_label)
    names_to = "claim_reason_label",
    # Tên cột mới (is_reason): Chứa GIÁ TRỊ (0 hoặc 1) của 4 cột cũ
    values_to = "is_reason") %>%
  # Chỉ giữ lại những hàng có giá trị "is_reason" == 1
  filter(is_reason == 1) %>%
  # Tạo cột "claim_reason" bằng cách xóa tiền tố "claim_reason_"
  mutate(claim_reason = str_remove(claim_reason_label, "claim_reason_"))
# In 10 DÒNG ĐẦU
kable(head(df_gop_b3, 10),
      caption = "10 dòng đầu sau khi gộp",booktabs = TRUE)
10 dòng đầu sau khi gộp
claim_reason_label is_reason claim_reason
claim_reason_Travel 1 Travel
claim_reason_Medical 1 Medical
claim_reason_Phone 1 Phone
claim_reason_Phone 1 Phone
claim_reason_Phone 1 Phone
claim_reason_Medical 1 Medical
claim_reason_Medical 1 Medical
claim_reason_Other 1 Other
claim_reason_Phone 1 Phone
claim_reason_Medical 1 Medical

TRỰC QUAN HÓA

# Khởi tạo biểu đồ và gán trục X, trục Y
ggplot(df_gop_b3, aes(x = claim_reason, fill = claim_reason)) +
  # Thêm Lớp Biểu đồ Cột
  geom_bar(show.legend = FALSE) +
  # Thêm nhãn số lượng trên đỉnh cột
  geom_text(stat = 'count', aes(label = ..count..), vjust = -0.5) +
  # Tăng giới hạn trên của trục y thêm 15% để nhãn số không bị cắt
  scale_y_continuous(expand = expansion(mult = c(0, 0.15))) +
  # Thêm tiêu đề và nhãn trục
  labs(title = " TỶ TRỌNG CÁC LÝ DO YÊU CẦU BỒI THƯỜNG", x = "Lý do Yêu cầu", y = "Số lượng") 


Kết quả: Lý do “Medical” (Y tế) là yếu tố áp đảo với 109,863 trường hợp. Con số này chiếm 55% (109,863 / 200,000) toàn bộ các lý do được ghi nhận.Ba nhóm “Other”, “Phone”, và “Travel” có tần suất xuất hiện tương đối đồng đều nhau, đều xấp xỉ 30,000. Cho thấy yếu tố bảo hiểm do y tế rất được quan tâm.

Biểu đồ 4: Tỷ lệ khách hàng theo nhóm phí bảo hiểm

# Tạo data.frame "df_premium"
df_premium <- df_clean %>%
  # Đếm số lượng khách hàng cho mỗi "premium_group"
  count(premium_group) %>%
  # Sắp xếp các nhóm 
  arrange(desc(premium_group)) %>% 
  # Tính toán % và vị trí để đặt nhãn
  mutate(percentage = n / sum(n),y_pos = cumsum(percentage) - 0.5 * percentage)
# In kết quả
kable(df_premium,
  # Đặt tên cột 
  col.names = c("Nhóm Phí", "Số lượng", "Tỷ lệ %", "Vị trí nhãn Y"),digits = c(0, 0, 2, 2),
  caption = "Bảng thống kê tỷ lệ các nhóm phí bảo hiểm",booktabs = TRUE)
Bảng thống kê tỷ lệ các nhóm phí bảo hiểm
Nhóm Phí Số lượng Tỷ lệ % Vị trí nhãn Y
High (trên Mean) 109863 0.55 0.27
Medium (Q1-Mean) 29934 0.15 0.62
Low (dưới Q1) 60203 0.30 0.85

TRỰC QUAN HÓA

# Khởi tạo ggplot, Đặt trục X là rỗng, Trục Y là cột tỷ lệ %
ggplot(df_premium, aes(x = "", y = percentage, fill = premium_group)) +
  # Vẽ biểu đồ cột
  geom_bar(stat = "identity", width = 1, color = "white") + 
  # Biến biểu đồ cột thành biểu đồ tròn
  coord_polar("y", start = 0) +
  # Thêm nhãn % vào giữa mỗi phần biểu đồ
  geom_text(aes(y = y_pos, label = percent(percentage, accuracy = 0.1)),
            color = "black", size = 5) +
  # Thêm Tiêu đề
  labs(title = "TỶ LỆ KHÁCH HÀNG THEO NHÓM PHÍ BẢO HIỂM")+
# Ẩn tất cả các yếu tố không cần thiết
theme(axis.title = element_blank(), axis.text = element_blank(),
      axis.ticks = element_blank(), panel.grid = element_blank())


Kết quả: Biểu đồ này cung cấp một cái nhìn tổng quan quan trọng về cơ cấu khách hàng của công ty, Kết quả chỉ ra rằng nhóm ‘High (trên Mean)’ là phân khúc khách hàng lớn nhất và quan trọng nhất:

Nhóm “High” (Phí cao) chiếm đa số: Phát hiện quan trọng nhất là nhóm khách hàng trả phí cao (“High (trên Mean)”) là nhóm lớn nhất, chiếm 54.9% (hơn một nửa) tổng số khách hàng.

Nhóm “Medium” (Phí trung bình) là nhỏ nhất: Nhóm “Medium (Q1-Mean)” là nhóm khách hàng ít ỏi nhất, chỉ chiếm 15.0%.

Sự phân cực: Dữ liệu cho thấy sự phân cực rõ rệt, với phần lớn khách hàng tập trung ở hai thái cực “Low” (30.1%) và “High” (54.9%), thay vì phân bổ đều.

Biểu đồ 5: Tỷ lệ ngừng sử dụng dịch vụ theo kết quả yêu cầu bồi thường

# Tạo data.frame "df_summary_b8"
df_summary_b8 <- df_clean %>%
  # Đếm số lượng theo cặp
  count(claim_output_encoded, churn_encoded) %>%
  # Nhóm theo "claim_output_encoded" (Nhóm Bị từ chối, Nhóm Được chấp thuận)
  group_by(claim_output_encoded) %>%
  # Tính toán % trong nhóm
  mutate(percentage = n / sum(n)) %>%
  # Sắp xếp
  arrange(churn_encoded) %>%
  # Tính vị trí đặt nhãn %
  mutate(y_position = cumsum(percentage) - 0.5 * percentage)
# In kết quả
kable(df_summary_b8, caption = "Thống kê tỷ lệ churn theo nhóm kết quả claim",
# Đặt tên cột tiếng Việt
 col.names = c("Kết quả Claim", "Trạng thái Churn", "Số lượng (n)",
                "Tỷ lệ % (trong nhóm)", "Vị trí nhãn Y" ),
 digits = c(0, 0, 0, 2, 2), booktabs = TRUE)
Thống kê tỷ lệ churn theo nhóm kết quả claim
Kết quả Claim Trạng thái Churn Số lượng (n) Tỷ lệ % (trong nhóm) Vị trí nhãn Y
0 0 70170 0.36 0.18
1 0 2558 0.37 0.18
0 1 122824 0.64 0.68
1 1 4448 0.63 0.68

TRỰC QUAN HÓA

# Khởi tạo ggplot, thiết lập trục x
ggplot(df_clean, 
       aes( x = factor(claim_output_encoded,labels = c("Bị từ chối", "Được chấp thuận")),
  fill = factor(churn_encoded, levels = c(1, 0),labels = c("Rời bỏ", "Ở lại")))) +
  # Thêm Lớp Biểu đồ Cột
  geom_bar(position = "fill") +
  # Thêm nhãn
  geom_text(data = df_summary_b8, 
            aes(y = y_position, label = scales::percent(percentage, accuracy = 0.1)),
            color = "black", size = 5) +
  # Tùy chỉnh Trục Y
  scale_y_continuous(labels = scales::percent) +
  # Tùy chỉnh Màu sắc
  scale_fill_manual(values = c("Ở lại" = "skyblue", "Rời bỏ" = "tomato")) +
  # Thêm Tiêu đề và Nhãn
  labs(title = " TỶ LỆ NGỪNG DỊCH VỤ THEO KẾT QUẢ YÊU CẦU BỒI THƯỜNG ",
       x = "Kết quả Yêu cầu", y = "Tỷ lệ", fill = "Trạng thái")+
# Điều chỉnh size tiêu đề
theme(plot.title = element_text(size = 12))


Kết quả: Biểu đồ cho thấy sự khác biệt về tỷ lệ khách hàng ngừng sử dụng dịch vụ giữa hai nhóm. kết quả yêu cầu bồi thường là “Bị từ chối” và “Được chấp thuận” hầu như không đáng kể.

Ở cả hai trường hợp, tỷ lệ khách hàng rời bỏ đều xấp xỉ 63–64%, trong khi tỷ lệ ở lại chỉ chiếm khoảng 36%. Điều này cho thấy kết quả xử lý yêu cầu bồi thường không phảiyếu tố ảnh hưởng mạnh đến quyết định tiếp tục sử dụng dịch vụ của khách hàng.

Biểu đồ 6: Tỷ lệ rời bỏ/tiếp tục theo Lý do Yêu cầu

# Tạo data.frame
df_gop_b9 <- df_clean %>%
  # Chọn cột 'churn_encoded' VÀ 4 cột dummy 'claim_reason'
  select(churn_encoded, starts_with("claim_reason_")) %>%
  # Gộp tất cả các cột TRỪ 'churn_encoded'
  pivot_longer(cols=-churn_encoded,names_to="claim_reason_label",values_to = "is_reason") %>%
  # Chỉ giữ lại các hàng có giá trị = 1 (là lý do thực sự)
  filter(is_reason == 1) %>%
  # Tạo các cột "claim_reason"
  mutate(claim_reason = str_remove(claim_reason_label, "claim_reason_"))
# Tính toán % chur trong từng nhóm lý do
df_summary_b9 <- df_gop_b9 %>%
  # Đếm số lượng theo cặp
  count(claim_reason, churn_encoded) %>%
  # Nhóm theo 4 Lý do
  group_by(claim_reason) %>%
  # Tính % trong nhóm
  mutate(percentage = n / sum(n)) %>%
  # Sắp xếp (để tính y_position cho đúng)
  arrange(churn_encoded) %>%
  # Tính vị trí đặt nhãn %
  mutate(y_position = cumsum(percentage) - 0.5 * percentage)
# In kết quả
kable( df_summary_b9,caption = "Thống kê tỷ lệ churn theo nhóm lý do",
       col.names = c( "Lý do Claim", "Trạng thái Churn","Số lượng (n)",
                      "Tỷ lệ % (trong nhóm)", "Vị trí nhãn Y"),
       digits = c(0, 0, 0, 2, 2),  booktabs = TRUE)
Thống kê tỷ lệ churn theo nhóm lý do
Lý do Claim Trạng thái Churn Số lượng (n) Tỷ lệ % (trong nhóm) Vị trí nhãn Y
Medical 0 39962 0.36 0.18
Other 0 10984 0.36 0.18
Phone 0 11028 0.37 0.18
Travel 0 10754 0.36 0.18
Medical 1 69901 0.64 0.68
Other 1 19203 0.64 0.68
Phone 1 18988 0.63 0.68
Travel 1 19180 0.64 0.68

TRỰC QUAN HÓA

# Khởi tạo ggplot, thiết lập trục
ggplot(df_gop_b9, aes(x = claim_reason,fill = factor(churn_encoded,
                          levels = c(1, 0), labels = c("Rời bỏ", "Ở lại")))) +
  # Thêm Lớp Biểu đồ Cột
  geom_bar(position = "fill") +
  # Thêm nhãn Tỷ lệ %
  geom_text(data = df_summary_b9, aes(y = y_position, 
      label = scales::percent(percentage, accuracy = 0.1)), color = "black", size = 5) +
  # Tùy chỉnh Trục Y
  scale_y_continuous(labels = scales::percent) +
  # Tùy chỉnh Màu sắc
  scale_fill_manual(values = c("Ở lại" = "skyblue", "Rời bỏ" = "tomato")) +
  # Thêm Tiêu đề và Nhãn
  labs(title = " TỶ LỆ NGỪNG DỊCH VỤ THEO LÝ DO YÊU CẦU",
       x = "Lý do Yêu cầu", y = "Tỷ lệ", fill = "Trạng thái") 


Kết quả: cho thấy không có sự khác biệt về Tỷ lệ Rời bỏ (Churn Rate) giữa 4 nhóm ‘Lý do Yêu cầu’. Tất cả các nhóm, bất kể là ‘Medical’ (chiếm đa số - Biểu đồ hay ‘Travel’ (chiếm thiểu số), đều có tỷ lệ khách hàng rời bỏ xấp xỉ 63.6%.

Biểu đồ 7: Tỷ lệ Chấp thuận theo Nhóm Phí (Premium Group)

#Tạo data.frame "df_summary_b10"
df_summary_b10 <- df_clean %>%
 # Xóa bất kỳ hàng nào bị thiếu giá trị trong 2 cột này
   na.omit(subset = c("premium_group", "claim_output_encoded")) %>%
  # Đếm số lượng theo cặp
  count(premium_group, claim_output_encoded) %>%
  # Nhóm theo 3 Nhóm Phí
  group_by(premium_group) %>%
  # Tính % trong nhóm
  mutate(percentage = n / sum(n)) %>%
  # Sắp xếp (0 = Từ chối, 1 = Chấp thuận)
  arrange(claim_output_encoded) %>% 
  # Tính vị trí đặt nhãn % (cho biểu đồ cột chồng)
  mutate(y_position = cumsum(percentage) - 0.5 * percentage)
# In kết quả
kable( df_summary_b10, caption = "Thống kê kết quả tỷ lệ claim theo nhóm premium",
       col.names = c("Nhóm Premium", "Kết quả Claim",
                "Số lượng (n)", "Tỷ lệ % (trong nhóm)", "Vị trí nhãn Y"),
       digits = c(0, 0, 0, 4, 4), booktabs = TRUE)
Thống kê kết quả tỷ lệ claim theo nhóm premium
Nhóm Premium Kết quả Claim Số lượng (n) Tỷ lệ % (trong nhóm) Vị trí nhãn Y
Low (dưới Q1) 0 53197 0.8836 0.4418
Medium (Q1-Mean) 0 29934 1.0000 0.5000
High (trên Mean) 0 109863 1.0000 0.5000
Low (dưới Q1) 1 7006 0.1164 0.9418

TRỰC QUAN HÓA

# Khởi tạo ggplot,thiết lập trục
ggplot(df_clean, aes(x = premium_group, fill = factor(claim_output_encoded,
                   levels = c(1, 0), labels = c("Được chấp thuận", "Bị từ chối")))) +
  # Thêm Lớp Biểu đồ Cột
  geom_bar(position = "fill") +
  # Thêm nhãn Tỷ lệ %
  geom_text(data = df_summary_b10, aes(y = y_position, 
                  label = scales::percent(percentage, accuracy = 0.1)), 
                  color = "black", size = 5, check_overlap = TRUE) +
  # Tùy chỉnh Trục Y
  scale_y_continuous(labels = scales::percent) +
  # Tùy chỉnh Màu sắc
  scale_fill_manual(values = c("Được chấp thuận" = "skyblue", "Bị từ chối" = "tomato")) +
  # Thêm Tiêu đề và Nhãn
  labs(title = " TỶ LỆ CHẤP THUẬN THEO NHÓM PHÍ",  x = "Nhóm Phí", y = "Tỷ lệ", fill = "Kết quả Yêu cầu")


Kết quả: Nhóm khách hàng có mức phí thấp (Low – dưới Q1) có 11,6% yêu cầu được chấp thuận, trong khi 88,4% bị từ chối. Đáng chú ý, ở hai nhóm có mức phí trung bình (Q1–Mean) và mức phí cao (trên Mean), tất cả yêu cầu bồi thường đều bị từ chối (100%).

Điều này cho thấy xu hướng tỷ lệ chấp thuận không tăng theo mức phí, ngược lại, những khách hàng đóng phí cao hơn không nhận được lợi thế rõ ràng trong việc được phê duyệt yêu cầu bồi thường.

Kết quả này có thể phản ánh chính sách xét duyệt nghiêm ngặt của công ty bảo hiểm, hoặc cho thấy mức phí cao không đồng nghĩa với khả năng được chấp thuận cao hơn.

Biểu đồ 8: Tỷ lệ Chấp thuận theo Nhóm Yêu cầu (Claim Group)

# Tạo data.frame "df_summary_b8"
df_summary_b8 <- df_clean %>%
  # Loại bỏ NA (nếu có)
  na.omit(subset = c("claim_group", "claim_output_encoded")) %>%
  # Đếm số lượng theo cặp
  count(claim_group, claim_output_encoded) %>%
  # Nhóm theo 4 Nhóm "Claim Amount" (Low, Medium, High, Very High)
  group_by(claim_group) %>%
  # Tính % trong nhóm
  mutate(percentage = n / sum(n)) %>%
  # Sắp xếp (0 = Từ chối, 1 = Chấp thuận)
  arrange(claim_output_encoded) %>%
  # Tính vị trí đặt nhãn % (cho biểu đồ cột chồng)
  mutate(y_position = cumsum(percentage) - 0.5 * percentage)
# In kết quả
kable( df_summary_b8, caption = "Thống kê tỷ lệ kết quả claim theo nhóm claim amount",
       col.names = c("Nhóm Claim Amount", "Kết quả Claim","Số lượng (n)",
                       "Tỷ lệ % (trong nhóm)", "Vị trí nhãn Y"),
       digits = c(0, 0, 0, 4, 4), booktabs = TRUE)
Thống kê tỷ lệ kết quả claim theo nhóm claim amount
Nhóm Claim Amount Kết quả Claim Số lượng (n) Tỷ lệ % (trong nhóm) Vị trí nhãn Y
Low 0 43037 0.86 0.43
Medium 0 50027 1.00 0.50
High 0 49942 1.00 0.50
Very High 0 49988 1.00 0.50
Low 1 7006 0.14 0.93

TRỰC QUAN HÓA

# Khởi tạo ggplot,thiết lập trục
ggplot(df_clean, aes(x = claim_group,fill = factor(claim_output_encoded,
             levels = c(1, 0), labels = c("Được chấp thuận", "Bị từ chối")))) +
  # Thêm Lớp Biểu đồ Cột
  geom_bar(position = "fill") +
  # Thêm nhãn Tỷ lệ %
  geom_text(data = df_summary_b8, aes(y = y_position, 
            label = scales::percent(percentage, accuracy = 0.1)),
            color = "black", size = 5, check_overlap = TRUE) +
  # Tùy chỉnh Trục Y
  scale_y_continuous(labels = scales::percent) +
  # Tùy chỉnh Màu sắc
  scale_fill_manual(values = c("Được chấp thuận" = "skyblue", "Bị từ chối" = "tomato")) +
  # Thêm Tiêu đề và Nhãn
  labs(title = " TỶ LỆ CHẤP THUẬN THEO NHÓM YÊU CẦU",
       x = "Nhóm Yêu cầu", y = "Tỷ lệ", fill = "Kết quả Yêu cầu")


Kết quả: nhóm Low có 14% yêu cầu được chấp thuận, trong khi 86% bị từ chối. Đáng chú ý, ở ba nhóm còn lại là Medium, High và Very High, toàn bộ yêu cầu (100%) đều bị từ chối.

Điều này phản ánh xu hướng rõ rệt rằng càng có giá trị yêu cầu bồi thường cao, khả năng được chấp thuận càng thấp.

Kết quả có thể xuất phát từ việc công ty bảo hiểm áp dụng quy trình xét duyệt nghiêm ngặt hơn đối với các yêu cầu có giá trị lớn,nhằm kiểm soát rủi ro và hạn chế gian lận trong bồi thường.

Biểu đồ 9: Tỷ lệ Chấp thuận theo Lý do Yêu cầu

# TÍNH TOÁN % KẾT QUẢ CLAIM THEO TỪNG LÝ DO CLAIM
# Tạo data.frame "df_gop_b9"
df_gop_b9 <- df_clean %>%
   # Chọn cột 'claim_output_encoded' VÀ 4 cột 'claim_reason'
   select(claim_output_encoded, starts_with("claim_reason_")) %>%
   # Gộp tất cả các cột TRỪ 'claim_output_encoded'
   pivot_longer(cols = -claim_output_encoded, names_to = "claim_reason_label",   
                values_to = "is_reason"  ) %>%    
   # Chỉ giữ lại các hàng có giá trị = 1 (là lý do thực sự)
   filter(is_reason == 1) %>%
   # Tạo cột "claim_reason"
   mutate(claim_reason = str_remove(claim_reason_label, "claim_reason_"))
# Tính toán % Kết quả Claim theo từng nhóm lý do
df_summary_b9 <- df_gop_b9 %>%
   # Đếm số lượng theo cặp
   count(claim_reason, claim_output_encoded) %>%
   # Nhóm theo 4 Lý do
   group_by(claim_reason) %>%
   # Tính % trong nhóm
   mutate(percentage = n / sum(n)) %>%
   # Sắp xếp (0=Từ chối, 1=Chấp thuận)
   arrange(claim_output_encoded) %>%
   # Tính vị trí đặt nhãn %
   mutate(y_position = cumsum(percentage) - 0.5 * percentage)
# In kết quả 
kable( df_summary_b9, caption = "Thống kê kết quả tỷ lệ claim theo nhóm lý do claim",
       col.names = c("Lý do Claim", "Kết quả Claim","Số lượng (n)", 
                     "Tỷ lệ % (trong nhóm)", "Vị trí nhãn Y" ),
       digits = c(0, 0, 0, 4, 4), booktabs = TRUE)
Thống kê kết quả tỷ lệ claim theo nhóm lý do claim
Lý do Claim Kết quả Claim Số lượng (n) Tỷ lệ % (trong nhóm) Vị trí nhãn Y
Medical 0 109863 1.0000 0.500
Other 0 23181 0.7679 0.384
Phone 0 30016 1.0000 0.500
Travel 0 29934 1.0000 0.500
Other 1 7006 0.2321 0.884

TRỰC QUAN HÓA

# Khởi tạo ggplot, thiết lập trục
ggplot(df_gop_b9, aes(x = claim_reason, fill = factor(claim_output_encoded,
           levels = c(1, 0), labels = c("Được chấp thuận", "Bị từ chối")))) +
  # Thêm Lớp Biểu đồ Cột
  geom_bar(position = "fill") +
  # Thêm nhãn Tỷ lệ %
  geom_text(data = df_summary_b9,aes(y = y_position, label = scales::percent(percentage,
             accuracy = 0.1)), color = "black", size = 5, check_overlap = TRUE) +
  # Tùy chỉnh Trục Y
  scale_y_continuous(labels = scales::percent) +
  # Tùy chỉnh Màu sắc
  scale_fill_manual(values = c("Được chấp thuận" = "skyblue", "Bị từ chối" = "tomato")) +
  # Thêm Tiêu đề và Nhãn
  labs(title = " TỶ LỆ CHẤP THUẬN THEO LÝ DO YÊU CẦU",
       x = "Lý do Yêu cầu", y = "Tỷ lệ", fill = "Kết quả Yêu cầu")


Kết quả: chỉ có nhóm Other có 23,2% yêu cầu được chấp thuận, trong khi 76,8% bị từ chối. Ba nhóm còn lại gồm Medical, Phone và Travel đều có tỷ lệ bị từ chối đạt 100%, tức là không có yêu cầu nào được chấp thuận. Điều này cho thấy chính sách phê duyệt bồi thường của công ty khá nghiêm ngặt, đặc biệt với các yêu cầu thuộc nhóm Medical và Travel.

Biểu đồ 10: Phân phối Lợi nhuận (Profitability)

# Khởi tạo ggplot, sử dụng data "df_clean"
ggplot(df_clean, aes(x = profitability)) +
  # Vẽ đường cong mật độ
  geom_density(fill = "darkgreen") + 
  # Thêm đường trung bình (màu đỏ, đứt nét)
  geom_vline(aes(xintercept = mean(profitability)), 
             color = "red", linetype = "dashed",linewidth = 1.2) +
  # Đặt tiêu đề và nhãn trục
  labs(title = " PHÂN PHỐI LỢI NHUẬN TỪ HỢP ĐỒNG",subtitle = "Đường đỏ là lợi nhuận trung bình",
       x = "Lợi nhuận (Premium - Claim)",  y = "Mật độ (Density)")


Nhận xét: Quan sát cho thấy phân phối lợi nhuận không đồng nhất, có nhiều đỉnh (multi-modal), phản ánh sự chênh lệch lớn giữa các nhóm hợp đồng. Phần lớn các hợp đồng tập trungvùng lợi nhuận cao, tức là phí thu được lớn hơn số tiền bồi thường, cho thấy công ty bảo hiểm nhìn chung đang hoạt động có lãi.

Tuy nhiên, cũng có một tỷ lệ hợp đồng nằm ở vùng lợi nhuận thấp hoặc gần bằng 0, cho thấy vẫn tồn tại các trường hợp rủi ro cao hoặc chi phí bồi thường lớn.

Đường trung bình nằm lệch về bên phải, hàm ý rằng đa số hợp đồng mang lại lợi nhuận cao hơn mức trung bình, và chỉ một phần nhỏ hợp đồng làm giảm hiệu quả chung.

Biểu đồ 11: Phân phối Tỷ lệ Tổn thất (Loss Ratio)

# Khởi tạo ggplot, sử dụng data "df_clean"
ggplot(df_clean, aes(x = loss_ratio)) +
  # Vẽ đường cong mật độ
  geom_density(fill = "lightblue",color = "steelblue",alpha = 0.6, linewidth = 0.7) +       
  # Thêm đường trung bình (nét đứt)
  geom_vline(aes(xintercept = mean(loss_ratio)),color = "red",linetype = "dashed",linewidth = 1) +         
  # "Phóng to" (Zoom) trục X mà không cắt bỏ dữ liệu
  coord_cartesian(xlim = c(0, 0.5)) +
  # Định dạng trục X
  scale_x_continuous(labels = scales::percent) +
  # Đặt tiêu đề
  labs(title = " PHÂN PHỐI MẬT ĐỘ TỶ LỆ TỔN THẤT",
       subtitle = "Đường xanh đậm (nét đứt) là tỷ lệ trung bình",
       x = "Tỷ lệ tổn thất", y = "Mật độ") +
  # Dùng 'theme_minimal' (có lưới)
  theme(panel.border = element_rect(color = "black", fill = NA,linewidth = 0.5))


Nhận xét: Quan sát cho thấy, phân phối có dạng chuông khá cân đối, tập trung chủ yếu quanh vùng 10–15%, và đường đỏ đứt nét biểu diễn tỷ lệ tổn thất trung bình nằm gần 10%.

Điều này cho thấy đa số hợp đồng có tỷ lệ tổn thất thấp, nghĩa là số tiền bồi thường chiếm một phần nhỏ so với phí bảo hiểm thu được, phản ánh hoạt động kinh doanh bảo hiểm đang ở mức sinh lợi ổn định.

Ngoài ra, phần đuôi bên phải mỏng cho thấy ít hợp đồng có tổn thất cao, chứng tỏ rủi ro được kiểm soát tốtphân bố lợi nhuận tương đối bền vững.

Biểu đồ 12: Tổng phí bảo hiểm theo khách hàng

# Tạo data.frame "df_tonghop_phi"
df_tonghop_phi <- df_clean %>%
  # Nhóm toàn bộ 200,000 hàng thành 3 nhóm (Low, Medium, High)
  group_by(premium_group) %>%
  # Tính tổng cột 'category_premium' cho từng nhóm
  summarise( Total_Premium = sum(category_premium, na.rm = TRUE))
# In kết quả:
kable(df_tonghop_phi, caption = "Tổng phí bảo hiểm theo từng nhóm phí",
      col.names = c("Nhóm phí", "Tổng Phí"), digits = 0,  align = "lr", booktabs = TRUE)
Tổng phí bảo hiểm theo từng nhóm phí
Nhóm phí Tổng Phí
Low (dưới Q1) 68324613
Medium (Q1-Mean) 143503596
High (trên Mean) 1580928570

TRỰC QUAN HÓA

# Khởi tạo ggplot, thiết lập trục
ggplot(df_tonghop_phi, aes(x = premium_group, y = Total_Premium,  fill = premium_group)) +
  # Vẽ cột
  geom_col() + 
  # Thêm nhãn số
  geom_text(aes(label = comma(Total_Premium, accuracy = 1)), vjust = -0.5, size = 5) +
  # Nới rộng trục Y (thêm 15%) để không bị lấp số
  scale_y_continuous( labels = comma, expand = expansion(mult = c(0, 0.15)) ) +
  # Thêm tiêu đề
  labs(title ="TỔNG PHÍ BẢO HIỂM THEO NHÓM KHÁCH HÀNG",x ="Nhóm Phí", y ="Tổng Phí Bảo Hiểm") 


Kết quả: cho thấy sự chênh lệch rất lớn giữa các nhóm:

Nhóm High đóng góp khoảng 1,58 tỷ, chiếm phần lớn tổng phí toàn bộ. Nhóm Medium chỉ đóng góp khoảng 143 triệu. Nhóm Low thấp nhất, chỉ khoảng 68 triệu.

Điều này chứng tỏ doanh thu phí bảo hiểm tập trung chủ yếu ở nhóm khách hàng có mức phí cao, và một số ít khách hàng lớn đóng góp phần lớn tổng doanh thu.

Qua đó doanh nghiệp cần duy trì, chăm sóc tốt nhóm khách hàng này, đồng thời xây dựng chính sách khuyến khích nhóm trung bình và thấp để tăng giá trị tổng thể.

Biểu đồ 13: Tỷ lệ Chấp thuận theo Nhóm Yêu cầu (Claim Group)

# Tạo data frame mới "df_gop_b13"
df_gop_b13 <- df_clean %>%
  # Chọn cột 'profitability' VÀ 4 cột dummy 'claim_reason'
  select(profitability, starts_with("claim_reason_")) %>%
  # Gộp tất cả các cột TRỪ 'profitability'
  pivot_longer(cols = -profitability, names_to = "claim_reason_label",
               values_to = "is_reason") %>%
  # Chỉ giữ lại các hàng có giá trị = 1 (là lý do thực sự)
  filter(is_reason == 1) %>%
  # Tạo cột "claim_reason"
  mutate(claim_reason = str_remove(claim_reason_label, "claim_reason_"))

#Tính Lợi nhuận TRUNG BÌNH (mean)
# Lấy dữ liệu từ "df_gop_b13"
df_summary_b13 <- df_gop_b13 %>%
  # Nhóm theo 4 Lý do (Medical, Other, Phone, Travel)
  group_by(claim_reason) %>%
  # Tính Lợi nhuận TRUNG BÌNH (mean) cho mỗi nhóm
  summarise(mean_profit = mean(profitability, na.rm = TRUE))
# In kết quả
kable(df_summary_b13, caption = "Lợi nhuận trung bình theo nhóm lý do claim",
      col.names = c("Lý do Claim", "Lợi nhuận Trung bình"),
      digits = 2,  align = "lr",  booktabs = TRUE)
Lợi nhuận trung bình theo nhóm lý do claim
Lý do Claim Lợi nhuận Trung bình
Medical 12591.26
Other 349.07
Phone 1640.58
Travel 4194.79

TRỰC QUAN HÓA

# Khởi tạo ggplot, thiết lập trục
ggplot(df_summary_b13, aes(x = claim_reason, y = mean_profit, fill = claim_reason)) +
  # Vẽ cột
  geom_col(show.legend = FALSE, color = "black") +
  # Thêm nhãn
  geom_text(aes(label = round(mean_profit, 0)),vjust = -0.5, color = "black",size = 5) +
  # Mở rộng trục Y để có chỗ cho nhãn số
  scale_y_continuous(expand = expansion(mult = c(0, 0.15))) +
  # Đặt tiêu đề và nhãn
  labs(title = "LỢI NHUẬN TRUNG BÌNH THEO LÝ DO YÊU CẦU",
       x = "Lý do Yêu cầu",   y = "Lợi nhuận Trung bình")


Kết quả:

Nhóm Medical mang lại lợi nhuận trung bình cao nhất (12.591), vượt trội so với các nhóm khác. Nhóm Travel đứng thứ hai với 4.195. Nhóm Phone đạt mức 1.641. Nhóm Other có lợi nhuận trung bình thấp nhất (349).

Sự chênh lệch lớn này cho thấy các yêu cầu liên quan đến Medical là nguồn sinh lợi chính của công ty, trong khi các yêu cầu khác (đặc biệt là Other) mang lại lợi nhuận thấp, cần được xem xét chính sách hoặc tối ưu chi phí.

Biểu đồ 14: So sánh Tỷ lệ Tổn thất (“Được chấp thuận” vs “Bị từ chối”)

# Tạo data frame mới "df_b14"
df_b14 <- df_clean %>%
  # Nhóm theo 2 nhóm "Kết quả Yêu cầu"
  group_by(claim_output_encoded) %>%
  # Tính Tỷ lệ tổn thất TRUNG BÌNH (mean) cho mỗi nhóm
  summarise(mean_loss_ratio = mean(loss_ratio, na.rm = TRUE))
# Tạo cột mớimới
df_b14 %>% mutate(KetQua = if_else(claim_output_encoded == 1, "Được chấp thuận", "Bị từ chối"),
  TyLe = scales::percent(mean_loss_ratio, accuracy = 0.1)) %>% 
  # Sắp xếp lại cột
   select(KetQua, TyLe)%>%
  # 5. In ra kable
  kable( caption = "Tỷ lệ tổn thất trung bình)",
         col.names = c("Kết quả yêu cầu", "Tỷ lệ Tổn thất trung bình"),
         align = "lr", booktabs = TRUE)
Tỷ lệ tổn thất trung bình)
Kết quả yêu cầu Tỷ lệ Tổn thất trung bình
Bị từ chối 12.8%
Được chấp thuận 3.0%

TRỰC QUAN HÓA

# Khởi tạo ggplot, thiết lập trục
ggplot( df_b14, aes(
  x = factor(claim_output_encoded, labels = c("Bị từ chối", "Được chấp thuận")),
  y = mean_loss_ratio,fill = factor(claim_output_encoded, 
                                    labels = c("Bị từ chối", "Được chấp thuận")) )) +
  #  Vẽ biểu đồ Cột
  geom_col() +
  # Thêm nhãn % (hiển thị giá trị trung bình)
  geom_text(aes(label = scales::percent(mean_loss_ratio, accuracy = 0.1)),
            vjust = -0.5,color = "black",size = 5) +
  # Định dạng nhãn trục Y thành %, mở rộng trục Y để nhãn số không bị cắt
  scale_y_continuous(labels = scales::percent,expand = expansion(mult = c(0, 0.15))) +
  # Tùy chỉnh Màu sắc
  scale_fill_manual(values = c("Bị từ chối" = "tomato","Được chấp thuận" = "skyblue")) +
  # Đặt tiêu đề và nhãn
  labs(  title = "SO SÁNH TỶ LỆ TỔN THẤT",
         x = "Kết quả Yêu cầu", y = "Tỷ lệ Tổn thất Trung bình (Mean Loss Ratio)") +
  # Ẩn chú giải (legend)
  theme(legend.position = "none")


Nhận xét: Biểu đồ cho thấy sự khác biệt rõ rệt về tỷ lệ tổn thất trung bình giữa hai nhóm yêu cầu bảo hiểm. Với các yêu cầu bị từ chối, tỷ lệ tổn thất trung bình đạt 12.8% — cao gấp hơn 4 lần so với nhóm còn lại. Trong khi đó, các yêu cầu được chấp thuận chỉ có tỷ lệ tổn thất trung bình khoảng 3.0%.

Điều này cho thấy: Các yêu cầu bị từ chối thường đi kèm với rủi ro tổn thất cao hơn, Còn những yêu cầu được chấp thuận có mức tổn thất thấp và ổn định hơn, phản ánh quy trình thẩm định rủi ro hiệu quả của công ty.

Biểu đồ 15: So sánh Tỷ lệ Tổn thất theo Lý do Yêu cầu

# Tạo data frame mới "df_gop_b15"
df_gop_b15 <- df_clean %>%
  # Chọn cột 'loss_ratio' VÀ 4 cột 'claim_reason'
  select(loss_ratio, starts_with("claim_reason_")) %>%
  # Gộp tất cả các cột TRỪ 'loss_ratio'
  pivot_longer( cols = -loss_ratio, names_to = "claim_reason_label",
                values_to = "is_reason" ) %>%
  # Chỉ giữ lại các hàng có giá trị = 1 (là lý do thực sự)
  filter(is_reason == 1) %>%
  # Tạo cột "claim_reason"
  mutate(claim_reason = str_remove(claim_reason_label, "claim_reason_")) 
# In kết quả
kable( head(df_gop_b15, 10),
       caption = "10 dòng đầu sau khi gộp 4 cột dummy (Giữ lại Loss Ratio)", booktabs = TRUE)
10 dòng đầu sau khi gộp 4 cột dummy (Giữ lại Loss Ratio)
loss_ratio claim_reason_label is_reason claim_reason
0.0786400 claim_reason_Travel 1 Travel
0.1000695 claim_reason_Medical 1 Medical
0.1365333 claim_reason_Phone 1 Phone
0.1242667 claim_reason_Phone 1 Phone
0.1274667 claim_reason_Phone 1 Phone
0.1134121 claim_reason_Medical 1 Medical
0.1564976 claim_reason_Medical 1 Medical
0.0927318 claim_reason_Other 1 Other
0.1194667 claim_reason_Phone 1 Phone
0.1377345 claim_reason_Medical 1 Medical

TRỰC QUAN HÓA

# Khởi tạo ggplot, thiết lập trục
ggplot(df_gop_b15, aes(x = loss_ratio)) +
  # Vẽ đường cong mật độ
  geom_density(aes(fill = claim_reason), alpha = 0.7,color = "black", show.legend = FALSE) +
  # Tách thành 4 biểu đồ con (2 cột)
  facet_wrap(~ claim_reason, ncol = 2) +
  # "Phóng to" (Zoom) vào khu vực 0-50%
  coord_cartesian(xlim = c(0, 0.5)) +
  # Định dạng trục X là %
  scale_x_continuous(labels = scales::percent) +
  # Đặt tiêu đề và nhãn
  labs(title = "PHÂN PHỐI TỶ LỆ TỔN THẤT THEO LÝ DO YÊU CẦU",
       subtitle = "Đã phóng to, chỉ hiển thị 0-50%",
       x = "Tỷ lệ Tổn thất (Loss Ratio)",  y = "Mật độ") 


Nhận xét:

Nhóm Medical: Có mật độ tập trung cao quanh mức 10–15%, cho thấy các hợp đồng loại này có tỷ lệ tổn thất khá ổn định. Nhóm Other: Tỷ lệ tổn thất trải đều hơn, mật độ thấp → mức độ biến động cao, khó dự đoán hơn. Nhóm Phone: Phân phối tập trung nhất, chủ yếu quanh 10–15%, chứng tỏ loại yêu cầu này có rủi ro thấp và đồng đều. Nhóm Travel: Có phạm vi dao động rộng hơn (từ 10–25%), phản ánh mức độ rủi ro cao hơn, dễ phát sinh biến động tổn thất.

Biểu đồ 16: Top 10 công ty phải chịu bồi thường cao nhất

# Tạo data frame tóm tắt "top_6_companies"
top_6_companies <- df %>%
  # Nhóm theo Tên Công ty
  group_by(`Company Name`) %>%
  # Tính TỔNG số tiền bồi thường
  summarise(Total_Claim_Amount = sum(`Claim Amount`, na.rm = TRUE) ) %>%
  # Sắp xếp giảm dần
  arrange(desc(Total_Claim_Amount)) %>%
  # Lấy 6 hàng đầu tiên 
  head(6)
# In kết quả ra
kable(top_6_companies,caption = "Top 6 Công ty có Tổng Tiền Bồi thường Cao nhất",
      col.names = c("Tên Công ty", "Tổng Tiền Bồi thường"), booktabs = TRUE)
Top 6 Công ty có Tổng Tiền Bồi thường Cao nhất
Tên Công ty Tổng Tiền Bồi thường
Smith LLC 307837
Smith Group 298943
Smith and Sons 287755
Smith Ltd 277418
Smith Inc 257402
Smith PLC 250624

TRỰC QUAN HÓA

# Khởi tạo ggplot, thiết lập trục
ggplot(top_6_companies,aes(x = reorder(`Company Name`, Total_Claim_Amount),
           y = Total_Claim_Amount)) +
  # Vẽ đường (segment)
  geom_segment(aes(x = reorder(`Company Name`, Total_Claim_Amount), 
              xend = reorder(`Company Name`, Total_Claim_Amount), 
              y = 0, yend = Total_Claim_Amount), color = "grey",linewidth = 1) +
  # Vẽ điểm (point)
  geom_point(aes(color = `Company Name`), size = 5, show.legend = FALSE) +
  # Thêm nhãn số (dùng scales::comma cho số lớn)
  geom_text(aes(label = scales::comma(Total_Claim_Amount)),color = "black",size = 5, hjust = -0.3) +
  # Biến biểu đồ cột dọc thành biểu đồ ngang (dễ đọc tên công ty hơn)
  coord_flip() +
  # Mở rộng trục Y (bây giờ là trục X)
  scale_y_continuous(
    labels = scales::comma,expand = expansion(mult = c(0, 0.15))) +
  # Đặt tiêu đề
  labs(title = "Top 6 Công ty có Tổng Tiền Bồi thường Cao nhất",
       x = "Tên Công ty",y = "Tổng Tiền Bồi thường") +
  # Xóa nền xám, giữ lưới ngang
  theme(panel.grid.major.y = element_blank(), panel.grid.minor.x = element_blank())

Biểu đồ 17: Công ty đứng đầu theo từng nguyên nhân

# Đếm số lần xuất hiện của mỗi cặp (Công ty, Lý do)
company_reason_counts <- df %>%
  count(`Company Name`, `Claim Reason`, name = "So_Luong")
# Với MỖI 'Claim Reason', tìm 'Company Name' có 'So_Luong' cao nhất
top_company_per_reason <- company_reason_counts %>%
  # Nhóm dữ liệu theo 4 Lý do (Medical, Other, Phone, Travel)
  group_by(`Claim Reason`) %>%
  # Sắp xếp và lấy hàng cao nhất (n=1)
  slice_max(order_by = So_Luong, n = 1, with_ties = FALSE) %>%
  ungroup() %>%
  # Sắp xếp kết quả cuối cùng theo thứ tự A-Z của "Claim Reason"
  arrange(`Claim Reason`) 
# In kết quả ra
kable(top_company_per_reason,
      caption = "Công ty có số lượng yêu cầu cao nhất (Theo từng Nguyên nhân)",
      col.names = c("Tên Công ty", "Nguyên nhân", "Số lượng Yêu cầu"),booktabs = TRUE)
Công ty có số lượng yêu cầu cao nhất (Theo từng Nguyên nhân)
Tên Công ty Nguyên nhân Số lượng Yêu cầu
Smith LLC Medical 153
Smith Group Other 55
Smith and Sons Phone 44
Smith PLC Travel 39

TRỰC QUAN HÓA

# Khởi tạo ggplot, thiết lập trục
ggplot(top_company_per_reason, aes(x = `Claim Reason`, y = factor(1), fill = So_Luong)) +
  # Vẽ ô vuông
  geom_tile(color = "white", linewidth = 2) +
  # Thêm nhãn 
  geom_text( aes(label = paste(`Company Name`, scales::comma(So_Luong), sep = "\n")),
             color = "black",size = 5) +
  # Đổi thang màu 
  scale_fill_gradient(low = "moccasin", high = "darkorange", name = "Số lượng") +
  # Đặt tiêu đề
  labs(title = "Công ty có nhiều Yêu cầu nhất (Theo từng Nguyên nhân)",
       x = "Nguyên nhân Yêu cầu",  y = NULL) + 
  # Tùy chỉnh giao diện
  theme(legend.position = "bottom", plot.title = element_text(hjust = 0.5, face = "bold"), 
    # Ẩn các yếu tố của trục Y 
    axis.text.y = element_blank(), axis.ticks.y = element_blank(),panel.grid = element_blank() )

Biểu đồ 18: Tỷ lệ Chấp thuận theo Nhóm Yêu cầu (Claim Group)

# Tạo data frame tóm tắt "top_5_churn_companies"
top_5_churn_companies <- df %>%
  # Lọc ra những khách hàng "Rời bỏ"
  filter(Churn == "Yes") %>%
  # Nhóm theo Tên Công ty
  group_by(`Company Name`) %>%
  # Đếm số lượng khách hàng rời bỏ trong mỗi công ty
  summarise(Total_Churned = n()) %>%
  # Sắp xếp giảm dần
  arrange(desc(Total_Churned)) %>%
  # Lấy 5 hàng đầu tiên
  head(5)
# In kết quả 
kable(top_5_churn_companies,caption = "Top 5 Công ty có Số lượng Khách hàng Rời bỏ Cao nhất",
      col.names = c("Tên Công ty", "Số lượng Rời bỏ"), booktabs = TRUE)
Top 5 Công ty có Số lượng Khách hàng Rời bỏ Cao nhất
Tên Công ty Số lượng Rời bỏ
Smith Group 179
Smith LLC 169
Smith and Sons 160
Smith Ltd 156
Smith Inc 150

TRỰC QUAN HÓA

# Khởi tạo ggplot, thiết lập trục
ggplot(top_5_churn_companies, 
       aes(x = reorder(`Company Name`, Total_Churned), y = Total_Churned)) +
  # Vẽ đường (segment)
  geom_segment(aes(x = reorder(`Company Name`, Total_Churned), 
                   xend = reorder(`Company Name`, Total_Churned), 
                   y = 0, yend = Total_Churned),  color = "grey",linewidth = 1) +
  # Vẽ điểm (point)
  geom_point(aes(color = `Company Name`), size = 5, show.legend = FALSE) +
  # Thêm nhãn số (số lượng rời bỏ)
  geom_text(aes(label = Total_Churned), color = "black", size = 5, hjust = -1) + 
  # Lật trục (Lollipop nằm ngang dễ đọc hơn)
  coord_flip() +
  # Mở rộng trục Y (bây giờ là trục X)
  scale_y_continuous(expand = expansion(mult = c(0, 0.15))) +
  # Đặt tiêu đề
  labs(title = "Top 5 Công ty có Số lượng Khách hàng Rời bỏ Cao nhất",
       x = "Tên Công ty",  y = "Số lượng Khách hàng Rời bỏ") +
  # Xóa lưới dọc (major.y) và lưới ngang phụ (minor.x)
  theme( panel.grid.major.y = element_blank(),panel.grid.minor.x = element_blank())

Biểu đồ 19: Top 6 Công ty từ chối bồi thường nhiều nhất (Số lượng)

#  Dùng 'df' (dữ liệu gốc)
top_6_rejected <- df %>%
  filter(`Claim Request output` == "No") %>%
  count(`Company Name`, name = "So_Luong_Tu_Choi") %>%
  arrange(desc(So_Luong_Tu_Choi)) %>%
  head(6)
# Tính thêm cột % cho biểu đồ tròn
df_pie_rejected_6 <- top_6_rejected %>% ungroup() %>% 
  # Tính %: Lấy số lượng của 1 công ty / TỔNG số lượng của 6 công ty
  mutate(percentage = So_Luong_Tu_Choi / sum(So_Luong_Tu_Choi),
         # Nhãn cho biểu đồ: chỉ %
         label_pct = scales::percent(percentage, accuracy = 0.1))
# In kết quả tính toán
kable(df_pie_rejected_6,
      caption = "Top 6 Công ty Từ chối Yêu cầu nhiều nhất)",
      col.names = c("Tên Công ty", "Số lượng Từ chối", "Tỷ lệ % (trong Top 6)", "Nhãn %"),
      digits = c(0, 0, 2, 0),align = "lrrr", booktabs = TRUE)
Top 6 Công ty Từ chối Yêu cầu nhiều nhất)
Tên Công ty Số lượng Từ chối Tỷ lệ % (trong Top 6) Nhãn %
Smith Group 264 0.18 18.4%
Smith LLC 247 0.17 17.2%
Smith and Sons 247 0.17 17.2%
Smith Ltd 235 0.16 16.4%
Smith Inc 226 0.16 15.8%
Smith PLC 215 0.15 15.0%

TRỰC QUAN HÓA

# Khởi tạo ggplot, thiết lập trục
ggplot(df_pie_rejected_6, aes(x = "", y = percentage, fill = `Company Name`)) +
  # Vẽ cột
  geom_col(width = 1, color = "white") +
  # Biến thành biểu đồ tròn
  coord_polar(theta = "y", start = 0) +
  # Thêm nhãn % 
  geom_text( aes(label = label_pct), position = position_stack (vjust = 0.5),
             color = "black", size = 5) +
  # Đặt tiêu đề
  labs(title = "Tỷ trọng Top 6 Công ty Từ chối Yêu cầu (Pie Chart)", fill = "Tên Công ty")+
  # Xóa nền và lưới
 theme(axis.title = element_blank(), axis.text = element_blank(),
      axis.ticks = element_blank(), panel.grid = element_blank())

Biểu đồ 20: Tính toán Tỷ lệ (Medical/các nguyên nhân còn lại)trong nhóm BMI cao

# 
df_gop_bmi <- df_clean %>%
   # Chọn cột 'bmi_group' VÀ 4 cột dummy 'claim_reason'
   select(bmi_group, starts_with("claim_reason_")) %>%
   # Gộp tất cả các cột TRỪ 'bmi_group'
   pivot_longer(cols = -bmi_group, names_to = "claim_reason_label",
     values_to = "is_reason") %>%
   # Chỉ giữ lại các hàng có giá trị = 1 (là lý do thực sự)
   filter(is_reason == 1) %>%
   # Tạo cột "claim_reason"
   mutate(claim_reason = str_remove(claim_reason_label, "claim_reason_"))
# Lọc NHÓM BMI CAO và phân loại Nguyên nhân
df_bmi_high_summary <- df_gop_bmi %>%
   # Chỉ giữ lại nhóm BMI cao
   filter(bmi_group == "Overweight (25-28)") %>%
   # Phân loại: "Medical" vs "Các nguyên nhân khác"
   mutate(Reason_Type = if_else(claim_reason == "Medical", "Medical","Các nguyên nhân khác")) %>%
   # Đếm số lượng
   count(Reason_Type) %>%
   # Tính % cho biểu đồ tròn, lấy số lượng (n) chia cho tổng số (sum(n))
   mutate(percentage = n / sum(n),label_text = scales::percent(percentage, accuracy = 0.1))
# In kết quả
kable(df_bmi_high_summary, caption = "Tỷ lệ Medical (Chỉ trong Nhóm BMI cao)",
      col.names = c("Loại Nguyên nhân", "Số lượng", "Tỷ lệ %", "Nhãn %"),
      digits = c(0, 0, 2, 0), align = "lrrr", booktabs = TRUE)
Tỷ lệ Medical (Chỉ trong Nhóm BMI cao)
Loại Nguyên nhân Số lượng Tỷ lệ % Nhãn %
Các nguyên nhân khác 32766 0.45 45.1%
Medical 39962 0.55 54.9%

TRỰC QUAN HÓA

# Khởi tạo ggplot, thiết lập trục
ggplot(df_bmi_high_summary, aes(x = "", y = percentage, fill = Reason_Type)) +
  # Vẽ cột
  geom_col(width = 1, color = "white") +
  # Biến thành biểu đồ tròn
  coord_polar(theta = "y", start = 0) +
  # Thêm nhãn %
  geom_text( aes(label = label_text), position = position_stack(vjust = 0.5),
             color = "black", size = 5 ) +
  # Tinh chỉnh màu
  scale_fill_manual(values = c("Medical" = "pink", "Các nguyên nhân khác" = "khaki1")) +
  # Đặt tiêu đề
  labs(title = "Tỷ lệ Yêu cầu 'Medical' (Trong nhóm BMI cao)",
       fill = "Loại Nguyên nhân") +
  # Xóa nền và lưới
 theme(axis.title = element_blank(), axis.text = element_blank(),
      axis.ticks = element_blank(), panel.grid = element_blank())

GIỚI THIỆU VỀ BỘ DỮ LIỆU BÁO CÁO TÀI CHÍNH ABS

Nguồn dữ liệu sơ cấp được sử dụng cho nghiên cứu này là bộ dữ liệu bctcabs.xlsx, được thu thập và tổng hợp từ các báo cáo tài chính công bố công khai của Tổng Công ty Cổ phần Bia - Rượu - Nước giải khát Sài Gòn (SABECO). Dữ liệu bao quát giai đoạn hoạt động 10 năm, từ 2015 đến 2024, cho phép thực hiện phân tích xu hướng (trend analysis) và đánh giá hiệu quả hoạt động một cách xuyên suốt.

Bộ dữ liệu bao gồm các chỉ tiêu tài chính then chốt, phản ánh tình hình tài sản và nguồn vốn (như tổng tài sản, nợ phải trả, vốn chủ sở hữu) cùng các chỉ số kết quả kinh doanh cốt lõi (như doanh thu, lợi nhuận sau thuế). Ngoài ra, dữ liệu thô cũng được sử dụng để tính toán các tỷ suất tài chính quan trọng nhằm đánh giá hiệu quả hoạt động và khả năng sinh lời của doanh nghiệp.

Mục tiêu của việc phân tích bộ dữ liệu này là thực hiện một cuộc đánh giá tài chính và hiệu suất kinh doanh của SABECO qua từng thời kỳ. Nghiên cứu tập trung vào việc nhận diện các xu hướng biến động, đánh giá năng lực quản lý tài chính, và hiệu quả sử dụng vốn. Hơn nữa, phân tích này còn nhằm xác định các yếu tố nội tại ảnh hưởng đến khả năng sinh lời, đặt trong bối cảnh cạnh tranh gay gắt của ngành đồ uống tại Việt Nam. Về mặt thực tiễn, kết quả nghiên cứu cung cấp luận cứ khoa học cho việc đề xuất các giải pháp chiến lược nhằm nâng cao hiệu quả hoạt động và năng lực cạnh tranh của SABECO trong tương lai.

PHẦN 1: THÔNG TIN CƠ BẢN VỀ BỘ DỮ LIỆU

Đọc dữ liệu

library(readxl)
# Xác định đường dẫn đến tệp dữ liệu
bc_raw<- read_excel("D:/thầy Tường/bctcabs.xlsx")

Đây là bước “nạp” dữ liệu thô vào môi trường R để chuẩn bị phân tích.

Xác định số hàng

# Tính số hàng của dữ liệu
n_rows_bc <- nrow(bc_raw)
# In kết quả
knitr::kable(data.frame("Chỉ tiêu" = "Số hàng của dữ liệu", "Giá trị" = n_rows_bc),
             caption = "Thông tin kích thước dữ liệu",align = "lr", booktabs = TRUE)
Thông tin kích thước dữ liệu
Chỉ.tiêu Giá.trị
Số hàng của dữ liệu 10

Số hàng là:10

Xác định số cột

# Số cột
n_cols_bc <- ncol(bc_raw) 
# In kết quả
knitr::kable( data.frame("Chỉ tiêu" = "Số hàng của dữ liệu","Giá trị" = n_cols_bc),
             caption = "Thông tin kích thước dữ liệu",align = "lr", booktabs = TRUE)
Thông tin kích thước dữ liệu
Chỉ.tiêu Giá.trị
Số hàng của dữ liệu 11

Số cột là: 11

Giải thích ý nghĩa các biến

# 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, col.names = c("Tên biến", "Tên tiếng anh","Mô tả"),
      caption = "Giải thích các biến trong dữ liệu")
Giải thích các biến trong dữ liệu
Tên biến Tên tiếng anh Mô tả
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ư.

Kiểm tra kiểu dữ liệu mỗi cột

#
library(knitr)
 Kieu_du_lieu_bctc <-data.frame( Ten_Bien = names(bc_raw), Kieu_du_lieu = sapply(bc_raw, class))
 #
 kable(Kieu_du_lieu_bctc, col.names = c("Tên biến", "Kiểu dữ liệu"),
       caption = "Thống kê kiểu dữ liệu các biến",row.names = FALSE)
Thống kê kiểu dữ liệu các biến
Tên biến Kiểu dữ liệu
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
và cung cấp dịch vụ numeric
Lợi nhuận gộp về bán hàng
và cung cấp dịch vụ numeric
Doanh thu hoạt động tài chính numeric

Kết luận: Tất cả đều thuộc kiểu dữ liệu numeric. Đây là điều kiện tiên quyết cho mọi phân tích định lượng. Các mô hình thống kê và các phép toán (tính trung bình, phương sai) chỉ hoạt động trên các biến số (numeric). Nếu một cột chỉ tiêu bị R đọc nhầm là character, nó sẽ bị loại bỏ khỏi mọi phân tích toán học.

Kiểm tra NA tổng thể

# Lập bảng dữ liệu bị thiếu
 Du_Lieu_Bi_Thieu_bc<-data.frame(Ten_Bien = names(bc_raw), N_A=colSums(is.na(bc_raw)))
 # In bảng giữ liệu.
 kable(Du_Lieu_Bi_Thieu_bc, col.names = c("Tên biến", "Dữ liệu bị thiếu"),
       caption = "Thống kê dữ liệu bị thiếu",row.names = FALSE)
Thống kê dữ liệu bị thiếu
Tên biến Dữ liệu bị thiếu
Năm/Biến 0
Tài sản ngắn hạn 0
Tiền và các khoản tương đương tiền 0
Các khoản đầu tư tài chính ngắn hạn 0
Tài sản dài hạn 0
Tổng tài sản 0
Tổng nợ phải trả 0
Vốn chủ sở hữu 0
Doanh thu thuần về bán hàng
và cung cấp dịch vụ 0
Lợi nhuận gộp về bán hàng
và cung cấp dịch vụ 0
Doanh thu hoạt động tài chính 0

Kết quả: Không có biến nào vi phạm.

Kiểm tra giá trị trùng lặp

# Đếm số hàng trùng lặp
dup_rows_bc <- sum(duplicated(bc_raw))
# Tắt ký hiệu khoa học mặc định của R (để đảm bảo số lớn hiển thị rõ ràng)
options(scipen = 999) 
# Tạo data frame tạm thời để in ra bảng kable
df_trung_lap <- data.frame( Thong_Tin = "Số hàng trùng lặp",
  # Định dạng số bằng scales::comma để hiển thị rõ ràng (ví dụ: 1,500)
  Gia_Tri = scales::comma(dup_rows_bc))
# In ra bảng kable
kable(df_trung_lap,caption = "Kiểm tra dữ liệu trùng lặp",col.names = c("Thông tin", "Giá trị"),
      align = "lr", booktabs = TRUE,row.names = FALSE)
Kiểm tra dữ liệu trùng lặp
Thông tin Giá trị
Số hàng trùng lặp 0

kết quả: Không có giá trị nào trùng lặp

Lấy mô tả sơ lược bằng summary

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.

Không mô tả biến năm vì nó không có ý nghĩa thống kê.

Mô tả sơ lược biến tài sản ngắn hạn

# Tính toán summary
sum_data <- summary(bc_raw$`Tài sản ngắn hạn`)
# Chuyển sang data.frame
data.frame( "Chỉ tiêu" = names(sum_data),
            "Giá trị" = format(as.vector(sum_data), big.mark = ".", trim = TRUE)) %>%
# In kết quả
  kable( caption = "Thống kê mô tả tài sản ngắn hạn",
         col.names = c("Chỉ tiêu thống kê", "Giá trị"),
         align = "lr",booktabs = TRUE, row.names = FALSE) 
Thống kê mô tả tài sản ngắn hạn
Chỉ tiêu thống kê Giá trị
Min. 10.714.345.997.642
1st Qu. 13.937.287.658.793
Median 19.338.991.982.037
Mean 19.132.051.618.009
3rd Qu. 24.519.307.933.646
Max. 26.860.224.573.663

Nhận xét: Với giai đoạn 2015–2024, bảng thống kê cho thấy tài sản ngắn hạn của doanh nghiệp tăng mạnh và ổn định dần qua các năm. Giá trị trung bình (~19.132 nghìn tỷ) và trung vị (~19.339 nghìn tỷ) gần tương đương, cho thấy phân bố khá cân đối, không bị lệch nhiều. So với mức tối thiểu năm đầu (10.714 nghìn tỷ) và tối đa năm gần nhất (26.860 nghìn tỷ).

Có thể thấy doanh nghiệp đã mở rộng quy mô hoạt động, cải thiện khả năng thanh khoản và quản lý vốn lưu động hiệu quả hơn trong 10 năm qua.

Mô tả sơ lược biến tiền và các khoản tương đương tiền

# Tính toán summary
sum_data <- summary(bc_raw$`Tiền và các khoản tương đương tiền`)

#Chuyển sang data.frame
data.frame( "Chỉ tiêu" = names(sum_data),
            "Giá trị" = format(as.vector(sum_data), big.mark = ".", trim = TRUE)) %>%
  
# In kết quả
  kable( caption = "Thống kê mô tả tiền và các khoản tương đương tiền",
         col.names = c("Chỉ tiêu thống kê", "Giá trị"),
         align = "lr", booktabs = TRUE, row.names = FALSE, digits = 2)
Thống kê mô tả tiền và các khoản tương đương tiền
Chỉ tiêu thống kê Giá trị
Min. 2.636.774.241.245
1st Qu. 3.485.232.684.180
Median 4.092.674.403.640
Mean 4.252.031.557.076
3rd Qu. 4.474.974.146.779
Max. 7.935.974.303.078

Nhận xét: Tiền và các khoản tương đương tiền của doanh nghiệp có xu hướng tăng ổn định, thể hiện qua giá trị trung bình hơn 4,25 nghìn tỷ đồng và mức tối đa đạt gần 7,94 nghìn tỷ đồng.

Điều này cho thấy doanh nghiệp duy trì khả năng thanh khoản tốt, đồng thời phản ánh hiệu quả trong việc quản lý dòng tiền và dự trữ tài chính, giúp đảm bảo hoạt động sản xuất kinh doanh được duy trì liên tục và an toàn trước biến động thị trường.

Mô tả sơ lược biến các khoản đầu tư tài chính ngắn hạn

# Tính toán summary
sum_data <- summary(bc_raw$`Các khoản đầu tư tài chính ngắn hạn`)

# Chuyển sang data.frame và in kable
data.frame( "Chỉ tiêu" = names(sum_data),
            "Giá trị" = format(as.vector(sum_data), big.mark = ".", trim = TRUE)) %>%
  
# In kết quả
  kable( caption = "Thống kê mô tả các khoản đầu tư tài chính ngắn hạn",
         col.names = c("Chỉ tiêu thống kê", "Giá trị"),
         align = "lr",  booktabs = TRUE, row.names = FALSE,digits = 2)  
Thống kê mô tả các khoản đầu tư tài chính ngắn hạn
Chỉ tiêu thống kê Giá trị
Min. 942.766.940.162
1st Qu. 6.805.148.028.643
Median 13.470.323.091.575
Mean 11.587.072.404.433
3rd Qu. 16.884.989.044.275
Max. 19.411.469.500.000

Nhận xét: Các khoản đầu tư tài chính ngắn hạn tăng mạnh, với giá trị trung bình hơn 11,5 nghìn tỷ đồng và mức cao nhất gần 19,4 nghìn tỷ đồng.

Sự gia tăng này cho thấy doanh nghiệp có chiến lược tối ưu hóa nguồn vốn nhàn rỗi, tập trung vào đầu tư ngắn hạn sinh lời, đồng thời phản ánh năng lực tài chính vững vàng và khả năng quản trị vốn linh hoạt trước biến động của thị trường.

Mô tả sơ lược biến tài sản dài hạn

# Tính toán summary
sum_data <- summary(bc_raw$`Tài sản dài hạn`)
# Chuyển sang data.frame 
data.frame( "Chỉ tiêu" = names(sum_data),
            "Giá trị" = format(as.vector(sum_data), big.mark = ".", trim = TRUE)) %>%
# In kết quả
  kable( caption = "Thống kê mô tả tài sản dài hạn",
         col.names = c("Chỉ tiêu thống kê", "Giá trị"),
         align = "lr",booktabs = TRUE,row.names = FALSE, digits = 2)
Thống kê mô tả tài sản dài hạn
Chỉ tiêu thống kê Giá trị
Min. 7.503.219.247.991
1st Qu. 7.626.636.934.369
Median 7.829.732.452.665
Mean 8.061.028.665.165
3rd Qu. 8.361.345.578.724
Max. 9.377.631.443.590

Nhận xét: Trong giai đoạn 2015–2024, giá trị tài sản dài hạn của doanh nghiệp tăng nhẹ nhưng ổn định, dao động từ 7,5 đến 9,4 nghìn tỷ đồng. Sự chênh lệch không quá lớn giữa các mốc thống kê cho thấy doanh nghiệp duy trì đầu tư dài hạn ở mức hợp lý, đảm bảo năng lực sản xuất và cơ sở vật chất ổn định trong dài hạn.

Mô tả sơ lược biến tổng tài sản

# Tính toán summary
sum_data <- summary(bc_raw$`Tổng tài sản`)
# Chuyển sang data.frame 
data.frame( "Chỉ tiêu" = names(sum_data),
            "Giá trị" = format(as.vector(sum_data), big.mark = ".", trim = TRUE)) %>%
# In kết quả
  kable( caption = "Thống kê mô tả tổng tài sản",
         col.names = c("Chỉ tiêu thống kê", "Giá trị"),
         align = "lr", booktabs = TRUE,row.names = FALSE, digits = 2) 
Thống kê mô tả tổng tài sản
Chỉ tiêu thống kê Giá trị
Min. 19.192.865.179.330
1st Qu. 22.101.952.530.486
Median 27.168.724.434.702
Mean 27.193.080.283.174
3rd Qu. 32.701.310.866.595
Max. 34.465.075.615.756

Kết quả: Giai đoạn 2015–2024 cho thấy tổng tài sản của doanh nghiệp duy trì xu hướng tăng ổn định, dao động từ 19,2 nghìn tỷ đến 34,5 nghìn tỷ đồng. Việc giá trị trung bình và trung vị xấp xỉ nhau cho thấy sự phát triển đồng đều, không chịu tác động lớn từ các biến động bất thường, phản ánh nền tảng tài chính vững và khả năng mở rộng hoạt động hiệu quả.

Mô tả sơ lược biến tổng nợ phải trả

# Tính toán summary
sum_data <- summary(bc_raw$`Tổng nợ phải trả`)
# Chuyển sang data.frame 
data.frame("Chỉ tiêu" = names(sum_data),
           "Giá trị" = format(as.vector(sum_data), big.mark = ".", trim = TRUE)) %>%
# In kết quả
  kable( caption = "Thống kê mô tả tổng nợ phải trả",
         col.names = c("Chỉ tiêu thống kê", "Giá trị"),
         align = "lr", booktabs = TRUE, row.names = FALSE,digits = 2)
Thống kê mô tả tổng nợ phải trả
Chỉ tiêu thống kê Giá trị
Min. 6.159.696.384.430
1st Qu. 6.791.321.090.971
Median 7.550.192.321.990
Mean 7.650.108.089.131
3rd Qu. 8.401.659.875.945
Max. 9.874.229.696.363

Nhận xét: Tổng nợ phải trả của doanh nghiệp tăng dần qua các năm, dao động từ 6,16 nghìn tỷ đến gần 9,87 nghìn tỷ đồng. Giá trị trung bình cao hơn trung vị cho thấy nợ có xu hướng tăng mạnh ở những năm gần đây, phản ánh sự mở rộng quy mô hoạt động đi kèm với nhu cầu vốn lớn hơn, nhưng vẫn trong ngưỡng kiểm soát hợp lý.

Mô tả sơ lược biến tổng vốn chủ sở hữu

# Tính toán summary
sum_data <- summary(bc_raw$`Vốn chủ sở hữu`)
# Chuyển sang data.frame 
data.frame("Chỉ tiêu" = names(sum_data),
           "Giá trị" = format(as.vector(sum_data), big.mark = ".", trim = TRUE)) %>%
 # In kết quả
   kable( caption = "Thống kê mô tả vốn chủ sở hữu",
         col.names = c("Chỉ tiêu thống kê", "Giá trị"),
         align = "lr", booktabs = TRUE,  row.names = FALSE,digits = 2 ) 
Thống kê mô tả vốn chủ sở hữu
Chỉ tiêu thống kê Giá trị
Min. 12.433.180.070.596
1st Qu. 14.843.343.168.253
Median 20.645.761.723.646
Mean 19.542.964.714.043
3rd Qu. 23.976.516.312.794
Max. 25.485.157.894.867

Nhận xét: vốn chủ sở hữu của doanh nghiệp tăng trưởng ổn định, dao động từ 12,43 nghìn tỷ đến 25,49 nghìn tỷ đồng. Mức trung vị cao hơn nhiều so với ngưỡng thấp nhất, cho thấy doanh nghiệp liên tục tích lũy lợi nhuận và mở rộng quy mô vốn. Điều này phản ánh năng lực tài chính vững mạnh và khả năng tự chủ cao trong dài hạn.

Mô tả sơ lược biến doanh thu thuần về bán hàng và cung cấp dịch vụ

# Tính toán summary
sum_data <- summary(bc_raw$`Doanh thu thuần về bán hàng\r\nvà cung cấp dịch vụ`)
# Chuyển sang data.frame
data.frame("Chỉ tiêu" = names(sum_data),
           "Giá trị" = format(as.vector(sum_data), big.mark = ".", trim = TRUE)) %>%
# In kết quả
  kable( caption = "Thống kê mô tả 'doanh thu thuần'",
         col.names = c("Chỉ tiêu thống kê", "Giá trị"),
         align = "lr",  booktabs = TRUE, row.names = FALSE,digits = 2)
Thống kê mô tả ‘doanh thu thuần’
Chỉ tiêu thống kê Giá trị
Min. 26.373.746.293.858
1st Qu. 28.586.334.698.312
Median 31.220.547.627.216
Mean 31.764.670.136.053
3rd Qu. 34.843.855.757.524
Max. 37.899.059.501.295

Nhận xét: doanh thu thuần duy trì xu hướng tăng ổn định, dao động từ 26,37 nghìn tỷ đến 37,89 nghìn tỷ đồng. Giá trị trung bình cao hơn trung vị cho thấy doanh nghiệp có những năm tăng trưởng vượt trội, phản ánh hiệu quả hoạt động kinh doanh và khả năng mở rộng thị trường tích cực.

Mô tả sơ lược biến lợi nhuận gộp về bán hàng và cung cấp dịch vụ

# Tính toán summary
sum_data <- summary(bc_raw$`Lợi nhuận gộp về bán hàng\r\nvà cung cấp dịch vụ`)
# Chuyển sang data.frame 
data.frame( "Chỉ tiêu" = names(sum_data),
            "Giá trị" = format(as.vector(sum_data), big.mark = ".", trim = TRUE)) %>%
# In kết quả
  kable(caption = "Thống kê mô tả 'lợi nhuận gộp'",
        col.names = c("Chỉ tiêu thống kê", "Giá trị"),
        align = "lr", booktabs = TRUE, row.names = FALSE,digits = 2)
Thống kê mô tả ‘lợi nhuận gộp’
Chỉ tiêu thống kê Giá trị
Min. 7.557.743.622.179
1st Qu. 8.129.988.160.736
Median 8.683.306.424.513
Mean 8.761.549.027.245
3rd Qu. 9.261.488.052.258
Max. 10.770.706.927.084

Nhận xét: Trong giai đoạn 2015–2024, lợi nhuận gộp tăng trưởng đều qua các năm, dao động từ 7,56 nghìn tỷ đến 10,77 nghìn tỷ đồng. Giá trị trung bình và trung vị khá gần nhau, cho thấy doanh nghiệp duy trì biên lợi nhuận ổn định, phản ánh khả năng kiểm soát chi phí và hiệu quả sản xuất – kinh doanh bền vững.

Mô tả sơ lược biến doanh thu hoạt động tài chính

# Tính toán summary
sum_data <- summary(bc_raw$`Doanh thu hoạt động tài chính`)
# Chuyển sang data.frame 
data.frame( "Chỉ tiêu" = names(sum_data),
            "Giá trị" = format(as.vector(sum_data),big.mark = ".", trim = TRUE)) %>%
# In kết quả
  kable(caption = "Thống kê mô tả 'doanh thu hoạt động tài chính'",
        col.names = c("Chỉ tiêu thống kê", "Giá trị"),
        align = "lr",booktabs = TRUE, row.names = FALSE,digits = 2) 
Thống kê mô tả ‘doanh thu hoạt động tài chính’
Chỉ tiêu thống kê Giá trị
Min. 418.646.983.429
1st Qu. 660.523.055.065
Median 932.127.070.083
Mean 888.020.478.391
3rd Qu. 1.084.690.114.858
Max. 1.432.568.520.354

Nhận xét: Trong giai đoạn 2015–2024, doanh thu hoạt động tài chính có xu hướng tăng, dao động từ 418,6 tỷ đến hơn 1.432 tỷ đồng. Mức trung bình gần 888 tỷ đồng cho thấy doanh nghiệp duy trì hiệu quả đầu tư tài chính ổn định, góp phần tích cực vào tổng lợi nhuận và khả năng sinh lời ngoài hoạt động kinh doanh chính.

PHẦN 2: XỬ LÝ DỮ LIỆU THÔ VÀ MÃ HÓA DỮ LIỆU

Đọc lại dữ liệu gốc

# đọc lại và xem dữ liệu gốc
bc <- read_excel("D:/thầy Tường/bctcabs.xlsx", sheet = 1)  
# Lấy tên cột từ dataframe 'bc'
column_names <- names(bc)
#  Tạo dataframe tạm thời cho Kable
data.frame( STT = 1:length(column_names), Ten_Bien = column_names) %>%
  # Định dạng và in ra bảng Kable
  kable( caption = "Phân loại và danh sách các biến Số trong dữ liệu",
         col.names = c("STT", "Tên Biến (Column Name)"),
         align = "ll",  booktabs = TRUE,  row.names = FALSE )%>%
  # Căn giữa, giữ đúng vị trí và tự động co lại
kable_styling( position = "center",latex_options = c("hold_position", "scale_down"))
Phân loại và danh sách các biến Số trong dữ liệu
STT Tên Biến (Column Name)
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 và cung cấp dịch v
10 Lợi nhuận gộp về bán hàng và cung cấp dịch vụ
11 Doanh thu hoạt động tài chính

Chuẩn hóa tên cột (xóa ký tự đặc biệt, thay khoảng trắng bằng “_“)

# Gọi thư viện
library(janitor)
# Xóa ký tự đặc biệt, thay khoảng trắng bằng "_
bc <- clean_names(bc_raw)  
# Lấy tên cột từ dataframe 'bc'
column_names <- names(bc)
# Tạo dataframe tạm thời cho Kable
data.frame( STT = 1:length(column_names),Ten_Bien_Sau_Clean = column_names) %>%
  # Định dạng và in ra bảng
  kable(caption = "Danh sách tên cột đã được chuẩn hóa",
        col.names = c("STT", "Tên Biến (Đã Làm Sạch)"), 
        align = "ll", booktabs = TRUE,row.names = FALSE )
Danh sách tên cột đã được chuẩn hóa
STT Tên Biến (Đã Làm Sạch)
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

Ý nghĩa: Việc chuẩn hóa tên cột giúp đảm bảo tính nhất quán và tránh lỗi cú pháp khi xử lý dữ liệu trong R. Các tên biến được chuyển về dạng không dấu, viết thường và có gạch dưới, giúp dễ truy cập và thao tác. Quá trình này được thực hiện bằng hàm clean_names() thuộc gói janitor, nhằm tăng tính chính xác và tự động hóa trong phân tích.

Đổi tên cột đầu tiên cho ngắn gọn hơn

names(bc)[1] <- "nam"    

Ý nghĩa: Giúp dễ dàng hơn trong việc viết và phân tích dữ liệu.

Chuyển biến “Nam” sang dạng số nguyên

bc$nam <- as.integer(bc$nam)  

Đây là bước đảm bảo kiểu dữ liệu (data type integrity). Biến “Năm” về bản chất là một biến thứ tự. Việc chuyển về kiểu integer đảm bảo R hiểu đúng bản chất của nó, giúp biến này hoạt động chính xác trong các mô hình chuỗi thời gian, các phép lọc (ví dụ: filter(Nam > 2018)), hoặc khi dùng làm trục (axis) trong biểu đồ và tối ưu hóa bộ nhớ so với kiểu numeric.

Mã hóa dữ liệu quy mô theo phân loại

# Tạo cột mới quy mô.
bc <- bc %>%
  mutate(quy_mo = case_when( tong_tai_san < 1e9 ~ "Nhỏ", tong_tai_san < 5e9 ~ "Vừa", 
                             TRUE ~ "Lớn"  ))  
bc %>%
  # Đếm tần suất và tỷ lệ phần trăm của từng nhóm Quy mô
  count(quy_mo) %>%
  mutate(Ti_Le = n / sum(n),
         Ti_Le_Phan_Tram = scales::percent(Ti_Le, decimal.mark = ",",accuracy = 0.1)) %>%
  # Sắp xếp lại cột 
  select(quy_mo, n, Ti_Le_Phan_Tram) %>%
  # Im kable
  kable( caption = "Tần suất phân loại quy mô công ty",
         col.names = c("Quy mô", "Tần suất (Số lượng)", "Tỷ lệ (%)"),
         align = "lcr", booktabs = TRUE, row.names = FALSE)
Tần suất phân loại quy mô công ty
Quy mô Tần suất (Số lượng) Tỷ lệ (%)
Lớn 10 100,0%

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 Kết quả: Bảng số liệu cho thấy quy mô của của các biến đều thuộc loại lớn

Chuẩn hóa kiểu dữ liệu (đảm bảo số liệu là numeric)

# Định nghĩa vector chứa tên các cột cần chuyển đổi
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","quy_mo")
# Áp dụng hàm as.numeric cho tất cả các cột đã chọn
bc[cols_to_num] <- lapply(bc[cols_to_num], as.numeric)  
# Tạo dataframe chứa Tên cột và Kiểu dữ liệu
df_structure <- data.frame( Ten_Bien = names(bc), Kieu_Du_Lieu = sapply(bc, class))
# In ra bảng
kable(df_structure, caption = "Tóm tắt cấu trúc dữ liệu)",
      col.names = c("Tên Biến", "Kiểu Dữ Liệu"),
      align = "lr", booktabs = TRUE,  row.names = FALSE)
Tóm tắt cấu trúc dữ liệu)
Tên Biến Kiểu Dữ Liệu
nam integer
tai_san_ngan_han numeric
tien_va_cac_khoan_tuong_duong_tien numeric
cac_khoan_dau_tu_tai_chinh_ngan_han numeric
tai_san_dai_han numeric
tong_tai_san numeric
tong_no_phai_tra numeric
von_chu_so_huu numeric
doanh_thu_thuan_ve_ban_hang_va_cung_cap_dich_vu numeric
loi_nhuan_gop_ve_ban_hang_va_cung_cap_dich_vu numeric
doanh_thu_hoat_dong_tai_chinh numeric
quy_mo numeric

Các biến định lượng trong bộ dữ liệu được chuyển đổi sang kiểu số (numeric) nhằm đảm bảo tính chính xác cho các phép tính và mô hình phân tích. Cụ thể, hàm as.numeric() được áp dụng đồng loạt cho các cột liên quan, giúp chuẩn hóa định dạng dữ liệu. Sau đó, cấu trúc dữ liệu được tổng hợp bằng bảng kable() để kiểm tra kiểu biến và xác nhận tính đồng nhất của tập dữ liệu.

PHẦN 3: THỐNG KÊ CƠ BẢN VÀ TRỰC QUAN HÓA

Tính hệ số biến thiên (biến động tương đối)

# Gán biến
sd_tts <- sd(bc$tong_tai_san, na.rm = TRUE)
mean_tts <- mean(bc$tong_tai_san, na.rm = TRUE)
# Tính hệ số biến thiên
cv_tts <- sd_tts / mean_tts
# TẠO VÀ IN BẢNG KABLE
data.frame(Chỉ_số = "Hệ số Biến thiên (CV)",
  # Định dạng CV dưới dạng phần trăm (accuracy = 0.1%):
  Giá_trị = scales::percent(cv_tts,decimal.mark = ",", accuracy = 0.01)) %>%
# In kết quả
  kable(caption = "Hệ số biến thiên của tổng tài sản",col.names = c("Chỉ số", "Giá trị"),
    align = "lr",booktabs = TRUE, row.names = FALSE)
Hệ số biến thiên của tổng tài sản
Chỉ số Giá trị
Hệ số Biến thiên (CV) 21,08%

Nhận Xét: Biến hệ số biến thiên (CV) được tạo ra để đo mức độ biến động tương đối của Tổng tài sản qua các năm.

CV = 21.1% cho thấy Tổng tài sản biến động ở mức trung bình, tức là giá trị tài sản có thay đổi theo thời gian nhưng vẫn ổn định tương đối, phản ánh quy mô doanh nghiệp phát triển đều đặn, không có biến động quá lớn giữa các năm.

Biểu đồ 1: Đường xu hướng Doanh thu theo năm

# khởi tạo biểu đồ và gán trục X, trục Y
ggplot(bc, aes(x = nam, y = tong_tai_san/1e9)) + 
  # Vẽ đường nối các năm, thể hiện xu hướng thay đổi tổng tài sản
  geom_line(linewidth = 1.2, color =  "pink2") +
  # Vẽ đường nối các năm, thể hiện xu hướng thay đổi tổng tài sản
  geom_point(size = 2, color =  "darkorchid2"      ) +
  # Ghi giá trị cụ thể của từng điểm lên biểu đồ
  geom_text(aes(label = number(tong_tai_san / 1e9, big.mark = ".")),
            vjust = -0.5, size = 3.1, color = "black") +
  # Định dạng trục Y để CÓ DẤU CHẤM
  scale_y_continuous(labels = number_format(big.mark = ".")) +
  #Đặt tiêu đề và tên trục X, Y
  labs(title = "XU HƯỚNG TỔNG TÀI SẢN (2015–2024)", x = "Năm",
       y = "Tổng tài sản (nghìn tỷ đồng)") +
  # Chỉnh hiển thị năm và định dạng số có dấu phẩy.
  scale_x_continuous(breaks = 2015:2024) +
  #Dùng giao diện tối giản, dễ nhìn
  theme_minimal(base_family = "Times",base_size = 13)


Nhận xét: Biểu đồ thể hiện xu hướng Tổng tài sản giai đoạn 2015–2024 cho thấy:

Giai đoạn 2015–2018: Tổng tài sản tăng chậm, chỉ dao động quanh mức 21–22 nghìn tỷ đồng.

Từ 2019 đến 2022: Tài sản tăng mạnh và liên tục, đặc biệt năm 2019 tăng vọt từ khoảng 22 nghìn tỷ lên gần 27 nghìn tỷ, sau đó tiếp tục đạt 34,465 tỷ đồng vào năm 2022 — mức cao nhất toàn kỳ.

Hai năm cuối (2023–2024): Có dấu hiệu giảm nhẹ, từ 34,056 tỷ xuống 33,439 tỷ đồng, cho thấy tốc độ mở rộng tài sản chững lại.

Kết luận: Giai đoạn 2015–2022 là thời kỳ tăng trưởng mạnh về quy mô tài sản, thể hiện khả năng mở rộng nguồn lực và hoạt động đầu tư. Tuy nhiên, sau 2022, xu hướng giảm nhẹ cho thấy doanh nghiệp cần chú trọng hiệu quả sử dụng tài sản và duy trì ổn định quy mô tài chính.

Tính lợi nhuận trung bình theo năm

Tính lợi nhuận trung bình

# Tạo một dataframe mới có tên df_loi_nhuan_tb
df_loi_nhuan_tb <- bc %>%
  # Nhóm dữ liệu theo cột 'Nam'
  group_by(nam) %>%
  # Tính Lợi nhuận gộp trung bình (mean) cho mỗi năm
  summarise(loi_nhuan_tb = mean(
    loi_nhuan_gop_ve_ban_hang_va_cung_cap_dich_vu, na.rm = TRUE)) %>%
  # Sắp xếp lại theo năm
  arrange(nam)
df_loi_nhuan_tb_formatted <- df_loi_nhuan_tb %>%
  mutate( loi_nhuan_tb = scales::number(loi_nhuan_tb,big.mark = ".", accuracy = 1) )
# In kết quả
kable( df_loi_nhuan_tb_formatted, caption = "Lợi nhuận gộp trung bình qua các năm",
  col.names = c("Năm", "Lợi nhuận gộp trung bình"),align = "lr", booktabs = TRUE,
  row.names = FALSE)
Lợi nhuận gộp trung bình qua các năm
Năm Lợi nhuận gộp trung bình
2015 7.557.743.622.179
2016 8.267.535.125.754
2017 8.865.518.064.577
2018 8.084.139.172.396
2019 9.550.628.692.014
2020 8.501.094.784.449
2021 7.608.564.811.493
2022 10.770.706.927.084
2023 9.091.362.504.235
2024 9.318.196.568.266

Biểu đồ 2: trực quan hóa lợi nhuận gộp trung bình

# 1. Vẽ biểu đồ đường cho Lợi nhuận gộp Trung bình
ggplot(df_loi_nhuan_tb, aes(x = nam, y = loi_nhuan_tb/1000000)) +
  # Vẽ đường và điểm
  geom_line(linewidth = 1.2, color = "lightpink") +
  geom_point(size = 3, color = "thistle") +
  # Thêm nhãn giá trị (đã định dạng)
  geom_text( aes(label = scales::number(loi_nhuan_tb/1000000, big.mark = ".", accuracy = 1)),    
             vjust = -0.7,size = 3.2) +
  # Đảm bảo tất cả các năm đều hiển thị
  scale_x_continuous(breaks = min(df_loi_nhuan_tb$nam):max(df_loi_nhuan_tb$nam)) +
  # Định dạng trục Y (thêm dấu chấm)
  scale_y_continuous(labels = number_format(big.mark = ".")) +
  # Thêm Tiêu đề
  labs( title = "Xu hướng Lợi nhuận gộp Trung bình qua các Năm",
    x = "Năm", y = "Lợi nhuận gộp Trung bình (triệu đồng)") +
  theme_minimal(base_family = "Times")

Tính lợi nhuận gộp bình quân

# Tính giá trị trung bình (loại bỏ NA)
mean_loinhuan <- mean(bc$loi_nhuan_gop_ve_ban_hang_va_cung_cap_dich_vu, na.rm = TRUE)
# Tạo cột mới
data.frame(Chỉ_số = "Trung bình lợi nhuận gộp",
  # Định dạng số có dấu phẩy, làm tròn đến số nguyên
  Giá_trị = scales::number(mean_loinhuan,big.mark = ".", accuracy = 1)) %>%
# In kết quả
  kable( caption = "Trung bình lợi nhuận gộp",  col.names = c("Chỉ số", "Giá trị"),
         align = "lr", booktabs = TRUE,row.names = FALSE)
Trung bình lợi nhuận gộp
Chỉ số Giá trị
Trung bình lợi nhuận gộp 8.761.549.027.245

Biểu đồ 3: Tương quan giữa Tổng tài sản và Tổng nợ

Tính toán

# Tính tương quan giữa Tổng tài sản và Tổng nợ
tuong_quan_tts_tn <- cor(bc$tong_tai_san, bc$tong_no_phai_tra, use = "complete.obs")
# Tạo cột mới
data.frame(Chỉ_số = "Tương quan (Tổng Tài Sản & Tổng Nợ)",
  # Định dạng số, làm tròn đến 3 chữ số thập phân
  Giá_trị = scales::number(tuong_quan_tts_tn,decimal.mark = ",", accuracy = 0.001)) %>%
# In kết quả
 kable(caption = "Hệ số tương quan giữa tổng tài sản và tổng nợ",
        col.names = c("Chỉ số", "Giá trị"), align = "lr", booktabs = TRUE, row.names = FALSE)
Hệ số tương quan giữa tổng tài sản và tổng nợ
Chỉ số Giá trị
Tương quan (Tổng Tài Sản & Tổng Nợ) 0,745

Trực quan hóa

# 1. Vẽ biểu đồ phân tán
# CHIA CHO 1.000.000.000 (Tỷ đồng)
ggplot(bc, aes(x = tong_tai_san / 1000000000, y = tong_no_phai_tra / 1000000000)) +
  
  # Vẽ các điểm dữ liệu
  geom_point(color = "skyblue", alpha = 0.8, size = 4) +
  # Thêm đường xu hướng
  geom_smooth(method = "lm", se = FALSE, color = "red", linetype = "dashed") +
  # Dùng geom_text_repel để tự động đẩy nhãn
  geom_text_repel(aes(label = nam), size = 3.5,color = "black",box.padding = 0.5,
    point.padding = 0.5,segment.color = 'grey50',segment.size = 0.2,max.overlaps = Inf) +
  # Cập nhật nhãn trục (scales)
  scale_x_continuous(name = "Tổng tài sản (Tỷ đồng)",labels = number_format(big.mark = "."))+
  scale_y_continuous(name = "Tổng nợ phải trả (Tỷ đồng)",labels = number_format(big.mark = "."))+
  # Thêm Tiêu đề và Phụ đề
  labs(title = "MỐI QUAN HỆ GIỮA TỔNG TÀI SẢN VÀ TỔNG NỢ",subtitle = paste("Hệ số tương quan:",                                                          scales::number(tuong_quan_tts_tn, accuracy = 0.001, decimal.mark = ",")))+
  # 
  theme_minimal(base_family = "Times")

Tạo biến mới: Tỷ lệ lợi nhuận gộp

Tạo biến

# Tạo cột mới tỷ lệ lợi nhuận gộp
bc <- bc %>%
  mutate(ty_le_loi_nhuan_gop = loi_nhuan_gop_ve_ban_hang_va_cung_cap_dich_vu /
           doanh_thu_thuan_ve_ban_hang_va_cung_cap_dich_vu)  
# Tạo một dataframe có hai cột: STT và Tên Biến Mới
data.frame(STT = 1:length(names(bc)), Ten_Bien_Moi = names(bc)) %>%
# In kết quả
kable(caption = "Danh sách tên cột đã chuẩn hóa",col.names = c("STT", "Tên Biến (Đã Làm Sạch)"),
      align = "ll", booktabs = TRUE, row.names = FALSE)
Danh sách tên cột đã chuẩn hóa
STT Tên Biến (Đã Làm Sạch)
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 quy_mo
13 ty_le_loi_nhuan_gop

Nhận xét: Trong phân tích tài chính, các giá trị tuyệt đối (như “Lợi nhuận gộp”) thường khó so sánh giữa các công ty có quy mô khác nhau (vấn đề về tính không đồng nhất). Bằng cách tạo ra một biến tỷ lệ, Biến tỷ lệ này có ý nghĩa kinh tế sâu sắc hơn, phản ánh hiệu quả hoạt động và cho phép so sánh công bằng giữa các doanh nghiệp.

Biểu đồ 4: Tỷ lệ lợi nhuận gộp

# khởi tạo biểu đồ và gán trục X, trục Y
ggplot(bc, aes(x = nam, y = ty_le_loi_nhuan_gop)) +
  # Vẽ đường nối các điểm dữ liệu, biểu thị xu hướng của tỷ lệ lợi nhuận gộp qua các năm
  geom_line(color = "cyan3", linewidth = 1.2) +
  # Thêm các điểm dữ liệu trên từng năm để dễ quan sát vị trí thực tế của từng giá trị
  geom_point(color = "brown1", size = 2.3) +
  # Thêm nhãn hiển thị giá trị phần trăm ngay trên các điểm dữ liệu.
  geom_text(aes(label = paste0(sub("\\.", ",", round(ty_le_loi_nhuan_gop, 1)), "%")),
            vjust = -0.6, size = 3) +
  # Đặt tiêu đề và nhãn trục rõ ràng.
  labs(title = "TỶ LỆ LỢI NHUẬN GỘP (%)", x = "Năm", y = "%") +
  # Hiển thị đầy đủ các mốc năm từ 2015 đến 2024 trên trục hoành.
  scale_x_continuous(breaks = 2015:2024) +
  # Áp dụng giao diện cổ điển (classic) với nền trắng và trục rõ ràng.
  theme_minimal(base_family = "Times")


Nhận xét: Giai đoạn 2015–2018: Tỷ lệ lợi nhuận gộp giảm nhẹ từ khoảng 0,28% xuống 0,23%, phản ánh chi phí sản xuất hoặc giá vốn tăng nhanh hơn doanh thu.

Từ 2019–2022: Biên lợi nhuận phục hồi và đạt đỉnh khoảng 0,31% năm 2022, cho thấy doanh nghiệp đã cải thiện hiệu quả hoạt động hoặc cơ cấu sản phẩm.

Năm 2023–2024: Tỷ lệ duy trì ổn định quanh mức 0,3%, thể hiện khả năng sinh lời ổn định dù thị trường có biến động.

Tổng thể: Tỷ suất lợi nhuận gộp có xu hướng biến động nhẹ nhưng vẫn duy trì ổn định, phản ánh doanh nghiệp giữ vững khả năng kiểm soát chi phí và duy trì biên lợi nhuận trong dài hạn.

Tạo thêm cột tỷ lệ nợ tài sản

Tạo cột

# TyLeNoTaiSan = Tổng Nợ Phải Trả / Tổng Tài Sản
bc <- bc %>% mutate(ty_le_no_tai_san = tong_no_phai_tra / tong_tai_san)
# Tạo một dataframe mới có tên df_kiem_tra_ty_le_no
df_kiem_tra_ty_le_no <- bc %>% 
  # Chọn 4 cột cần thiết để kiểm tra
  select(nam, tong_tai_san, tong_no_phai_tra, ty_le_no_tai_san )
# Lấy data frame
df_kiem_tra_ty_le_no %>%
  # Định dạng các cột số
  mutate(tong_no_phai_tra = scales::number(tong_no_phai_tra,big.mark = ".", accuracy = 1),
    tong_tai_san = scales::number(tong_tai_san,big.mark = ".", accuracy = 1),
    # Định dạng Tỷ lệ nợ thành %
    ty_le_no_tai_san= scales::percent(ty_le_no_tai_san, decimal.mark = ",", accuracy = 0.1)) %>%
# In kết quả
kable( caption = "Tỷ lệ nợ trên tổng tài sản",
       col.names = c("Năm", "Tổng tài sản", "Tổng nợ", "Tỷ lệ nợ"),
         align = "lrrr", booktabs = TRUE, row.names = FALSE)
Tỷ lệ nợ trên tổng tài sản
Năm Tổng tài sản Tổng nợ Tỷ lệ nợ
2015 21.571.925.688.349 7.507.221.969.018 34,8%
2016 19.192.865.179.330 6.759.685.108.734 35,2%
2017 22.013.689.109.910 7.593.162.674.962 34,5%
2018 22.366.742.792.213 6.254.837.224.044 28,0%
2019 26.962.476.094.045 6.886.229.037.681 25,5%
2020 27.374.972.775.358 6.159.696.384.430 22,5%
2021 30.487.024.372.425 7.892.238.669.264 25,9%
2022 34.465.075.615.756 9.874.229.696.363 28,6%
2023 34.056.624.839.705 8.571.466.944.838 25,2%
2024 33.439.406.364.651 9.002.313.181.979 26,9%

Biểu đồ 5: Tỷ lệ nợ trên tài sản

library(scales)
# khởi tạo biểu đồ và gán trục X, trục Y.
ggplot(bc, aes(x = nam, y = ty_le_no_tai_san )) +
  # Tạo lớp vùng tô màu hồng nhạt, biểu diễn diện tích dưới đường xu hướng.
  geom_area(fill = "pink", alpha = 0.5) +
  # Vẽ đường xu hướng màu đỏ tím để thể hiện sự thay đổi của tỷ lệ qua thời gian.
  geom_line(color = "maroon3", linewidth = 1.2) +
  # Thêm các điểm dữ liệu trên đường biểu diễn.
  geom_point(color = "lightsalmon4", size = 2.5) +
  # Hiển thị giá trị cụ thể của từng điểm ngay trên biểu đồ.
  geom_text(aes(label =  paste0(sub("\\.", ",",round(ty_le_no_tai_san ,2)))),vjust = 2) +
  # Đặt tiêu đề và tên trục.
  labs(title = "TỶ LỆ NỢ TRÊN TÀI SẢN (%))", x = "Năm", y = "Tỷ lệ (%)") +
  # Hiển thị đầy đủ các mốc năm từ 2015 đến 2024 trên trục hoành.
  scale_x_continuous(breaks = 2015:2024) +
  # Chuyển các giá trị trục tung sang định dạng phần trăm (%).
  scale_y_continuous(labels = percent_format(scale = 1)) +
  # Áp dụng giao diện tối giản, loại bỏ khung và đường nền thừa.
  theme_minimal(base_family = "Times")


Nhận xét: Giai đoạn 2015–2017: Tỷ lệ nợ duy trì ổn định ở mức cao (khoảng 34–35%), cho thấy doanh nghiệp phụ thuộc tương đối nhiều vào nguồn vốn vay.

Từ 2018–2020: Tỷ lệ nợ giảm mạnh xuống mức thấp nhất 0.23 năm 2020, phản ánh chính sách giảm đòn bẩy tài chính, tăng vốn chủ sở hữu hoặc trả bớt nợ.

Giai đoạn 2021–2024: Tỷ lệ nợ dao động nhẹ quanh mức 25–29%, thể hiện mức nợ ổn định, doanh nghiệp kiểm soát tốt rủi ro tài chính.

Nhận xét tổng quát: Cơ cấu tài chính ngày càng an toàn và bền vững hơn, với xu hướng giảm phụ thuộc vào nợ vay trong giai đoạn gần đây.

Tạo biến mới ROE và trực quan hóa

Tạo cột

# Thêm biến ROE
bc <- bc %>%
  mutate(ROE = loi_nhuan_gop_ve_ban_hang_va_cung_cap_dich_vu / von_chu_so_huu)
# Tạo bảng hiển thị 
df_roe <- bc %>%
  mutate(ROE_hien_thi = scales::percent(ROE, decimal.mark = ",", accuracy = 0.01)) %>%
  select(Năm = nam, `ROE` = ROE_hien_thi)
# In kết quả
kable( df_roe, caption = "Bảng ROE", align = "lr",booktabs = TRUE, row.names = FALSE)
Bảng ROE
Năm ROE
2015 53,74%
2016 66,50%
2017 61,48%
2018 50,17%
2019 47,57%
2020 40,07%
2021 33,67%
2022 43,80%
2023 35,67%
2024 38,13%

ROE trung bình

# Tính giá trị trung bình (loại bỏ NA)
mean_roe <- mean(bc$ROE, na.rm = TRUE)
# Tạo biến
data.frame( Chỉ_số = c( "Trung bình ROE"),
  # Định dạng cả hai giá trị thành % 
  Giá_trị = scales::percent(c(mean_roe),decimal.mark = ",", accuracy = 0.1)) %>%
  # In kết quả
  kable(caption = "Trung bình chỉ số sinh lời ROE",col.names = c("Chỉ số", "Giá trị Trung bình"),
        align = "lr", booktabs = TRUE, row.names = FALSE)
Trung bình chỉ số sinh lời ROE
Chỉ số Giá trị Trung bình
Trung bình ROE 47,1%

Biểu đồ 6: ROE theo năm

# khởi tạo biểu đồ và gán trục X, trục Y
ggplot(bc, aes(x = nam, y = ROE)) +
 geom_line(color = "darkcyan", linewidth = 1.2) +
 geom_point(color = "orange", size = 3) +
 geom_text(aes(label = paste0(sub("\\.", ",",round(ROE, 2)))),
           vjust = -0.6,hjust = -0.22, size = 3) +
  # Đặt tiêu đề và nhãn trục rõ ràng
  labs(title = "TỶ SUẤT SINH LỜI TRÊN VỐN CHỦ SỞ HỮU (%)", x = "Năm", y = "%") +
  # Hiển thị đầy đủ các mốc năm từ 2015 đến 2024 trên trục hoành
  scale_x_continuous(breaks = 2015:2024) +
  # Áp dụng giao diện nền sáng (theme_light)
  theme_light(base_family = "Times")


Nhận xét: Giai đoạn 2015–2017: ROE tăng mạnh, đạt đỉnh 66,5% năm 2016, phản ánh khả năng tạo lợi nhuận trên vốn chủ sở hữu rất cao.

Giai đoạn 2018–2021: ROE giảm liên tục xuống mức thấp nhất 33,67% năm 2021, cho thấy hiệu quả sử dụng vốn suy giảm, có thể do chi phí hoặc áp lực nợ tăng.

Giai đoạn 2022–2024: ROE phục hồi nhẹ, đạt khoảng 38% năm 2024, thể hiện doanh nghiệp đang dần cải thiện hiệu quả sinh lời.

Tổng quan: ROE biến động mạnh, song vẫn duy trì mức khá cao so với trung bình ngành, phản ánh khả năng sinh lời tốt nhưng chưa ổn định.

Biểu đồ 7: Ảnh hưởng của tỷ lệ nợ đến ROE

# khởi tạo biểu đồ và gán trục X.
ggplot(bc, aes(x = nam)) +
  # Vẽ biểu đồ cột (bar) biểu thị Tỷ lệ nợ trên tổng tài sản của SABECO
  # Nhân với 100 vì có thể dữ liệu ban đầu ở dạng tỷ lệ thập phân.
  geom_col(aes(y = ty_le_no_tai_san  * 100), fill = "lightblue", alpha = 0.6) +
  # Vẽ đường nối thể hiện ROE (%) qua các năm.
  geom_line(aes(y = ROE, color = "ROE"), linewidth = 1.2) +
  # Đánh dấu các điểm dữ liệu cụ thể giúp biểu đồ trực quan hơn.
  geom_point(aes(y = ROE, color = "ROE"), size = 3) +
  # Dùng để thêm trục tung phụ bên phải
  # Hiển thị Tỷ lệ nợ (%) với cùng thang giá trị như trục ROE ở bên trái.
  scale_y_continuous(sec.axis = sec_axis(~ ., name = "Tỷ lệ nợ (%)")) +
  # Đặt tiêu đề và nhãn trục rõ ràng.
  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 = "") +
  # Hiển thị đầy đủ các mốc năm từ 2015 đến 2024 trên trục hoành.
  scale_x_continuous(breaks = 2015:2024) +
  # Áp dụng giao diện nền sáng (theme_light).
  theme_light(base_family = "Times")


Nhận xét: Tỷ lệ nợ và ROE biến động cùng chiều trong hầu hết các năm, cho thấy đòn bẩy tài chính có tác động đáng kể đến khả năng sinh lời trên vốn chủ sở hữu. Giai đoạn 2015–2017, khi tỷ lệ nợ cao, ROE đạt mức đỉnh (trên 60%), phản ánh hiệu quả khuếch đại lợi nhuận nhờ vay nợ. Tuy nhiên, từ 2018 trở đi, ROE giảm dần khi doanh nghiệp giảm đòn bẩy tài chính, cho thấy việc sử dụng nợ hợp lý giúp kiểm soát rủi ro nhưng đồng thời làm giảm lợi nhuận kỳ vọng. Từ 2021–2024, ROE có dấu hiệu phục hồi nhẹ cùng mức nợ ổn định, phản ánh xu hướng tái cân bằng giữa hiệu quả sinh lời và an toàn tài chính.

Tạo biến mới ROA và trực quan hóa

Tạo cột

# Tính biến ROA
bc <- bc %>%
  mutate(ROA = loi_nhuan_gop_ve_ban_hang_va_cung_cap_dich_vu / tong_tai_san)
# Tạo bảng hiển thị 
df_roa <- bc %>%
  # Định dạng ROA 
  mutate(ROA_hien_thi = scales::percent(ROA, decimal.mark = ",", accuracy = 0.01)) %>%
  select(Năm = nam, `ROA` = ROA_hien_thi)
# In kết quả
kable( df_roa,caption = "Bảng ROA", align = "lr",booktabs = TRUE, row.names = FALSE)
Bảng ROA
Năm ROA
2015 35,04%
2016 43,08%
2017 40,27%
2018 36,14%
2019 35,42%
2020 31,05%
2021 24,96%
2022 31,25%
2023 26,69%
2024 27,87%

ROA trung bình

# Tính giá trị trung bình (loại bỏ NA)
mean_roa <- mean(bc$ROA, na.rm = TRUE)
# Tạo cột mới
data.frame( Chỉ_số = c( "Trung bình ROA"),
  # Định dạng cả hai giá trị thành % (ví dụ: 0.052 -> 5.2%)
  Giá_trị = scales::percent(c(mean_roa),decimal.mark = ",", accuracy = 0.1)) %>%
  # In kết quả
  kable(caption = "Trung bình chỉ số sinh lời ROA",col.names = c("Chỉ số", "Giá trị Trung bình"),
        align = "lr", booktabs = TRUE, row.names = FALSE)
Trung bình chỉ số sinh lời ROA
Chỉ số Giá trị Trung bình
Trung bình ROA 33,2%

Biểu đồ 8. ROA theo năm

# khởi tạo biểu đồ và gán trục X, trục Y
ggplot(bc, aes(x = nam, y = ROA)) +
  # Vẽ đường biểu diễn xu hướng ROA qua các năm
  geom_line(color = "pink", linewidth = 1.2) +
  # Thêm các điểm dữ liệu màu xanh lá tại từng năm
  geom_point(color = "green", size = 3) +
  # Hiển thị nhãn giá trị ROA ngay gần mỗi điểm, làm tròn đến 2 chữ số thập phân
  geom_text(aes(label = paste0(sub("\\.", ",",round(ROA, 2)))), vjust = 1.5, size = 3) +
  # Đặt tiêu đề và nhãn trục rõ ràng
labs(title = "TỶ SUẤT SINH LỜI TRÊN TÀI SẢN(%)", x = "Năm", y = "%") +
  # Hiển thị đầy đủ các mốc năm từ 2015 đến 2024 trên trục hoành
  scale_x_continuous(breaks = 2015:2024) +
  # Dùng giao diện nền trắng – khung đen (Black & White theme)
  theme_bw(base_family = "Times")


Nhận xét:

Giai đoạn 2015–2017: ROA tăng mạnh, đạt đỉnh 43,08% năm 2016, cho thấy hiệu quả sử dụng tài sản cao.

Giai đoạn 2018–2021: ROA giảm dần, đặc biệt thấp nhất 24,96% năm 2021, phản ánh hiệu quả sinh lời suy giảm, có thể do chi phí tăng hoặc lợi nhuận giảm.

Giai đoạn 2022–2024: ROA phục hồi nhẹ, dao động quanh mức 27–31%, cho thấy doanh nghiệp dần ổn định trở lại.

Nhìn chung: ROA có xu hướng biến động giảm nhẹ qua thời gian, nhưng vẫn duy trì ở mức khá, thể hiện hiệu quả khai thác tài sản tương đối tốt.

Biểu đồ 9: Tỷ lệ nợ tài sản và ROA

# khởi tạo biểu đồ và gán trục X
ggplot(bc, aes(x = nam)) +
  # Vẽ cột biểu diễn tỷ lệ nợ trên tổng tài sản (đổi sang %), màu xanh nhạt trong suốt
  geom_col(aes(y = ty_le_no_tai_san  * 100), fill = "skyblue", alpha = 0.6) +
  # Thêm đường biểu diễn ROA theo năm để so sánh
  geom_line(aes(y = ROA, color = "ROA"), linewidth = 1.2) +
  # Đánh dấu từng điểm dữ liệu ROA
  geom_point(aes(y = ROA, color = "ROA"), size = 3) +
  # Đặt tiêu đề và nhãn trục rõ ràng
  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 = "") +
  # Hiển thị đầy đủ các mốc năm từ 2015 đến 2024 trên trục hoành
  scale_x_continuous(breaks = 2015:2024) +
  # Áp dụng giao diện tối giản, loại bỏ khung và đường nền thừa
  theme_minimal(base_family = "Times")


Nhận xét: Trong các năm 2015–2017, tỷ lệ nợ duy trì ở mức cao (trên 30%), song ROA cũng đạt mức khá tốt, cho thấy doanh nghiệp sử dụng đòn bẩy tài chính hiệu quả, tận dụng nguồn vốn vay để mở rộng hoạt động sinh lời.

Từ 2018–2021, tỷ lệ nợ giảm dần nhưng ROA cũng sụt giảm mạnh, đặc biệt năm 2021 ROA chạm mức thấp nhất trong giai đoạn. Điều này phản ánh hiệu quả sử dụng vốn vay suy giảm, có thể do hiệu suất đầu tư kém hoặc tác động tiêu cực từ thị trường (như COVID-19).

Sau năm 2022, mặc dù tỷ lệ nợ tăng trở lại nhẹ, ROA có dấu hiệu hồi phục, cho thấy doanh nghiệp đang dần cải thiện khả năng sinh lời và kiểm soát chi phí vốn tốt hơn.

Biểu đồ 10: ROA vs ROE

# # khởi tạo biểu đồ và gán trục X
ggplot(bc, aes(x = nam)) +
  # vẽ đường xu hướng liền, thể hiện hiệu quả sinh lời trên tài sản
  geom_line(aes(y = ROA, color = "ROA"), linewidth = 1.2) +
   # vẽ đường xu hướng gạch đứt, thể hiện hiệu quả sinh lời trên vốn chủ sở hữu
  geom_line(aes(y = ROE, color = "ROE"), linewidth = 1.2, linetype = "dashed") +
  # đánh dấu mỗi điểm để dễ nhìn hơn
  geom_point(aes(y = ROA, color = "ROA"), size = 3) +
  geom_point(aes(y = ROE, color = "ROE"), size = 3) +
  # Đặt tiêu đề và nhãn trục rõ ràng
  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") +
  # Hiển thị đầy đủ các mốc năm từ 2015 đến 2024 trên trục hoành
  scale_x_continuous(breaks = 2015:2024) +
  # Áp dụng giao diện tối giản, loại bỏ khung và đường nền thừa
  theme_minimal(base_family = "Times")


Nhận xét:

ROA (tỷ suất sinh lời trên tài sản) duy trì ở mức 30–43%, phản ánh khả năng tạo lợi nhuận từ tổng tài sản tương đối ổn định, dù có giảm nhẹ giai đoạn 2019–2021 do hiệu suất sử dụng tài sản kém hơn.

ROE (tỷ suất sinh lời trên vốn chủ sở hữu) cao hơn đáng kể so với ROA, dao động từ 55–70% đầu kỳ giảm dần còn khoảng 35–40% về cuối kỳ. Điều này cho thấy doanh nghiệp sử dụng đòn bẩy tài chính mạnh nhưng hiệu quả vốn chủ giảm dần theo thời gian.

Giai đoạn 2021–2024, cả hai chỉ tiêu đều có xu hướng ổn định và có dấu hiệu phục hồi nhẹ, cho thấy hiệu quả sử dụng tài sản và vốn chủ đang được cải thiện trở lại.

Thêm biến tốc độ tăng trưởng Tổng tài sản (%)

Tạo cột mới

# Tính toán và lưu lại kết quả vào bc
bc <- bc %>%
  # Sắp xếp theo Năm
  arrange(nam) %>%
  # Công thức Tăng trưởng (%)
  mutate(tang_truong_tai_san = (tong_tai_san - lag(tong_tai_san)) / lag(tong_tai_san) * 100)
# Lọc cột
bc %>% transmute(Năm = nam,
  `Tổng tài sản (VND)`=format(tong_tai_san, big.mark =".",scientific = FALSE), 
  `Tăng trưởng tài sản (%)`=round(tang_truong_tai_san, 2)) %>%
# In kết quả
  kable( caption = "Tốc độ tăng trưởng(10 hàng đầu)",
         col.names = c("Năm", "Tổng tài sản (VND)", "Tăng trưởng tài sản (%)"),
         digits = c(0, 0, 4),  align = "lrr", booktabs = TRUE,row.names = FALSE)
Tốc độ tăng trưởng(10 hàng đầu)
Năm Tổng tài sản (VND) Tăng trưởng tài sản (%)
2015 21.571.925.688.349
2016 19.192.865.179.330 -11.03
2017 22.013.689.109.910 14.70
2018 22.366.742.792.213 1.60
2019 26.962.476.094.045 20.55
2020 27.374.972.775.358 1.53
2021 30.487.024.372.425 11.37
2022 34.465.075.615.756 13.05
2023 34.056.624.839.705 -1.19
2024 33.439.406.364.651 -1.81

Trung bình tốc độ tăng trưởng tài sản

# Tính giá trị trung bình (loại bỏ NA)
mean_tang_truong_tai_san <- mean(bc$tang_truong_tai_san, na.rm = TRUE)
# Tạo cột mới
data.frame(Chỉ_số = "Trung bình tăng trưởng tổng tài sản",
  Giá_trị = scales::number(mean_tang_truong_tai_san,decimal.mark = ",", accuracy = 0.01)) %>%
  # In kết quả
  kable( caption = "Trung bình tăng trưởng tài sản", col.names = c("Chỉ số", "Giá trị"),
         align = "lr", booktabs = TRUE,row.names = FALSE)
Trung bình tăng trưởng tài sản
Chỉ số Giá trị
Trung bình tăng trưởng tổng tài sản 5,42

Biểu đồ 11: Tăng trưởng tài sản

# khởi tạo biểu đồ và gán trục X, trục Y
ggplot(bc, aes(x = nam, y = tang_truong_tai_san)) +
  geom_col(fill = "darkseagreen") +
  geom_text(aes(label = paste0(round(tang_truong_tai_san), "%")), vjust = -0.6, size = 3.2) +
  # Đặt tiêu đề và nhãn trục rõ ràng
  labs(title = "TĂNG TRƯỞNG TÀI SẢN (%)", x = "Năm", y = "%") +
  # Hiển thị đầy đủ các mốc năm từ 2015 đến 2024 trên trục hoành
  scale_x_continuous(breaks = 2015:2024) +
  # Áp dụng giao diện tối giản, loại bỏ khung và đường nền thừa
  theme_minimal(base_family = "Times")


Nhận xét: Giai đoạn 2016–2019: Tăng trưởng biến động mạnh — năm 2016 giảm 11%, sau đó phục hồi mạnh, đạt đỉnh 20,5% năm 2019, phản ánh giai đoạn mở rộng quy mô tích cực.

Giai đoạn 2020–2022: Tài sản tiếp tục tăng ổn định, lần lượt 1,5%, 11,4% và 13%, cho thấy doanh nghiệp vẫn duy trì được xu hướng phát triển. Giai đoạn 2023–2024: Ghi nhận suy giảm nhẹ (-1,2% và -1,8%), có thể do ảnh hưởng từ thị trường chung hoặc chính sách thận trọng trong đầu tư.

Tổng thể: Doanh nghiệp có tăng trưởng tài sản mạnh nhưng thiếu ổn định, chu kỳ tăng – giảm thể hiện sự nhạy cảm với biến động kinh tế vĩ mô.

Biểu đồ 12: ROE và Tăng trưởng tài sản

# khởi tạo biểu đồ và gán trục X
ggplot(bc, aes(x = nam)) +
  # Vẽ cột thể hiện tốc độ tăng trưởng tài sản (%), màu xanh nhạt trong suốt
  geom_col(aes(y = tang_truong_tai_san), fill = "lightgreen", alpha = 0.6) +
  # Thêm đường thể hiện tỷ suất sinh lời vốn chủ sở hữu (ROE)
  geom_line(aes(y = ROE, color = "ROE"), linewidth = 1.2) +
  # Đánh dấu các điểm dữ liệu của ROE
  geom_point(aes(y = ROE, color = "ROE"), size = 3) +
  # Đặt tiêu đề và nhãn trục rõ ràng
  labs(title = "ẢNH HƯỞNG CỦA TĂNG TRƯỞNG TÀI SẢN ĐẾN 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 = "") +
  # Hiển thị đầy đủ các mốc năm từ 2015 đến 2024 trên trục hoành
  scale_x_continuous(breaks = 2015:2024) +
  # Áp dụng giao diện nền sáng (theme_light)
  theme_light(base_family = "Times")


Nhận xét: Từ 2015–2018, ROE duy trì ở mức cao trên 50%, cho thấy doanh nghiệp sử dụng vốn chủ sở hữu hiệu quả trong việc tạo ra lợi nhuận. Giai đoạn này, mức tăng trưởng tài sản dương, thể hiện chiến lược mở rộng đầu tư, giúp gia tăng quy mô và hiệu quả kinh doanh.

Tuy nhiên, từ 2019–2021, ROE giảm mạnh, dù tài sản vẫn tăng nhẹ. Điều này có thể xuất phát từ việc doanh nghiệp đầu tư mở rộng nhưng hiệu suất sử dụng tài sản chưa tương xứng, dẫn đến tỷ suất sinh lời trên vốn chủ bị suy giảm.

Sau năm 2022, ROE có dấu hiệu hồi phục nhẹ, song vẫn ở mức thấp hơn giai đoạn đầu, phản ánh rằng doanh nghiệp đang trong quá trình tái cơ cấu nguồn vốn hoặc chuyển hướng đầu tư để tìm lại hiệu quả sinh lời ổn định hơn.

Nhìn chung, biểu đồ cho thấy mối quan hệ thuận chiều nhưng không hoàn toàn tuyến tính giữa tăng trưởng tài sản và ROE – khi tốc độ tăng tài sản cao đi kèm khả năng sinh lời tốt, nhưng nếu tăng trưởng không đi đôi với quản trị vốn hiệu quả, ROE sẽ suy giảm.

Biểu đồ 13: Tỷ lệ Tiền mặt trên Tổng Tài sản

Tính toán

# Tính toán biến mới
df_ty_le_tien_mat <- bc %>%
mutate(ty_le_tien = (tien_va_cac_khoan_tuong_duong_tien / tong_tai_san) * 100)%>%
  select(nam, ty_le_tien) %>%
# Định dạng %
  mutate(Gia_tri_hien_thi = scales::percent(ty_le_tien / 100,accuracy = 0.01, decimal.mark = ","))
# In bảng
kable(df_ty_le_tien_mat %>% select(nam, Gia_tri_hien_thi),
      caption = "Tỷ lệ tiền mặt trên tổng tài sản",
      col.names = c("Năm", "Tỷ lệ Tiền mặt (%)"),
      align = "lr", booktabs = TRUE, row.names = FALSE) %>%
  kable_styling(position = "center", latex_options = "hold_position")
Tỷ lệ tiền mặt trên tổng tài sản
Năm Tỷ lệ Tiền mặt (%)
2015 36,79%
2016 17,95%
2017 11,98%
2018 19,97%
2019 15,27%
2020 9,96%
2021 11,83%
2022 11,81%
2023 14,80%
2024 13,39%

Trực quan hóa

# khởi tạo biểu đồ và gán trục X, trục Y
ggplot(df_ty_le_tien_mat, aes(x = nam, y = ty_le_tien)) +
  # các lớp vẽ vùng, đường, điểm
  geom_area(fill = "paleturquoise", alpha = 0.5) +
  geom_line(color = "dodgerblue", linewidth = 1) +
  geom_point(color = "blue2", size = 3) +
  # Dùng trực tiếp cột 'Gia_tri_hien_thi' bạn đã tạo
  geom_text(aes(label = Gia_tri_hien_thi),vjust = -0.8,hjust= 0.3,size = 3.4) +
  # Tùy chỉnh trục X
  scale_x_continuous(breaks =min(df_ty_le_tien_mat$nam):max(df_ty_le_tien_mat$nam)) +
  # Tùy chỉnh trục Y
  scale_y_continuous(labels = function(x) paste0(x, "%")) +
  # Thêm tiêu đề, phụ đề, nhãn trục và chú thích
  labs(title = "TỶ LỆ TIỀN MẶT TRÊN TỔNG TÀI SẢN",
       x = "Năm", y = "Tỷ lệ (%)") +
  # Áp dụng giao diện tối giản
  theme_minimal(base_family = "Times")


Nhận xét: Tỷ lệ tiền mặt trên tổng tài sản có xu hướng giảm mạnh từ năm 2015 đến 2020, sau đó dao động nhẹ và phục hồi nhẹ ở giai đoạn 2021–2024. Điều này cho thấy doanh nghiệp giảm dần mức nắm giữ tiền mặt, có thể nhằm tăng hiệu quả sử dụng tài sản hoặc chuyển hướng đầu tư vào các tài sản sinh lời khác.

Giai đoạn đầu (2015–2018): Biến động mạnh, có thể do điều chỉnh chiến lược tài chính. Giai đoạn giữa và cuối (2019–2024): Ổn định và duy trì ở mức hợp lý, phản ánh sự cân bằng giữa khả năng thanh khoản và hiệu quả sử dụng vốn.

Kết luận:Tỷ lệ tiền mặt trên tổng tài sản giảm và ổn định dần, cho thấy doanh nghiệp đang tối ưu hóa cấu trúc tài sản, giảm tồn quỹ tiền mặt dư thừa để nâng cao hiệu quả đầu tư và lợi nhuận.

Biểu đồ 14 : Tỷ lệ đầu tư tài chính trên tổng tài sản

# Tính toán biến mới
df_ty_le_dautu <- bc %>%
  mutate(ty_le_dautu = (cac_khoan_dau_tu_tai_chinh_ngan_han / tong_tai_san) * 100)%>%
  select(nam, ty_le_dautu) %>%
# Định dạng %
mutate(Gia_tri_hien_thi = scales::percent(ty_le_dautu / 100,accuracy = 0.01, decimal.mark = ","))
# In bảng
kable(df_ty_le_dautu %>% select(nam, Gia_tri_hien_thi),
      caption = "Tỷ lệ đầu tư tài chính ngắn hạn trên tổng tài sản",
      col.names = c("Năm", "Tỷ lệ đầu tư"),
      align = "lr", booktabs = TRUE, row.names = FALSE) %>%
  kable_styling(position = "center", latex_options = "hold_position")
Tỷ lệ đầu tư tài chính ngắn hạn trên tổng tài sản
Năm Tỷ lệ đầu tư
2015 4,37%
2016 16,54%
2017 29,79%
2018 33,73%
2019 45,96%
2020 53,14%
2021 55,73%
2022 56,32%
2023 52,09%
2024 49,54%

TRỰC QUAN HÓA

# khởi tạo biểu đồ và gán trục X, Y
ggplot(df_ty_le_dautu, aes(x = nam, y = ty_le_dautu)) +
  geom_area(fill = "lavender", alpha = 0.5) +
  # Vẽ các đường kẻ
  geom_line(linewidth = 1.2, color = "plum") +
  # Vẽ dấu chấm tại các điểm dữ liệu
  geom_point(size = 3, color = "purple") +
  # Thêm nhãn văn bản
  geom_text(aes( label = Gia_tri_hien_thi), vjust = -1, size = 3) +
  # Tùy chỉnh trục X, trục Y
  scale_x_continuous(breaks = min(df_ty_le_dautu$nam):max(df_ty_le_dautu$nam)) +
  scale_y_continuous(labels = function(x) paste0(x, "%")) +
  # Thêm tiêu đề, phụ đề, nhãn trục và chú thích
  labs(title = "TỶ LỆ ĐẦU TƯ TÀI CHÍNH NGẮN HẠN TRÊN TỔNG TÀI SẢN",
       x = "Năm", y = "Tỷ lệ (%)") +
  # Áp dụng giao diện tối giản
  theme_minimal(base_family = "Times")

* Nhận xét**:Tỷ lệ đầu tư tài chính ngắn hạn tăng mạnh từ năm 2015 đến 2021, sau đó giảm nhẹ trong giai đoạn 2022–2024.

Giai đoạn 2015–2021: Tăng trưởng mạnh mẽ, phản ánh chiến lược mở rộng đầu tư tài chính ngắn hạn.

Giai đoạn 2022–2024: Điều chỉnh giảm, thể hiện xu hướng cân đối lại cơ cấu tài sản.

Dù giảm nhẹ, tỷ lệ đầu tư vẫn ở mức cao (gần 50%), chứng tỏ doanh nghiệp vẫn duy trì lượng đầu tư ngắn hạn đáng kể.

BIỂU ĐỒ 15: Tài sản ngắn hạn theo năm

# khởi tạo biểu đồ và gán trục X, trục Y.
ggplot(bc, aes(x = nam, y = tai_san_ngan_han)) +
  # Tạo vùng tô màu dưới đường biểu diễn, màu xanh nhạt.
  geom_area(fill = "lightblue", alpha = 0.4) +
  # Vẽ đường xu hướng chính, thể hiện mức biến động tài sản ngắn hạn.
  geom_line(color = "darkblue", linewidth = 1.2) +
  # Đánh dấu các giá trị cụ thể từng năm bằng các điểm màu đỏ.
  geom_point(color = "red", size = 3) +
  # Hiển thị nhãn giá trị thực tế phía trên mỗi điểm (làm tròn đến số nguyên).
  geom_text(aes(label = scales::number(tai_san_ngan_han,big.mark = ".")),
            angle = 90,hjust = 1.1, vjust = 1) +
  # Định dạng trục Y để CÓ DẤU CHẤM
  scale_y_continuous(labels = number_format(big.mark = ".")) +
  #Đặt tên biểu đồ, nhãn trục X và Y .
  labs(title = "XU HƯỚNG TÀI SẢN NGẮN HẠN (2015–2024)",x = "Năm", y = "Giá trị (tỷ đồng)") +
  # Hiển thị từng năm từ 2015 đến 2024 trên trục X.
  scale_x_continuous(breaks = 2015:2024) +
  # Chọn giao diện sáng nhẹ, giúp biểu đồ rõ ràng và dễ đọc.
  theme_light(base_family = "Times")

* Nhận xét**:Tài sản ngắn hạn có xu hướng tăng mạnh qua các năm, cho thấy khả năng thanh khoản và quy mô tài sản lưu động của doanh nghiệp được mở rộng đáng kể.

Tuy nhiên, đến năm 2024, giá trị có dấu hiệu giảm nhẹ, thể hiện sự điều chỉnh hoặc thu hẹp quy mô đầu tư ngắn hạn.

Biểu đồ 16: Tài sản dài hạn theo năm

# khởi tạo biểu đồ và gán trục X, trục Y.
ggplot(bc, aes(x = nam, y = tai_san_dai_han)) +
  # Vẽ biểu đồ cột để thể hiện giá trị tài sản dài hạn theo từng năm,
  geom_col(fill = "steelblue3") +
  # Hiển thị nhãn giá trị cụ thể trên đầu mỗi cột.
  geom_text(aes(label = scales::number(tai_san_ngan_han, big.mark = ".")), 
            angle = 90, vjust = 1,hjust = 1.1, color = "azure") +
  # Định dạng trục Y để CÓ DẤU CHẤM
  scale_y_continuous(labels = number_format(big.mark = ".")) +
  # Đặt tiêu đề và tên trục rõ ràng.
  labs(title = "XU HƯỚNG TÀI SẢN DÀI HẠN (2015–2024)", x = "Năm", y = "Tỷ đồng") +
  # Hiển thị các mốc năm từ 2015 đến 2024 trên trục hoành.
  scale_x_continuous(breaks = 2015:2024) +
  # Dùng giao diện nền trắng – khung đen (Black & White theme).
  theme_bw(base_family = "Times")


Nhận xét: Trong giai đoạn 2015–2024, tài sản dài hạn có xu hướng tăng nhẹ, nhưng mức độ biến động thấp hơn đáng kể so với tài sản ngắn hạn. Điều này phản ánh rằng doanh nghiệp duy trì chính sách đầu tư dài hạn ổn định, không có biến động mạnh về cơ cấu tài sản cố định hoặc đầu tư dài hạn.

Mặc dù có sự dao động nhẹ, nhưng nhìn chung, tài sản dài hạn duy trì ở mức ổn định và chiếm tỷ trọng đáng kể trong tổng tài sản.

Sự ổn định này thể hiện chiến lược đầu tư dài hạn bền vững, giúp doanh nghiệp đảm bảo năng lực sản xuất, cơ sở vật chất và lợi thế cạnh tranh lâu dài.

Tuy nhiên, việc giảm nhẹ vào năm 2024 cần được theo dõi để đảm bảo rằng đây chỉ là điều chỉnh tạm thời, không ảnh hưởng đến hiệu quả sử dụng vốn dài hạn.

Biểu đồ 17: Trực quan tài sản dài hạn và ngắn hạn

# khởi tạo biểu đồ và gán trục X
ggplot(bc, aes(x = nam)) +
# Vẽ vùng diện tích (area chart) thể hiện "Tài sản ngắn hạn"
geom_area(aes(y = tai_san_ngan_han/1000, fill = "Ngắn hạn"), alpha = 0.4) +
# Vẽ vùng diện tích (area chart) thể hiện "Tài sản dài hạn"
geom_area(aes(y = tai_san_dai_han/1000, fill = "Dài hạn"), alpha = 0.4) +
# Thêm đường viền làm nổi bật rõ xu hướng của "Tài sản ngắn hạn"
geom_line(aes(y = tai_san_ngan_han/1000, color = "Ngắn hạn"), linewidth = 1.2) +
# Thêm đường viền làm nổi bật rõ xu hướng của "Tài sản dài hạn"
geom_line(aes(y = tai_san_dai_han/1000, color = "Dài hạn"), linewidth = 1.2) +
# Định dạng trục Y để CÓ DẤU CHẤM
  scale_y_continuous(labels = number_format(big.mark = ".")) +
# Đặt tiêu đề và nhãn trục rõ ràng
labs(title = "TÀI SẢN NGẮN HẠN VÀ DÀI HẠN (2015–2024)",
x = "Năm", y = "Giá trị (nghìn tỷ đồng)", color = "Loại tài sản") +
# Hiển thị đầy đủ các mốc năm từ 2015 đến 2024 trên trục hoành
scale_x_continuous(breaks = 2015:2024) +
# Áp dụng giao diện tối giản, loại bỏ khung và đường nền thừa
theme_minimal(base_family = "Times")


Nhận xét: Trong giai đoạn 2015–2024, tài sản ngắn hạn chiếm tỷ trọng lớn và tăng mạnh qua các năm, đặc biệt từ 2018 đến 2022. Ngược lại, tài sản dài hạn duy trì ổn định với mức tăng nhẹ về cuối kỳ. Điều này cho thấy doanh nghiệp tập trung mở rộng quy mô hoạt động ngắn hạn và tăng khả năng thanh khoản, trong khi vẫn giữ cơ cấu tài sản dài hạn ổn định.

Biểu đồ 18: Biểu đồ tổng hợp đa tuyến

# Khởi tạo biểu đồ với dữ liệu bc
ggplot(bc) +
  # Vẽ đường xu hướng thể hiện "Tổng tài sản"," doanh thu thuần"và lợi nhuận gộp
  # Các giá trị được chia cho 1000 để đổi sang đơn vị nghìn tỷ đồng
  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) +
  # Thêm tiêu đề, phụ đề, nhãn trục và chú thích màu để biểu đồ rõ ràng
  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") +
  # Hiển thị đầy năm
  scale_x_continuous(breaks = 2015:2024) +
  # Áp dụng giao diện tối giản, loại bỏ khung và đường nền thừa
  theme_minimal(base_family = "Times")


Nhận xét: Giai đoạn 2015–2019, doanh thu và lợi nhuận gộp tăng ổn định, song đến 2020–2021 có dấu hiệu suy giảm rõ rệt. Ngược lại, tổng tài sản tăng đều qua các năm, cho thấy doanh nghiệp mở rộng quy mô đầu tư dù hiệu quả sinh lời có biến động. Từ 2022–2024, cả doanh thu và lợi nhuận phục hồi, phản ánh sự ổn định trở lại trong hoạt động kinh doanh.

Biểu đồ 19: Các năm có doanh thu hoạt động tài chính hơn 1 nghìn tỷ

Tính toán

# Đặt ngưỡng 
nguong_dt_tc <- 1000000000000 # 1 nghìn tỷ
# Tính toán
df_dt_tc_vuot_nguong <- bc %>%
  # Lọc: theo Doanh thu Hoạt động Tài chính
  filter(doanh_thu_hoat_dong_tai_chinh > nguong_dt_tc) %>%
  # Tạo biến mới (chia Tỷ đồng)
  mutate( Doanh_thu_TC_Ty_dong = doanh_thu_hoat_dong_tai_chinh / 1000000000,
          Doanh_thu_Hien_thi = scales::number(Doanh_thu_TC_Ty_dong, 
          big.mark = ".", accuracy = 1)) %>%
  arrange(nam) # Sắp xếp
# In kết quả
kable(df_dt_tc_vuot_nguong %>% select(nam, Doanh_thu_Hien_thi),
      caption = "Năm có doanh thu hoạt động tài chính vượt 1.000 tỷ đồng",
      col.names = c("Năm", "Doanh thu TC (Tỷ đồng)"),
      align = "lr", booktabs = TRUE, row.names = FALSE) %>%
  kable_styling(position = "center", latex_options = "hold_position")
Năm có doanh thu hoạt động tài chính vượt 1.000 tỷ đồng
Năm Doanh thu TC (Tỷ đồng)
2021 1.120
2022 1.091
2023 1.433
2024 1.067

Trực quan hóa

# Khởi tạo (DỌC): X = Năm (Tên), Y = Doanh thu (Số)
ggplot(df_dt_tc_vuot_nguong, aes(x = reorder(factor(nam), Doanh_thu_TC_Ty_dong), 
           y = Doanh_thu_TC_Ty_dong)) +
  # Vẽ đường: y = 0, yend = Doanh thu
  geom_segment(aes(x = reorder(factor(nam), Doanh_thu_TC_Ty_dong),
        xend = reorder(factor(nam), Doanh_thu_TC_Ty_dong), 
        y = 0, yend = Doanh_thu_TC_Ty_dong),color = "gray", linewidth = 1)+
  # Vẽ điểm
  geom_point(aes(color = factor(nam)),  size = 5,show.legend = FALSE) +
  # Thêm nhãn giá trị
  geom_text(aes(label = Doanh_thu_Hien_thi), size = 4, hjust= -0.7) +
  # Lật trục
  coord_flip() +
  # Tùy chỉnh trục Y 
  scale_y_continuous(labels = number_format(big.mark = "."),
                     expand = expansion(mult = c(0, 0.15))) +
  # Thêm bảng màu
  scale_color_brewer(palette = "Set2") +
  # Tiêu đề
  labs(title = "CÁC NĂM CÓ DOANH THU HOẠT ĐỘNG VƯỢT 1.000 TỶ ĐỒNG",
       subtitle = "Biểu đồ Lollipop", x ="Năm", y ="Doanh thu hoạt động (tỷ đồng)") +
  # Giao diện 
  theme_minimal(base_family = "Times") +
  theme(panel.grid.major.y = element_blank(),panel.grid.minor.x = element_blank())