CHƯƠNG 1: BỘ DỮ LIỆU CUSTOMER CHURN DATASET FOR LIFE INSURANCE INDUSTRY

library(readxl)
## Warning: package 'readxl' was built under R version 4.4.3
df <- read_excel("D:/thầy Tường/Customer.xlsx")

Đoạn mã R được sử dụng để nhập bộ dữ liệu Customen từ tệp Excel vào môi trường làm việc của R để chuẩn bị cho quá trình phân tích

1 GIỚI THIỆU VỀ DỮ LIỆU

Bộ dữ liệu được sử dụng trong tiểu luận này được thu thập từ nền tảng Kaggle, một kho lưu trữ dữ liệu trực tuyến phổ biến, được sử dụng rộng rãi trong các nghiên cứu học thuật và dự án phân tích dữ liệu. Bộ dữ liệu phản ánh thông tin của khách hàng trong lĩnh vực bảo hiểm nhân thọ, bao gồm nhiều đặc điểm nhân khẩu học, hành vi tiêu dùng và tình trạng rời bỏ dịch vụ (churn).

Việc sử dụng bộ dữ liệu này trong nghiên cứu nhằm phân tích và xác định các yếu tố có ảnh hưởng đáng kể đến khả năng rời bỏ của khách hàng, từ đó xây dựng mô hình dự báo churn có độ chính xác cao. Kết quả phân tích không chỉ giúp doanh nghiệp nhận diện nhóm khách hàng có nguy cơ rời bỏ, mà còn là cơ sở để đề xuất các chính sách chăm sóc, duy trì và phát triển khách hàng hiệu quả hơn. Ngoài ra, nghiên cứu cũng mang ý nghĩa thực tiễn khi thể hiện ứng dụng của phân tích dữ liệu trong ngành bảo hiểm nhân thọ, góp phần nâng cao hiệu quả hoạt động và năng lực cạnh tranh của doanh nghiệp trên thị trường.

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

2.1 Số biến, số quan sát

dim(df)
## [1] 200000     12

-> Bộ dữ liệu có 12 biến và 200000 quan sát.

2.2 Ý nghĩa các biến

library(knitr)
## Warning: package 'knitr' was built under R version 4.4.3
variable_meaning <- data.frame(
  Variable = c(
    "Index",
    "Customer Name",
    "Customer Address",
    "Company Name",
    "Claim Reason",
    "Data confidentiality",
    "Claim Amount",
    "Category Premium",
    "Premium/Amount Ratio",
    "Claim Request output",
    "BMI",
    "Churn"
  ), 
  Meaning = c(
    "Số thứ tự của khách hàng trong bộ dữ liệu",
    "Tên khách hàng",
    "Địa chỉ khách hàng",
    "Tên công ty bảo hiểm quản lý hợp đồng",
    "Lý do khách hàng yêu cầu bồi thường",
    "Mức độ bảo mật dữ liệu của khách hàng",
    "Số tiền khách hàng yêu cầu bồi thường",
    "Mức phí bảo hiểm của khách hàng",
    "Tỷ lệ giữa phí bảo hiểm và số tiền yêu cầu bồi thường",
    "Kết quả xử lý yêu cầu bồi thường ",
    "Chỉ số khối cơ thể",
    "Khách hàng có rời công ty bảo hiểm hay không"
  ),
  stringsAsFactors = FALSE
)
kable(variable_meaning, col.names = c("Variable", "Meaning"))
Variable Meaning
Index Số thứ tự của khách hàng trong bộ dữ liệu
Customer Name Tên khách hàng
Customer Address Địa chỉ khách hàng
Company Name Tên công ty bảo hiểm quản lý hợp đồng
Claim Reason Lý do khách hàng yêu cầu bồi thường
Data confidentiality Mức độ bảo mật dữ liệu của khách hàng
Claim Amount Số tiền khách hàng yêu cầu bồi thường
Category Premium Mức phí bảo hiểm của khách hàng
Premium/Amount Ratio Tỷ lệ giữa phí bảo hiểm và số tiền yêu cầu bồi thường
Claim Request output Kết quả xử lý yêu cầu bồi thường
BMI Chỉ số khối cơ thể
Churn Khách hàng có rời công ty bảo hiểm hay không

2.3 Kiểu dữ liệu các biến

# Tạo đối tượng tibble chứa thông tin tóm tắt
library(tibble)
data_types_summary <- tibble(
  Ten_Cot = c("Index", "Customer Name", "Customer Address", "Company Name", 
              "Claim Reason", "Data confidentiality", "Claim Amount", 
              "Category Premium", "Premium/Amount Ratio", "Claim Request output", 
              "BMI", "Churn"),
  Loai_Du_lieu_trong_R = c("num", "chr", "chr", "chr", "chr", 
                           "chr", "num", "num", "num", "chr", 
                           "num", "chr")
)

# Tải gói knitr và hiển thị bảng
library(knitr)
kable(data_types_summary,
      caption = "Bảng Tóm Tắt Loại Dữ liệu",
      col.names = c("Tên Cột", "Loại Dữ liệu trong R (R Type)")
)
Bảng Tóm Tắt Loại Dữ liệu
Tên Cột Loại Dữ liệu trong R (R Type)
Index num
Customer Name chr
Customer Address chr
Company Name chr
Claim Reason chr
Data confidentiality chr
Claim Amount num
Category Premium num
Premium/Amount Ratio num
Claim Request output chr
BMI num
Churn chr

->Có 5 biến định lương (num) và 7 biến định tính(chr).

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

# Tóm tắt biến Index 
summary(df$Index)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##       0   50000  100000  100000  149999  199999

-> Đây là biến số thứ tự, nó không mang ý nghĩa thống kê

# Tóm tắt biến Claim Amount
summary(df$`Claim Amount`)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##       1     245    1390    1120    1844    2299

-> Giá trị Trung vị (Median = 1390) cao hơn đáng kể so với Giá trị Trung bình (Mean = 1120). Sự chênh lệch này (\(\text{Median} > \text{Mean}\)) chỉ ra rằng phân bố dữ liệu bị lệch trái.

Kết luận: Phần lớn các yêu cầu bồi thường đều có giá trị ở mức cao (gần \(1390\) đến \(2299\)). Giá trị Trung bình bị kéo xuống thấp hơn do sự tồn tại của một lượng nhỏ các yêu cầu có giá trị rất thấp (gần \(1\)). Điều này cho thấy công ty bảo hiểm phải xử lý một lượng lớn các yêu cầu bồi thường có giá trị lớn.

# Tóm tắt biến Category Premium
summary(df$`Category Premium`)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##     399    1875   14390    8964   14390   14390

-> Phân tích thống kê mô tả cho biến Category Premium cho thấy một sự phân bố cực kỳ bất thường và lệch trái nghiêm trọng. Sự trùng khớp giữa Giá trị Trung vị (\(\text{Median} = 14390\)), Tứ phân vị thứ ba (\(\mathbf{3^{rd} Qu.} = 14390\)) và Giá trị Cao nhất (\(\mathbf{Max.} = 14390\)) chỉ ra rằng phần lớn (ít nhất \(\mathbf{75\%}\) ) các khách hàng đều đóng mức phí bảo hiểm cao nhất là \(14390\).

Giá trị Trung bình (\(\mathbf{Mean} = 8964\)) thấp hơn Trung vị rất nhiều, bị kéo xuống bởi một lượng nhỏ các khách hàng đóng phí thấp (chỉ từ \(\mathbf{399}\) đến \(1875\)). Điều này gợi ý rằng mô hình phí bảo hiểm đang có tính tập trung cao vào mức phí tối đa, hoặc hầu hết khách hàng thuộc nhóm phí cao nhất.

# Chuyển đổi biến từ kiểu character sang kiểu numeric
df$'Premium/Amount Ratio' <- as.numeric(df$'Premium/Amount Ratio')
# Tóm tắt biến Premium/Amount Ratio
summary(df$`Premium/Amount Ratio`)
##     Min.  1st Qu.   Median     Mean  3rd Qu.     Max. 
## 0.002506 0.106741 0.125122 0.125024 0.143155 0.248120

-> Giá trị Trung vị (Median = 0.125122) và Giá trị Trung bình (Mean = 0.125024) gần như bằng nhau (\(\text{Median} \approx \text{Mean}\)). Điều này chỉ ra rằng sự phân bố tỷ lệ là rất đối xứng (không bị lệch đáng kể).

Khoảng phân tán (Range): Tỷ lệ nằm trong khoảng hẹp, từ tối thiểu \(\mathbf{0.0025}\) đến tối đa \(\mathbf{0.248}\).

Kết luận: Phần lớn các quan sát có tỷ lệ phí bảo hiểm trên số tiền yêu cầu bồi thường tập trung rất sát mức \(\mathbf{12.5\%}\) (\(0.125\)). Điều này cho thấy có một sự ổn định hoặc tính đồng nhất cao trong cách công ty bảo hiểm định giá phí so với rủi ro tiềm ẩn (số tiền bồi thường).

# Tóm tắt biến BMI 
summary(df$BMI)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##   18.00   20.00   23.00   23.01   26.00   28.00

-> Biến BMI có phân bố rất đối xứng, được xác định bởi sự trùng khớp giữa Giá trị Trung bình (\(\text{Mean} = 23.01\)) và Giá trị Trung vị (\(\text{Median} = 23.00\)).BMI của khách hàng dao động trong khoảng từ \(\mathbf{18.00}\) (thấp nhất) đến \(\mathbf{28.00}\) (cao nhất). Phần lớn chỉ số BMI tập trung chặt chẽ xung quanh mức \(23.00\), với \(50\%\) số khách hàng có BMI nằm trong khoảng \(\mathbf{20.00}\) đến \(\mathbf{26.00}\) (khoảng tứ phân vị).

Kết quả này cho thấy, về tổng thể, khách hàng trong bộ dữ liệu có chỉ số khối cơ thể tương đối đồng nhất và nằm chủ yếu trong phạm vi cân nặng khỏe mạnh đến thừa cân nhẹ, không có trường hợp BMI quá cực đoan.

2.5 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ê

# Số lần xuất hiện của các giá trị trong biến Claim Reason
table(df$'Claim Reason')
## 
## Medical   Other   Phone  Travel 
##  109863   30187   30016   29934

-> Medical (109863): Có 109863 trường hợp yêu cầu bồi thường liên quan đến lý do Y tế/Sức khỏe.

Other (30187): Có 30187 trường hợp yêu cầu bồi thường thuộc các lý do Khác (không được phân loại cụ thể).

Phone (30016): Có 30016 trường hợp yêu cầu bồi thường liên quan đến Điện thoại (có thể là hư hỏng, mất mát thiết bị, hoặc các vấn đề liên quan đến dịch vụ).

Travel (29934): Có 29934 trường hợp yêu cầu bồi thường liên quan đến Du lịch (có thể là hủy chuyến, mất hành lý, trễ giờ, v.v.).

Lý do phổ biến nhất là Medical, chiếm số lượng áp đảo so với các lý do khác. Điều này cho thấy các yêu cầu bồi thường y tế là vấn đề lớn nhất trong dữ liệu này.

Ba loại còn lại (Other, Phone, Travel) có số lượng khá tương đương nhau, dao động quanh mức 30000 trường hợp.

# Số lần xuất hiện của các giá trị trong biến Data confidentiality
table(df$`Data confidentiality`)
## 
##     High      Low   Medium Very low 
##   109863    29934    30016    30187

-> Dữ liệu cho thấy có một sự phân cực rõ rệt: phần lớn các bản ghi được coi là có mức độ bảo mật Cao, trong khi ba mức độ còn lại (Low, Medium, Very low) có số lượng bản ghi tương đương nhau và thấp hơn đáng kể so với mức High.

# Số lần xuất hiện của các giá trị trong biến Claim Request output
table(df$`Claim Request output`)
## 
##     No    Yes 
## 192994   7006

-> Kết quả cho thấy một sự mất cân bằng rất lớn trong dữ liệu.

Số lượng yêu cầu bị từ chối (No) (199994) lớn hơn rất nhiều so với số lượng yêu cầu được chấp thuận (Yes) (7006).

# Số lần xuất hiện của các giá trị trong biến Churn 
table(df$Churn)
## 
##     No    Yes 
##  72728 127272

Kết quả cho thấy số lượng khách hàng rời bỏ (Yes) (127272) lớn hơn đáng kể so với số lượng khách hàng duy trì (72728).

Điều này chỉ ra rằng công ty đang đối mặt với một tỷ lệ khách hàng rời bỏ rất cao.

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

colSums(is.na(df))
##                Index        Customer Name     Customer_Address 
##                    0                    0                    0 
##         Company Name         Claim Reason Data confidentiality 
##                    0                    0                    0 
##         Claim Amount     Category Premium Premium/Amount Ratio 
##                    0                    0                    0 
## Claim Request output                  BMI                Churn 
##                    0                    0                    0

Bộ dữ liệu không có giá trị bị thiếu

2.7 Kiểm tra trùng lặp

sum(duplicated(df))
## [1] 0

Bộ dữ liệu không có hàng nào giống nhau hoàn toàn

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

# Tải lại các thư viện cần thiết
library(dplyr)
## Warning: package 'dplyr' was built under R version 4.4.3
## 
## Attaching package: 'dplyr'
## The following objects are masked from 'package:stats':
## 
##     filter, lag
## The following objects are masked from 'package:base':
## 
##     intersect, setdiff, setequal, union
library(stringr)
library(readr)
## Warning: package 'readr' was built under R version 4.4.3
library(tibble)
# Tạo bản sao dữ liệu gốc để bắt đầu chuỗi xử lý
data_start <- df
data_start 

3.1 Tách các Bang/tiểu Bang trong Customer Address

# 1. Tách Bang/Tiểu bang
data_step_1 <- data_start %>%
  mutate(State = str_extract(Customer_Address, pattern = "(?<=,\\s)[A-Z]{2}(?=\\s\\d{5})|(?<=,\\s)[A-Z]{2}(?=\\d{5})")) %>%
  mutate(State = ifelse(is.na(State), "Unknown", State))
data_step_1

3.2 Xóa cột Index và Address để làm sạch

# 2. Làm sạch Cột (Xóa cột Address và Index)
data_step_2 <- data_step_1 %>%
  select(-Index, -Customer_Address)
data_step_2

3.3 Chuẩn hóa tên các công ty

# Sử dụng str_to_title() để chuẩn hóa văn bản
data_step_3 <- data_step_2 %>%
  mutate('Company Name' = str_to_title(`Company Name`))
data_step_3

3.4 Đổi tên cột

# 4. Đổi tên cột (Chuẩn hóa)
data_step_4 <- data_step_3 %>%
  rename(Customer_Name = 'Customer Name',
 Company_Name='Company Name',
    Claim_Request_Output = 'Claim Request output',
         Data_Confidentiality = 'Data confidentiality',
         Claim_Reason = 'Claim Reason',
         Claim_Amount = 'Claim Amount',
         Category_Premium = 'Category Premium')
data_step_4

3.5 Chuyển kiểu dữ liệu đảm bảo biến Claim Amount là số nguyên

# 5. Chuyển kiểu dữ liệu (Claim Amount sang Integer)
data_step_5 <- data_step_4 %>%
  mutate(Claim_Amount = as.integer(Claim_Amount))
data_step_5

3.6 Mã hóa nhị phân ở Churn

# 6. Mã hóa Nhị phân cho Churn
data_step_6 <- data_step_5 %>%
  mutate(Churn_Encoded = ifelse(Churn == "Yes", 1, 0))
data_step_6

3.7 Mã hóa thứ bậc cho Data Confidentiality

# 7. Mã hóa Thứ bậc cho Data_Confidentiality
confidentiality_levels <- c("Very low", "Low", "Medium", "High", "Very high")
data_step_7 <- data_step_6 %>%
  mutate(Data_Conf_Encoded = factor(Data_Confidentiality,
                                    levels = confidentiality_levels,
                                    labels = 1:5,
                                    ordered = TRUE))
data_step_7

3.8 Tạo cột tỷ lệ Claim_Amount/Category_Premium

# 8. Tạo Cột Tỷ lệ Claim/Premium
data_step_8 <- data_step_7 %>%
  mutate(Claim_Premium_Ratio = Claim_Amount / Category_Premium)
data_step_8

3.9 Phân nhóm cho BMI

# 9. PHÂN NHÓM BMI (Tạo biến phân loại)
# Dựa trên tiêu chuẩn phân loại BMI:
# < 18.5: Dưới cân; 18.5 - 24.9: Bình thường; 25.0 - 29.9: Thừa cân; >= 30: Béo phì
data_step_9 <- data_step_8 %>%
  mutate(BMI_Status = cut(BMI,
                          breaks = c(0, 18.5, 25, 30, Inf), # Các điểm cắt
                          labels = c("Underweight", "Normal", "Overweight", "Obese"), # Tên của 4 nhóm
                          include.lowest = TRUE,
                          right = FALSE))

data_step_9
print(data_step_9 %>% count(BMI_Status))
## # A tibble: 3 × 2
##   BMI_Status       n
##   <fct>        <int>
## 1 Underweight  18293
## 2 Normal      108979
## 3 Overweight   72728

3.10 Mã hóa cho các giá trị của Claim Reason

# 10. Mã hóa cho Claim Reason
reason_dummies <- model.matrix(~ Claim_Reason - 1, data = data_step_9) %>% as_tibble()
data_step_10 <- bind_cols(data_step_9, reason_dummies)
data_step_10 <- data_step_10 %>% rename_with(~ gsub("Claim_Reason", "Reason_", .x), starts_with("Claim_Reason"))
data_step_10

-> Thao tác data_step_10 (One-Hot Encoding) có mục đích mã hóa cột lý do yêu cầu bồi thường (Claim_Reason) từ dữ liệu chữ thành dữ liệu số (1 và 0), bằng cách tạo ra các cột nhị phân mới (ví dụ: Reason_Medical, Reason_Travel) để dữ liệu có thể được sử dụng trong các mô hình học máy mà không ngụ ý bất kỳ thứ bậc nào giữa các lý do khác nhau.

4 PHẦN 3: CÁC THỐNG KÊ CƠ BẢN, PHÂN TÍCH VÀ TRỰC QUAN HÓA DỮ LIỆU

library(dplyr)    
library(ggplot2)  
## Warning: package 'ggplot2' was built under R version 4.4.3
library(tidyr)    
library(corrplot) 
## corrplot 0.95 loaded
library(scales)
## Warning: package 'scales' was built under R version 4.4.3
## 
## Attaching package: 'scales'
## The following object is masked from 'package:readr':
## 
##     col_factor
dff <- data_step_10
dff 

4.1 Thống kê tỷ lệ khách hàng rời bỏ (Churn Rate)

4.1.1 Tỷ lệ Churn

churn_rate <- dff %>%
  summarise(
    Total_Customers = n(),
    Churned_Customers = sum(Churn_Encoded),
    Churn_Rate = mean(Churn_Encoded) * 100
  )
churn_rate

4.1.2 Phân bố biến mục tiêu Churn (Bar Plot)

ggplot(dff, aes(x = Churn, fill = Churn)) +
  geom_bar() + # Layer 1: Đồ thị thanh
  labs(title = "Phân Bố Khách Hàng Rời Bỏ (Churn)",
       x = "Trạng thái Rời bỏ",
       y = "Số lượng Khách hàng") + # Layer 2: Tiêu đề và nhãn
  scale_fill_manual(values = c("Yes" = "#E41A1C", "No" = "#377EB8")) + # Layer 3: Đổi màu
  geom_text(stat = 'count', aes(label = after_stat(count)), vjust = -0.5, size = 4) + # Layer 4: Gắn nhãn số lượng
  theme_minimal() # Layer 5: Chủ đề tối giản

4.2 Phân bố Chỉ số Khối Cơ thể (BMI) (Histogram)

# 3. Biểu đồ Histogram cho BMI
ggplot(dff, aes(x = BMI)) +
  geom_histogram(binwidth = 1, fill = "#4DAF4A", color = "white") + # Layer 1: Histogram, binwidth = 1
  labs(title = "Phân Bố Chỉ Số Khối Cơ Thể (BMI)",
       x = "Chỉ số BMI",
       y = "Tần suất") + # Layer 2: Tiêu đề và nhãn
  geom_vline(aes(xintercept = mean(BMI)), color = "red", linetype = "dashed", linewidth = 1) + # Layer 3: Đường trung bình
  annotate("text", x = mean(dff$BMI) + 2, y = 30000, label = paste("Mean =", round(mean(dff$BMI), 2)), color = "red") + # Layer 4: Ghi chú trung bình
  theme_classic() # Layer 5: Chủ đề cổ điển

4.3 So sánh Lượng Bồi thường theo Trạng thái Rời bỏ (Box Plot)

# 4. Biểu đồ Hộp (Box Plot) Claim Amount theo Churn
ggplot(dff, aes(x = Churn, y = Claim_Amount, fill = Churn)) +
  geom_boxplot() + # Layer 1: Biểu đồ hộp
  labs(title = "Phân Bố Số Tiền Yêu Cầu Bồi Thường theo Trạng thái Churn",
       x = "Khách hàng Rời bỏ",
       y = "Số tiền Bồi thường (Claim Amount)") + # Layer 2: Tiêu đề và nhãn
  coord_flip() + # Layer 3: Xoay trục
  scale_fill_manual(values = c("Yes" = "#E41A1C", "No" = "#377EB8")) + # Layer 4: Đổi màu
  theme_bw() # Layer 5: Chủ đề đen trắng

4.4 Tỷ lệ Churn theo Mức độ Bảo mật Dữ liệu (Bar Plot Tỷ lệ)

# 5. Tỷ lệ Churn theo Data_Confidentiality
churn_by_conf <- dff %>%
  group_by(Data_Confidentiality) %>%
  summarise(Churn_Rate = mean(Churn_Encoded) * 100) %>%
  mutate(Data_Confidentiality = factor(Data_Confidentiality, levels = c("Very low", "Low", "Medium", "High"))) # Đảm bảo thứ tự

ggplot(churn_by_conf, aes(x = Data_Confidentiality, y = Churn_Rate)) +
  geom_col(fill = "#FF7F00") + # Layer 1: Đồ thị cột
  labs(title = "Tỷ lệ Churn theo Mức độ Bảo mật Dữ liệu",
       x = "Mức độ Bảo mật",
       y = "Tỷ lệ Churn (%)") + # Layer 2: Tiêu đề và nhãn
  geom_text(aes(label = paste0(round(Churn_Rate, 2), "%")), vjust = -0.5, size = 4) + # Layer 3: Gắn nhãn tỷ lệ
  scale_y_continuous(limits = c(0, max(churn_by_conf$Churn_Rate) * 1.1)) + # Layer 4: Điều chỉnh trục Y
  theme_minimal() # Layer 5: Chủ đề tối giản

4.5 Phân bố Phí Bảo hiểm (Category Premium) (Density Plot)

# 6. Biểu đồ mật độ (Density Plot) cho Category Premium
ggplot(dff, aes(x = Category_Premium)) +
  geom_density(fill = "#984EA3", alpha = 0.7) + # Layer 1: Biểu đồ mật độ
  labs(title = "Phân Bố Mức Phí Bảo Hiểm (Category Premium)",
       x = "Mức Phí Bảo hiểm",
       y = "Mật độ") + # Layer 2: Tiêu đề và nhãn
  geom_rug(alpha = 0.5) + # Layer 3: Dấu gạch (rug plot) trên trục x
  geom_vline(aes(xintercept = median(Category_Premium)), color = "darkgreen", linetype = "solid", linewidth = 1) + # Layer 4: Đường trung vị
  theme_light() # Layer 5: Chủ đề sáng

4.6 Tỷ lệ Churn theo Kết quả Yêu cầu Bồi thường (Bar Plot So sánh)

# 7. Tỷ lệ Churn theo Claim Request Output
churn_by_claim_output <- dff %>%
  group_by(Claim_Request_Output) %>%
  summarise(Churn_Rate = mean(Churn_Encoded) * 100)

ggplot(churn_by_claim_output, aes(x = Claim_Request_Output, y = Churn_Rate, fill = Claim_Request_Output)) +
  geom_col() + # Layer 1: Đồ thị cột
  labs(title = "Tỷ lệ Churn khi Yêu cầu Bồi thường được/bị Chấp thuận",
       x = "Kết quả Yêu cầu Bồi thường",
       y = "Tỷ lệ Churn (%)") + # Layer 2: Tiêu đề và nhãn
  scale_fill_manual(values = c("No" = "#E41A1C", "Yes" = "#4DAF4A")) + # Layer 3: Đổi màu
  geom_text(aes(label = paste0(round(Churn_Rate, 2), "%")), vjust = -0.5, size = 4) + # Layer 4: Gắn nhãn tỷ lệ
  theme_bw() # Layer 5: Chủ đề đen trắng

4.7 Số tiền Yêu cầu Bồi thường trung bình theo Lý do Bồi thường (Grouped Bar Plot)

4.7.1 Tính toán bồi thường trung bình cho từng lý do

# 8.Số tiền Yêu cầu Bồi thường Trung bình theo Lý do Bồi thường (Sử dụng cột đã mã hóa)
 # Tính toán bồi thường trung bình cho từng lý do
mean_claim_by_reason_fixed <- dff %>%
  summarise(
    Medical = mean(Claim_Amount[Reason_Medical == 1]),
    Other = mean(Claim_Amount[Reason_Other == 1]),
    Phone = mean(Claim_Amount[Reason_Phone == 1]),
    Travel = mean(Claim_Amount[Reason_Travel == 1])
  ) %>%
  # Chuyển đổi từ dạng rộng sang dạng dài để vẽ biểu đồ
  tidyr::pivot_longer(cols = everything(), 
                      names_to = "Claim_Reason_Fixed", 
                      values_to = "Mean_Claim_Amount")
mean_claim_by_reason_fixed

4.7.2 Trực quan hóa

  # Trực quan hóa
ggplot(mean_claim_by_reason_fixed, aes(x = Claim_Reason_Fixed, y = Mean_Claim_Amount, fill = Claim_Reason_Fixed)) +
  geom_bar(stat = "identity") + # Layer 1: Đồ thị cột
  labs(title = "Số tiền Bồi thường trung bình theo Lý do yêu cầu bồi thường ",
       x = "Lý do Yêu cầu Bồi thường",
       y = "Số tiền Bồi thường Trung bình") + # Layer 2: Tiêu đề và nhãn
  scale_fill_brewer(palette = "Set1") + # Layer 3: Bảng màu
  geom_text(aes(label = round(Mean_Claim_Amount, 0)), vjust = -0.5, size = 4) + # Layer 4: Gắn nhãn giá trị
  theme_classic() + # Layer 5: Chủ đề cổ điển
  theme(axis.text.x = element_text(angle = 45, hjust = 1)) # Xoay nhãn x

4.8 Phân tích Tương quan giữa các biến số (Correlation Plot)

4.8.1 Chọn các biến số và tính ma trận tương quan

# Chọn lọc và đảm bảo dữ liệu là số
cor_data <- dff %>%
  select(Claim_Amount, Category_Premium, `Premium/Amount Ratio`, 
         BMI, Claim_Premium_Ratio, Churn_Encoded) %>%
  # Bắt buộc chuyển đổi tất cả sang numeric để tránh lỗi 'x' must be numeric
  mutate(across(everything(), as.numeric))
# Tính Ma trận tương quan
# Cột 'Premium/Amount Ratio' có dấu cách nên cần dùng backticks (`)
M <- cor(cor_data, use = "complete.obs") 
# CHUYỂN SANG BẢNG VÀ HIỂN THỊ KẾT QUẢ
correlation_table <- M %>%
  as.data.frame() %>%
  round(3) # Làm tròn 3 chữ số thập phân
# In bảng ra console
print(correlation_table)
##                      Claim_Amount Category_Premium Premium/Amount Ratio    BMI
## Claim_Amount                1.000            0.959                0.160  0.001
## Category_Premium            0.959            1.000               -0.001  0.001
## Premium/Amount Ratio        0.160           -0.001                1.000 -0.003
## BMI                         0.001            0.001               -0.003  1.000
## Claim_Premium_Ratio         0.160           -0.001                1.000 -0.003
## Churn_Encoded               0.001            0.000                0.001 -0.836
##                      Claim_Premium_Ratio Churn_Encoded
## Claim_Amount                       0.160         0.001
## Category_Premium                  -0.001         0.000
## Premium/Amount Ratio               1.000         0.001
## BMI                               -0.003        -0.836
## Claim_Premium_Ratio                1.000         0.001
## Churn_Encoded                      0.001         1.000

4.8.2 Trực quan hóa

corrplot::corrplot(M,
                   method = "circle", # Layer 1: Dạng hình tròn
                   type = "upper", # Layer 2: Chỉ hiển thị nửa trên
                   order = "hclust", # Layer 3: Sắp xếp theo phân cụm
                   tl.col = "black", # Layer 4: Màu nhãn
                   tl.srt = 45, # Layer 5: Xoay nhãn
                   diag = FALSE)

4.9 Phân bố Claim_Premium_Ratio (Line/Density Plot)

4.9.1 Lập Bảng Tần Suất

# Tính toán các điểm cắt (breaks)
bin_width <- 0.005
min_val <- 0
max_val <- 0.5
breaks <- seq(min_val, max_val, by = bin_width)

# Tạo bảng tần suất (chỉ xem xét dữ liệu trong phạm vi 0 đến 0.5)
# Sử dụng dplyr::count để tạo bảng tần suất rõ ràng hơn
library(dplyr)
# Lọc dữ liệu trong phạm vi (0, 0.5]
dff_filtered <- dff %>%
  filter(Claim_Premium_Ratio > 0 & Claim_Premium_Ratio <= 0.5)

# Phân loại vào các bins và đếm
frequency_table <- dff_filtered %>%
  mutate(Bin = cut(Claim_Premium_Ratio,
                   breaks = breaks,
                   include.lowest = TRUE,
                   right = TRUE,
                   labels = FALSE)) %>%
  # Tính toán giá trị trung tâm (center) của mỗi bin cho dễ hình dung
  mutate(Bin_Center = breaks[Bin] + bin_width / 2) %>%
  # Đếm tần suất
  count(Bin, Bin_Center, name = "Frequency") %>%
  # Chọn các cột cần thiết và đổi tên cột Bin_Center cho dễ hiểu
  select(Bin_Center, Frequency) %>%
  rename(`Claim/Premium Ratio Center` = Bin_Center, `Tần Suất` = Frequency)

# Hiển thị 10 dòng đầu của bảng tần suất
head(frequency_table, 10)

4.9.2 Trực quan hóa biểu đồ tần suất Claim_Premium_Ratio

ggplot(dff, aes(x = Claim_Premium_Ratio)) +
  geom_freqpoly(binwidth = 0.005, color = "#FF7F00", linewidth = 1.2) + # Layer 1: Đồ thị tần suất đường
  labs(title = "Phân Bố Tỷ Lệ Bồi Thường/Phí Bảo Hiểm",
       x = "Tỷ lệ Claim/Premium",
       y = "Số lượng") + # Layer 2: Tiêu đề và nhãn
  geom_density(aes(y = after_stat(count) * 0.005), alpha = 0.2, fill = "#FF7F00") + # Layer 3: Biểu đồ mật độ với scaling
  geom_vline(aes(xintercept = median(Claim_Premium_Ratio)), color = "blue", linetype = "dashed") + # Layer 4: Đường trung vị
  theme_minimal() + # Layer 5: Chủ đề tối giản
  xlim(0, 0.5) # Giới hạn trục X để dễ quan sát hơn
## Warning: Removed 2 rows containing missing values or values outside the scale range
## (`geom_path()`).

4.10 Tỷ lệ Churn theo Phân nhóm BMI (Bar Plot)

# 11. Tỷ lệ Churn theo BMI_Status
churn_by_bmi <- dff %>%
  group_by(BMI_Status) %>%
  summarise(Churn_Rate = mean(Churn_Encoded) * 100)

ggplot(churn_by_bmi, aes(x = BMI_Status, y = Churn_Rate, fill = BMI_Status)) +
  geom_col() + # Layer 1: Đồ thị cột
  labs(title = "Tỷ lệ Churn theo Tình trạng BMI",
       x = "Tình trạng BMI",
       y = "Tỷ lệ Churn (%)") + # Layer 2: Tiêu đề và nhãn
  scale_fill_brewer(palette = "Dark2") + # Layer 3: Bảng màu
  geom_text(aes(label = paste0(round(Churn_Rate, 2), "%")), vjust = -0.5, size = 4) + # Layer 4: Gắn nhãn tỷ lệ
  theme_void() + # Layer 5: Chủ đề trống
  theme(plot.title = element_text(hjust = 0.5)) # Căn giữa tiêu đề

4.11 Phân bố Phí Bảo hiểm theo Trạng thái Churn (Violin Plot)

# 12. Biểu đồ Violin cho Category Premium theo Churn
ggplot(dff, aes(x = Churn, y = Category_Premium, fill = Churn)) +
  geom_violin(trim = FALSE) + # Layer 1: Biểu đồ Violin (trim=FALSE để giữ toàn bộ mật độ)
  geom_boxplot(width = 0.1, color = "black", alpha = 0.8) + # Layer 2: Thêm biểu đồ hộp để thấy tứ phân vị
  labs(title = "Phân Bố Phí Bảo hiểm theo Trạng thái Churn",
       x = "Trạng thái Churn",
       y = "Phí Bảo hiểm (Category Premium)") + # Layer 3: Tiêu đề và nhãn
  scale_fill_manual(values = c("Yes" = "#E41A1C", "No" = "#377EB8")) + # Layer 4: Đổi màu
  theme_minimal() # Layer 5: Chủ đề tối giản

4.12 PHÂN BỐ CLAIM AMOUNT THEO LÝ DO YÊU CẦU

4.12.1 Chuyển đổi dữ liệu và xử lý lỗi kiểu dữ liệu

claim_data_long <- dff %>%
  select(Claim_Amount, starts_with("Reason_")) %>%
  # Ép kiểu tất cả các cột Reason_... về số
  mutate(across(starts_with("Reason_"), as.numeric)) %>% 
  # Chuyển đổi từ dạng rộng sang dạng dài
  tidyr::pivot_longer(
    cols = starts_with("Reason_"), 
    names_to = "Claim_Reason_Fixed", 
    values_to = "Is_Claim"
  ) %>%
  # Lọc ra chỉ những hàng có yêu cầu bồi thường (Is_Claim = 1)
  filter(Is_Claim == 1) %>%
  # Làm sạch nhãn
  mutate(Claim_Reason_Fixed = gsub("Reason_", "", Claim_Reason_Fixed))
## Warning: There was 1 warning in `mutate()`.
## ℹ In argument: `across(starts_with("Reason_"), as.numeric)`.
## Caused by warning:
## ! NAs introduced by coercion
# In bảng thống kê số lượng Claim
print(claim_data_long %>% count(Claim_Reason_Fixed) %>% rename(Num_Claims = n))
## # A tibble: 4 × 2
##   Claim_Reason_Fixed Num_Claims
##   <chr>                   <int>
## 1 Medical                109863
## 2 Other                   30187
## 3 Phone                   30016
## 4 Travel                  29934

4.12.2 Trực quan hóa Biểu đồ Hộp (Box Plot)

ggplot(claim_data_long, aes(x = Claim_Reason_Fixed, y = Claim_Amount, fill = Claim_Reason_Fixed)) +
  geom_boxplot(outlier.shape = NA) + # Layer 1: Biểu đồ hộp (ẩn outlier để gọn)
  labs(title = "Phân Bố Claim Amount theo Lý do Yêu cầu",
       x = "Lý do Bồi thường",
       y = "Số tiền Bồi thường (Claim Amount)") + # Layer 2: Tiêu đề và nhãn
  scale_fill_brewer(palette = "Set2") + # Layer 3: Bảng màu
  theme_bw() + # Layer 4: Chủ đề đen trắng
  theme(legend.position = "none") # Layer 5: Ẩn chú giải

4.13 Tổng hợp Churn theo Lý do Claim (Ordered Bar Plot)

4.13.1 TỶ LỆ CHURN THEO LÝ DO CLAIM

# 1. Tính toán Tỷ lệ Churn bằng cách Chuyển đổi dữ liệu và Nhóm
churn_rate_by_reason_fixed <- dff %>%
  select(Churn_Encoded, starts_with("Reason_")) %>%
#ÉP KIỂU SỐ
  mutate(across(starts_with("Reason_"), as.numeric)) %>% 
  # Chuyển đổi từ dạng rộng sang dạng dài
  tidyr::pivot_longer(
    cols = starts_with("Reason_"), 
    names_to = "Claim_Reason_Fixed", 
    values_to = "Is_Claim"
  ) %>%
  # Lọc (chỉ giữ lại những hàng có yêu cầu bồi thường) và Làm sạch nhãn
  filter(Is_Claim == 1) %>%
  mutate(Claim_Reason_Fixed = gsub("Reason_", "", Claim_Reason_Fixed)) %>%
  # Nhóm và Tính toán tỷ lệ Churn
  group_by(Claim_Reason_Fixed) %>%
  summarise(
    Churn_Rate = mean(Churn_Encoded) * 100
  ) %>%
  arrange(desc(Churn_Rate))
## Warning: There was 1 warning in `mutate()`.
## ℹ In argument: `across(starts_with("Reason_"), as.numeric)`.
## Caused by warning:
## ! NAs introduced by coercion
# In bảng tính toán
print(churn_rate_by_reason_fixed)
## # A tibble: 4 × 2
##   Claim_Reason_Fixed Churn_Rate
##   <chr>                   <dbl>
## 1 Travel                   64.1
## 2 Medical                  63.6
## 3 Other                    63.6
## 4 Phone                    63.3

4.13.2 Trực quan hóa Biểu đồ Cột

ggplot(churn_rate_by_reason_fixed, 
       aes(x = reorder(Claim_Reason_Fixed, -Churn_Rate), y = Churn_Rate, fill = Claim_Reason_Fixed)) +
  geom_col() + # Layer 1: Đồ thị cột
  labs(title = "Tỷ lệ Churn theo Lý do Yêu cầu Bồi thường ",
       x = "Lý do Claim",
       y = "Tỷ lệ Churn (%)") + # Layer 2: Tiêu đề và nhãn
  scale_fill_brewer(palette = "Accent") + # Layer 3: Bảng màu
  geom_text(aes(label = paste0(round(Churn_Rate, 2), "%")), vjust = -0.5) + # Layer 4: Gắn nhãn tỷ lệ
  theme_classic() + # Layer 5: Chủ đề cổ điển
  theme(legend.position = "none")

4.14 Phân bố Địa lý của Khách hàng (Top 10 State)

# 15. Top 10 Bang/Tiểu bang
top_10_states <- dff %>%
  count(State, sort = TRUE) %>%
  head(10)

ggplot(top_10_states, aes(x = reorder(State, n), y = n, fill = State)) +
  geom_col() + # Layer 1: Đồ thị cột
  labs(title = "Top 10 Bang/Tiểu bang có nhiều khách hàng",
       x = "Bang/Tiểu bang",
       y = "Số lượng Khách hàng") + # Layer 2: Tiêu đề và nhãn
  coord_flip() + # Layer 3: Xoay trục
  scale_fill_hue(l = 50) + # Layer 4: Bảng màu sắc thái
  geom_text(aes(label = n), hjust = -0.1, size = 4) + # Layer 5: Gắn nhãn số lượng
  theme_light()

4.15 Tương tác đa chiều 4 biến (Small Multiples Scatter Plot)

# Biểu đồ Lưới Nhỏ 4 Biến (Small Multiples Scatter Plot)
ggplot(dff, aes(x = Category_Premium, y = Claim_Amount)) +
  geom_point(aes(color = Claim_Premium_Ratio), alpha = 0.3) + # Layer 1: Biểu đồ phân tán (màu theo Ratio)
  scale_color_viridis_c() + # Layer 2: Bảng màu Viridis cho Ratio
  facet_grid(Claim_Request_Output ~ Churn, scales = "free") + # Layer 3: Chia theo 2 biến (4 ô)
  geom_smooth(method = "lm", color = "red", se = FALSE, linewidth = 1) + # Layer 4: Đường xu hướng
  labs(title = "Tương tác Premium vs Claim, phân tách theo Churn và Claim Output",
       x = "Phí Bảo hiểm (Category Premium)", y = "Số tiền Bồi thường (Claim Amount)") + # Layer 5: Tiêu đề và nhãn
  theme_bw()
## `geom_smooth()` using formula = 'y ~ x'

4.16 So sánh Claim_Premium_Ratio theo Trạng thái Churn (Grouped Density Plot)

# Biểu đồ Mật độ cho Claim_Premium_Ratio theo Churn
ggplot(dff, aes(x = Claim_Premium_Ratio, fill = Churn)) +
  geom_density(alpha = 0.6) + # Layer 1: Biểu đồ mật độ (alpha để thấy sự chồng lấp)
  labs(title = "Phân Bố Tỷ lệ Claim/Premium theo Trạng thái Churn",
       x = "Tỷ lệ Claim/Premium",
       y = "Mật độ") + # Layer 2: Tiêu đề và nhãn
  scale_fill_manual(values = c("Yes" = "#E41A1C", "No" = "#377EB8")) + # Layer 3: Đổi màu
  geom_vline(data = dff %>% group_by(Churn) %>% summarise(mean_ratio = mean(Claim_Premium_Ratio)),
             aes(xintercept = mean_ratio, color = Churn), linetype = "dashed") + # Layer 4: Đường trung bình cho mỗi nhóm
  theme_classic() + # Layer 5: Chủ đề cổ điển
  xlim(0, 0.4) # Giới hạn trục X

4.17 Sự thay đổi Median Premium (Slope Chart)

# Biểu đồ Dạng Dốc (Slope Chart)
slope_data <- dff %>%
  group_by(BMI_Status, Churn) %>%
  summarise(Median_Premium = median(Category_Premium), .groups = 'drop')

ggplot(slope_data, aes(x = BMI_Status, y = Median_Premium, group = Churn, color = Churn)) +
  geom_line(linewidth = 1.5) + # Layer 1: Đường nối
  geom_point(size = 4) + # Layer 2: Điểm
  labs(title = "Sự thay đổi Premium Trung vị theo BMI Status và Churn",
       x = "Tình trạng BMI", y = "Trung vị Phí Bảo hiểm (Category Premium)") + # Layer 3: Tiêu đề và nhãn
  scale_color_manual(values = c("Yes" = "#E41A1C", "No" = "#377EB8")) + # Layer 4: Đổi màu theo Churn
  theme_bw() + # Layer 5: Chủ đề đen trắng
  theme(axis.text.x = element_text(angle = 45, hjust = 1))

4.18 Phân bố Claim_Amount theo Bang/Tiểu bang (Top 5 Median)

# Median Claim Amount theo Top 5 Bang
top_5_states_list <- (dff %>% count(State, sort = TRUE) %>% head(5))$State

median_claim_by_state <- dff %>%
  filter(State %in% top_5_states_list) %>%
  group_by(State) %>%
  summarise(Median_Claim_Amount = median(Claim_Amount))

ggplot(median_claim_by_state, aes(x = reorder(State, Median_Claim_Amount), y = Median_Claim_Amount, fill = State)) +
  geom_col() + # Layer 1: Đồ thị cột
  labs(title = "Số tiền Bồi thường theo trung vị trong Top 5 Bang",
       x = "Bang/Tiểu bang",
       y = "Số tiền Bồi thường theo trung vị") + # Layer 2: Tiêu đề và nhãn
  coord_flip() + # Layer 3: Xoay trục
  scale_fill_brewer(palette = "Pastel1") + # Layer 4: Bảng màu Pastel
  geom_text(aes(label = round(Median_Claim_Amount, 0)), hjust = -0.1, size = 4) + # Layer 5: Gắn nhãn giá trị
  theme_minimal()

4.19 Phân bố Claim Amount theo BMI Status và Churn (Faceted Density Plot)

# Phân bố Claim Amount theo BMI Status và Churn (Faceted Density Plot)
ggplot(dff, aes(x = Claim_Amount, fill = Churn)) +
  geom_density(alpha = 0.6) + # Layer 1: Biểu đồ mật độ (alpha để thấy sự chồng lấp)
  facet_wrap(~ BMI_Status, scales = "free_y", ncol = 2) + # Layer 2: Chia đồ thị theo BMI_Status, trục Y độc lập
  labs(title = "Phân Bố Claim Amount theo Tình trạng BMI và Trạng thái Churn",
       x = "Số tiền Bồi thường (Claim Amount)",
       y = "Mật độ") + # Layer 3: Tiêu đề và nhãn
  scale_fill_manual(values = c("Yes" = "#E41A1C", "No" = "#377EB8")) + # Layer 4: Đổi màu theo Churn
  theme_bw() # Layer 5: Chủ đề đen trắng

4.20 Tỷ lệ Churn theo Tình trạng BMI (Lollipop Chart)

4.20.1 Tỷ lệ Churn chi tiết theo Tình trạng BMI

# Tỷ lệ Churn chi tiết theo BMI Status
churn_by_bmi_detail <- dff %>%
  group_by(BMI_Status) %>%
  summarise(
    Total_Customers = n(),
    Churned_Customers = sum(Churn_Encoded),
    Churn_Rate = mean(Churn_Encoded) * 100
  ) %>%
  arrange(desc(Churn_Rate))
print(churn_by_bmi_detail)
## # A tibble: 3 × 4
##   BMI_Status  Total_Customers Churned_Customers Churn_Rate
##   <fct>                 <int>             <dbl>      <dbl>
## 1 Underweight           18293             18293        100
## 2 Normal               108979            108979        100
## 3 Overweight            72728                 0          0

4.20.2 Tỷ lệ Churn theo Tình trạng BMI (Lollipop Chart)

# Tỷ lệ Churn theo BMI Status (Lollipop Chart)
churn_by_bmi_plot <- dff %>%
  group_by(BMI_Status) %>%
  summarise(Churn_Rate = mean(Churn_Encoded) * 100, .groups = 'drop')

ggplot(churn_by_bmi_plot, aes(x = BMI_Status, y = Churn_Rate)) +
  geom_segment(aes(x = BMI_Status, xend = BMI_Status, y = 0, yend = Churn_Rate), 
               color = "gray", linewidth = 1) + # Layer 1: Đường nối
  geom_point(aes(color = BMI_Status), size = 5) + # Layer 2: Điểm (Lollipop)
  labs(title = "Tỷ lệ Churn theo Tình trạng BMI",
       x = "Tình trạng BMI",
       y = "Tỷ lệ Churn (%)") + # Layer 3: Tiêu đề và nhãn
  scale_color_brewer(palette = "Set1") + # Layer 4: Bảng màu
  geom_text(aes(label = paste0(round(Churn_Rate, 1), "%")), vjust = -1, size = 3) + # Layer 5: Gắn nhãn tỷ lệ
  theme_minimal()

CHƯƠNG 2: PHÂN TÍCH BÁO CÁO TÀI CHÍNH ABS

5 Giới thiệu về dữ liệu

options(repos = c(CRAN = "https://cloud.r-project.org"))
install.packages(c(
  "readxl",
  "dplyr",
  "DT",
  "knitr",
  "ggplot2",
  "scales",
  "janitor",
  "magrittr",
  "kableExtra"
))
## Warning: packages 'readxl', 'dplyr', 'knitr', 'ggplot2', 'scales' are in use
## and will not be installed
## Installing packages into 'C:/Users/DELL/AppData/Local/R/win-library/4.4'
## (as 'lib' is unspecified)
## package 'DT' successfully unpacked and MD5 sums checked
## package 'janitor' successfully unpacked and MD5 sums checked
## package 'magrittr' successfully unpacked and MD5 sums checked
## Warning: cannot remove prior installation of package 'magrittr'
## Warning in file.copy(savedcopy, lib, recursive = TRUE): problem copying
## C:\Users\DELL\AppData\Local\R\win-library\4.4\00LOCK\magrittr\libs\x64\magrittr.dll
## to
## C:\Users\DELL\AppData\Local\R\win-library\4.4\magrittr\libs\x64\magrittr.dll:
## Permission denied
## Warning: restored 'magrittr'
## package 'kableExtra' successfully unpacked and MD5 sums checked
## 
## The downloaded binary packages are in
##  C:\Users\DELL\AppData\Local\Temp\RtmpA70Gt8\downloaded_packages

6 Phần 1: giới thiệu bộ dữ liệu

6.1 Đọc dữ liệu

library(readxl)
# Xác định đường dẫn đến tệp dữ liệu
bc <- read_excel("D:/thầy Tường/bctcabs.xlsx")
  • Ý nghĩa thống kê: Đây là bước “nạp” dữ liệu thô vào môi trường R để chuẩn bị phân tích.

6.2 đọc toàn bộ sheet names

# Đọc và hiển thị tên của tất cả các sheet trong file
sheets <- excel_sheets("D:/thầy Tường/bctcabs.xlsx") 
print(sheets)
## [1] "bctc abs"
  • Ý nghĩa thống kê: Các file báo cáo tài chính (BCTC) thường chứa nhiều sheet (ví dụ: “Bảng Cân đối kế toán”, “Báo cáo KQKD”, “Lưu chuyển tiền tệ”, “Thuyết minh”). Lệnh này giúp tổng quan file Excel có bao nhiêu sheet và tên của chúng là gì, từ đó quyết định sẽ đọc sheet nào vào để phân tích.

6.3 Đọc sheet chính

# Đọc dữ liệu từ sheet đầu tiên
bc_raw <- read_excel ("D:/thầy Tường/bctcabs.xlsx", sheet = sheets[1]) 
  • Ý nghĩa thống kê: Việc đọc dữ liệu bằng hàm read_excel() là bước đầu tiên trong quy trình phân tích thống kê, giúp đưa dữ liệu thực tế từ file Excel vào môi trường R để xử lý và phân tích. Dữ liệu bc_raw được xem là mẫu quan sát gốc, làm cơ sở cho các bước thống kê mô tả, kiểm định và mô hình hóa sau này. Việc chọn sheet = sheets[1] đảm bảo tính thống nhất khi tệp có nhiều bảng dữ liệu khác nhau.

6.4 Xem cấu trúc

# Xem cấu trúc của data frame:tổng số quan sát (dòng), tổng số biến (cột), tên của từng cột và quan trọng nhất là kiểu dữ liệu (data type) của cột đó (ví dụ: numeric, character, dttm).
str(bc_raw)
## tibble [10 × 11] (S3: tbl_df/tbl/data.frame)
##  $ Năm/Biến                                          : num [1:10] 2015 2016 2017 2018 2019 ...
##  $ Tài sản ngắn hạn                                  : num [1:10] 1.22e+13 1.07e+13 1.37e+13 1.47e+13 1.92e+13 ...
##  $ Tiền và các khoản tương đương tiền                : num [1:10] 7.94e+12 3.44e+12 2.64e+12 4.47e+12 4.12e+12 ...
##  $ Các khoản đầu tư tài chính ngắn hạn               : num [1:10] 9.43e+11 3.17e+12 6.56e+12 7.54e+12 1.24e+13 ...
##  $ Tài sản dài hạn                                   : num [1:10] 9.38e+12 8.48e+12 8.33e+12 7.68e+12 7.80e+12 ...
##  $ Tổng tài sản                                      : num [1:10] 2.16e+13 1.92e+13 2.20e+13 2.24e+13 2.70e+13 ...
##  $ Tổng nợ phải trả                                  : num [1:10] 7.51e+12 6.76e+12 7.59e+12 6.25e+12 6.89e+12 ...
##  $ Vốn chủ sở hữu                                    : num [1:10] 1.41e+13 1.24e+13 1.44e+13 1.61e+13 2.01e+13 ...
##  $ Doanh thu thuần về bán hàng
## và cung cấp dịch vụ: num [1:10] 2.71e+13 3.06e+13 3.44e+13 3.59e+13 3.79e+13 ...
##  $ Lợi nhuận gộp về bán hàng
## và cung cấp dịch vụ  : num [1:10] 7.56e+12 8.27e+12 8.87e+12 8.08e+12 9.55e+12 ...
##  $ Doanh thu hoạt động tài chính                     : num [1:10] 4.19e+11 7.51e+11 5.06e+11 6.30e+11 8.90e+11 ...
  • Ý nghĩa thống kê: Đây là bước kiểm tra dữ liệu (data sanity check) then chốt. Nó giúp xác nhận các cột chứa chỉ tiêu tài chính (ví dụ: “Tài sản”, “Nợ”) đã được R nhận diện đúng là kiểu numeric. Nếu R đọc nhầm một cột số thành character, điều đó cho thấy sự tồn tại của các ký tự phi số (ví dụ: dấu phẩy, “N/A”) cần phải được làm sạch trước khi tính toán.

6.5 Xem số liệu bộ dữ liệu

head(bc_raw, 10) 
  • ý nghĩa thống kê: Trực quan số liệu 10 năm của Báo Cáo Tài Chính ABS

6.6 Xác định số dòng

# Số dòng
n_rows <- nrow(bc_raw) 
cat('Số dòng:', n_rows, "\n")
## Số dòng: 10
  • Ý nghĩa thống kê: Xác định số dòng

6.7 Xác định số cột

# Số cột
n_cols <- ncol(bc_raw) 
cat('Số cột:', n_cols, "\n")
## Số cột: 11
  • Ý nghĩa thống kê: Xác định số cột

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

sapply(bc_raw, class) 
##                                           Năm/Biến 
##                                          "numeric" 
##                                   Tài sản ngắn hạn 
##                                          "numeric" 
##                 Tiền và các khoản tương đương tiền 
##                                          "numeric" 
##                Các khoản đầu tư tài chính ngắn hạn 
##                                          "numeric" 
##                                    Tài sản dài hạn 
##                                          "numeric" 
##                                       Tổng tài sản 
##                                          "numeric" 
##                                   Tổng nợ phải trả 
##                                          "numeric" 
##                                     Vốn chủ sở hữu 
##                                          "numeric" 
## Doanh thu thuần về bán hàng\r\nvà cung cấp dịch vụ 
##                                          "numeric" 
##   Lợi nhuận gộp về bán hàng\r\nvà cung cấp dịch vụ 
##                                          "numeric" 
##                      Doanh thu hoạt động tài chính 
##                                          "numeric"
  • Ý nghĩa thống kê: Đâ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.

6.9 Kiểm tra các giá trị duy nhất của cột năm

unique_years <- unique(bc_raw[[1]]) 
print(unique_years)
##  [1] 2015 2016 2017 2018 2019 2020 2021 2022 2023 2024
  • Ý nghĩa thống kê: Thao tác này trích xuất cột đầu tiên của bộ dữ liệu (giả định là một biến phân loại như “Năm” hoặc “Chỉ tiêu”) và sử dụng hàm unique để lọc ra danh sách các giá trị duy nhất (không trùng lặp).

Mục đích là để kiểm tra tính nhất quán (consistency) và phạm vi (scope) của biến. Trong phân tích, nếu có lỗi nhập liệu (ví dụ: “Tiền mặt” và “Tiền mặt” - thừa dấu cách), R sẽ hiểu đây là hai nhóm (factors) riêng biệt. Điều này dẫn đến việc “pha loãng” dữ liệu, khiến các phép thống kê gộp nhóm (group_by) hoặc đếm tần suất (count) bị chia tách và cho ra kết quả sai lệch. Thao tác này giúp phát hiện sớm các lỗi trên để tiến hành chuẩn hóa dữ liệu.

6.10 Kiểm tra giá trị trùng lặp (toàn bộ hàng)

# Hàm duplicated được sử dụng để rà soát toàn bộ bộ dữ liệu và xác định các hàng (quan sát) bị trùng lặp y hệt (giống nhau trên tất cả các biến).
dup_rows <- sum(duplicated(bc_raw)) 
cat('Số hàng trùng lặp toàn bộ:', dup_rows, "\n")
## Số hàng trùng lặp toàn bộ: 0
  • Ý nghĩa thống kê: Các quan sát trùng lặp vi phạm giả định về tính độc lập của các quan sát. Nếu không bị loại bỏ, chúng sẽ làm tăng một cách giả tạo kích thước mẫu (n), dẫn đến việc các ước lượng thống kê (như sai số chuẩn - standard errors) bị báo cáo thấp hơn thực tế.

6.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, caption = "Bảng giải thích các biến trong dữ liệu")
Bảng giải thích các biến trong dữ liệu
Ten_Bien Ten_TiengAnh Mo_Ta
Năm/Biến Year Biến chỉ năm tài chính (2015–2020).
Tài sản ngắn hạn Current Assets Tổng giá trị tài sản ngắn hạn có thể chuyển đổi thành tiền trong vòng một năm.
Tiền và các khoản tương đương tiền Cash and Cash Equivalents Tiền mặt và các khoản tương đương tiền của doanh nghiệp.
Các khoản đầu tư tài chính ngắn hạn Short-term Financial Investments Các khoản đầu tư tài chính ngắn hạn như cổ phiếu, trái phiếu ngắn hạn.
Tài sản dài hạn Non-current Assets Tổng tài sản dài hạn (nhà xưởng, thiết bị, bất động sản…).
Tổng tài sản Total Assets Tổng giá trị tài sản mà doanh nghiệp sở hữu.
Tổng nợ phải trả Total Liabilities Tổng nghĩa vụ tài chính mà doanh nghiệp phải thanh toán.
Vốn chủ sở hữu Owner’s Equity Nguồn vốn thuộc cổ đông – phần chênh giữa tài sản và nợ phải trả.
Doanh thu thuần về bán hàng
và cung cấp dịch vụ Net Revenue from Sales and Services Doanh thu thuần từ hoạt động bán hàng và cung cấp dịch vụ.
Lợi nhuận gộp về bán hàng
và cung cấp dịch vụ Gross Profit from Sales and Services Khoản lợi nhuận sau khi trừ giá vốn hàng bán.
Doanh thu hoạt động tài chính Financial Income Doanh thu từ các hoạt động tài chính như lãi tiền gửi, cổ tức, đầu tư.
  • Ý nghĩa thống kê: giải thích nội dung và ý nghĩa từng biến mà ta đi phân tích.

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

summary(bc_raw)  
##     Năm/Biến    Tài sản ngắn hạn    Tiền và các khoản tương đương tiền
##  Min.   :2015   Min.   :1.071e+13   Min.   :2.637e+12                 
##  1st Qu.:2017   1st Qu.:1.394e+13   1st Qu.:3.485e+12                 
##  Median :2020   Median :1.934e+13   Median :4.093e+12                 
##  Mean   :2020   Mean   :1.913e+13   Mean   :4.252e+12                 
##  3rd Qu.:2022   3rd Qu.:2.452e+13   3rd Qu.:4.475e+12                 
##  Max.   :2024   Max.   :2.686e+13   Max.   :7.936e+12                 
##  Các khoản đầu tư tài chính ngắn hạn Tài sản dài hạn      Tổng tài sản      
##  Min.   :9.428e+11                   Min.   :7.503e+12   Min.   :1.919e+13  
##  1st Qu.:6.805e+12                   1st Qu.:7.627e+12   1st Qu.:2.210e+13  
##  Median :1.347e+13                   Median :7.830e+12   Median :2.717e+13  
##  Mean   :1.159e+13                   Mean   :8.061e+12   Mean   :2.719e+13  
##  3rd Qu.:1.688e+13                   3rd Qu.:8.361e+12   3rd Qu.:3.270e+13  
##  Max.   :1.941e+13                   Max.   :9.378e+12   Max.   :3.447e+13  
##  Tổng nợ phải trả    Vốn chủ sở hữu     
##  Min.   :6.160e+12   Min.   :1.243e+13  
##  1st Qu.:6.791e+12   1st Qu.:1.484e+13  
##  Median :7.550e+12   Median :2.065e+13  
##  Mean   :7.650e+12   Mean   :1.954e+13  
##  3rd Qu.:8.402e+12   3rd Qu.:2.398e+13  
##  Max.   :9.874e+12   Max.   :2.549e+13  
##  Doanh thu thuần về bán hàng\r\nvà cung cấp dịch vụ
##  Min.   :2.637e+13                                 
##  1st Qu.:2.859e+13                                 
##  Median :3.122e+13                                 
##  Mean   :3.176e+13                                 
##  3rd Qu.:3.484e+13                                 
##  Max.   :3.790e+13                                 
##  Lợi nhuận gộp về bán hàng\r\nvà cung cấp dịch vụ Doanh thu hoạt động tài chính
##  Min.   :7.558e+12                                Min.   :4.186e+11            
##  1st Qu.:8.130e+12                                1st Qu.:6.605e+11            
##  Median :8.683e+12                                Median :9.321e+11            
##  Mean   :8.762e+12                                Mean   :8.880e+11            
##  3rd Qu.:9.261e+12                                3rd Qu.:1.085e+12            
##  Max.   :1.077e+13                                Max.   :1.433e+12
  • Ý nghĩa thống kê: Cung cấp cái nhìn tổng quan đầu tiên về phân phối (distribution) của dữ liệu:

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.

7 Phần 2: Xử lý dữ liệu thô, mã hóa dữ liệu

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

# gọi thư viện
library(readxl)
# đọc lại và xem dữ liệu gốc
bc <- read_excel("D:/thầy Tường/bctcabs.xlsx", sheet = 1)  
names(bc)
##  [1] "Năm/Biến"                                          
##  [2] "Tài sản ngắn hạn"                                  
##  [3] "Tiền và các khoản tương đương tiền"                
##  [4] "Các khoản đầu tư tài chính ngắn hạn"               
##  [5] "Tài sản dài hạn"                                   
##  [6] "Tổng tài sản"                                      
##  [7] "Tổng nợ phải trả"                                  
##  [8] "Vốn chủ sở hữu"                                    
##  [9] "Doanh thu thuần về bán hàng\r\nvà cung cấp dịch vụ"
## [10] "Lợi nhuận gộp về bán hàng\r\nvà cung cấp dịch vụ"  
## [11] "Doanh thu hoạt động tài chính"
  • Ý nghĩa thống kê: đọc lại dữu liệu gốc để bắt đầu xử lý dữ liệu.

7.2 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)
## Warning: package 'janitor' was built under R version 4.4.3
## 
## Attaching package: 'janitor'
## The following objects are masked from 'package:stats':
## 
##     chisq.test, fisher.test
# chuẩn hóa và xem dữ liệu sau khi sửa
bc <- clean_names(bc_raw)  
names(bc)
##  [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 thống kê:

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

names(bc)[1] <- "Nam"    
  • Ý nghĩa thống kê: Giúp dễ dàng hơn trong việc viết và phân tích dữ liệu.

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

bc$Nam <- as.integer(bc$Nam)  
  • Ý nghĩa thống kê: Đâ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. ## Kiểm tra NA tổng thể
# Đếm tổng số ô bị trống (giá trị NA - Not Available) trong toàn bộ bảng dữ liệu
total_na <- sum(is.na(bc_raw)) 
cat('Tổng số giá trị NA:', total_na, "\n")
## Tổng số giá trị NA: 0
  • Ý nghĩa thống kê:Đâ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.

7.5 Lọc bỏ các hàng có toàn NA (nếu có)

bc <- bc[rowSums(is.na(bc)) != ncol(bc), ]   
  • Ý nghĩa thống kê: Làm sạch dữ liệu (data cleaning) an toàn. Các hàng trống không chứa bất kỳ thông tin thống kê nào . Chúng là “nhiễu” (noise) và có thể gây lỗi trong một số thuật toán. Việc loại bỏ chúng giúp dọn dẹp bộ dữ liệu mà không làm mất thông tin vì chúng không đại diện cho một quan sát hợp lệ nào.

7.6 Tạo biến mới: Tỷ lệ lợi nhuận gộp = Lợi nhuận gộp / Doanh thu thuần

# gọi thư viện
library(dplyr)
library(magrittr)
## 
## Attaching package: 'magrittr'
## The following object is masked from 'package:tidyr':
## 
##     extract
# Sử dụng hàm mutate (từ gói dplyr) để tạo một cột mới tên là TyLeLoiNhuanGop, với giá trị được tính bằng cách lấy cột "lợi nhuận gộp về bán hàng và cung cấp dịch vụ" chia cho cột "doanh thu thuần về bán hàng và cung cấp dịch vụ"
bc <- bc %>%
  mutate(TyLeLoiNhuanGop = loi_nhuan_gop_ve_ban_hang_va_cung_cap_dich_vu /
           doanh_thu_thuan_ve_ban_hang_va_cung_cap_dich_vu)  
names(bc)
##  [1] "Nam"                                            
##  [2] "tai_san_ngan_han"                               
##  [3] "tien_va_cac_khoan_tuong_duong_tien"             
##  [4] "cac_khoan_dau_tu_tai_chinh_ngan_han"            
##  [5] "tai_san_dai_han"                                
##  [6] "tong_tai_san"                                   
##  [7] "tong_no_phai_tra"                               
##  [8] "von_chu_so_huu"                                 
##  [9] "doanh_thu_thuan_ve_ban_hang_va_cung_cap_dich_vu"
## [10] "loi_nhuan_gop_ve_ban_hang_va_cung_cap_dich_vu"  
## [11] "doanh_thu_hoat_dong_tai_chinh"                  
## [12] "TyLeLoiNhuanGop"
  • Ý nghĩa thống kê: 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ệ (ratio variable), 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.

7.7 Mã hóa dữ liệu quy mô theo phân loại (tình huống tự đặt)

# Tạo cột mới quy mô. Dùng hàm case_when để gán nhãn "Nhỏ", "Vừa", "Lớn" dựa trên các ngưỡng giá trị của cột tổng tài sản
bc <- bc %>%
  mutate(QuyMo = case_when( tong_tai_san < 1e9 ~ "Nhỏ", tong_tai_san < 5e9 ~ "Vừa", TRUE ~ "Lớn"  ))  
# in bảng thống kê tần suất từng nhóm quy mô
table(bc$QuyMo)
## 
## Lớn 
##  10
  • Ý nghĩa thống kê:

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

cols_to_num <- c("tai_san_ngan_han", "tong_tai_san", "von_chu_so_huu", 
                 "doanh_thu_thuan_ve_ban_hang_va_cung_cap_dich_vu",
                 "loi_nhuan_gop_ve_ban_hang_va_cung_cap_dich_vu")
bc[cols_to_num] <- lapply(bc[cols_to_num], as.numeric)  
  • Ý nghĩa thống kê:

7.9 Kiểm tra lại dữ liệu sau xử lý

str(bc)  
## tibble [10 × 13] (S3: tbl_df/tbl/data.frame)
##  $ Nam                                            : int [1:10] 2015 2016 2017 2018 2019 2020 2021 2022 2023 2024
##  $ tai_san_ngan_han                               : num [1:10] 1.22e+13 1.07e+13 1.37e+13 1.47e+13 1.92e+13 ...
##  $ tien_va_cac_khoan_tuong_duong_tien             : num [1:10] 7.94e+12 3.44e+12 2.64e+12 4.47e+12 4.12e+12 ...
##  $ cac_khoan_dau_tu_tai_chinh_ngan_han            : num [1:10] 9.43e+11 3.17e+12 6.56e+12 7.54e+12 1.24e+13 ...
##  $ tai_san_dai_han                                : num [1:10] 9.38e+12 8.48e+12 8.33e+12 7.68e+12 7.80e+12 ...
##  $ tong_tai_san                                   : num [1:10] 2.16e+13 1.92e+13 2.20e+13 2.24e+13 2.70e+13 ...
##  $ tong_no_phai_tra                               : num [1:10] 7.51e+12 6.76e+12 7.59e+12 6.25e+12 6.89e+12 ...
##  $ von_chu_so_huu                                 : num [1:10] 1.41e+13 1.24e+13 1.44e+13 1.61e+13 2.01e+13 ...
##  $ doanh_thu_thuan_ve_ban_hang_va_cung_cap_dich_vu: num [1:10] 2.71e+13 3.06e+13 3.44e+13 3.59e+13 3.79e+13 ...
##  $ loi_nhuan_gop_ve_ban_hang_va_cung_cap_dich_vu  : num [1:10] 7.56e+12 8.27e+12 8.87e+12 8.08e+12 9.55e+12 ...
##  $ doanh_thu_hoat_dong_tai_chinh                  : num [1:10] 4.19e+11 7.51e+11 5.06e+11 6.30e+11 8.90e+11 ...
##  $ TyLeLoiNhuanGop                                : num [1:10] 0.278 0.27 0.257 0.225 0.252 ...
##  $ QuyMo                                          : chr [1:10] "Lớn" "Lớn" "Lớn" "Lớn" ...
  • Ý nghĩa thống kê:

7.10 Xem mô tả thống kê cơ bản

summary(bc)   
##       Nam       tai_san_ngan_han    tien_va_cac_khoan_tuong_duong_tien
##  Min.   :2015   Min.   :1.071e+13   Min.   :2.637e+12                 
##  1st Qu.:2017   1st Qu.:1.394e+13   1st Qu.:3.485e+12                 
##  Median :2020   Median :1.934e+13   Median :4.093e+12                 
##  Mean   :2020   Mean   :1.913e+13   Mean   :4.252e+12                 
##  3rd Qu.:2022   3rd Qu.:2.452e+13   3rd Qu.:4.475e+12                 
##  Max.   :2024   Max.   :2.686e+13   Max.   :7.936e+12                 
##  cac_khoan_dau_tu_tai_chinh_ngan_han tai_san_dai_han      tong_tai_san      
##  Min.   :9.428e+11                   Min.   :7.503e+12   Min.   :1.919e+13  
##  1st Qu.:6.805e+12                   1st Qu.:7.627e+12   1st Qu.:2.210e+13  
##  Median :1.347e+13                   Median :7.830e+12   Median :2.717e+13  
##  Mean   :1.159e+13                   Mean   :8.061e+12   Mean   :2.719e+13  
##  3rd Qu.:1.688e+13                   3rd Qu.:8.361e+12   3rd Qu.:3.270e+13  
##  Max.   :1.941e+13                   Max.   :9.378e+12   Max.   :3.447e+13  
##  tong_no_phai_tra    von_chu_so_huu     
##  Min.   :6.160e+12   Min.   :1.243e+13  
##  1st Qu.:6.791e+12   1st Qu.:1.484e+13  
##  Median :7.550e+12   Median :2.065e+13  
##  Mean   :7.650e+12   Mean   :1.954e+13  
##  3rd Qu.:8.402e+12   3rd Qu.:2.398e+13  
##  Max.   :9.874e+12   Max.   :2.549e+13  
##  doanh_thu_thuan_ve_ban_hang_va_cung_cap_dich_vu
##  Min.   :2.637e+13                              
##  1st Qu.:2.859e+13                              
##  Median :3.122e+13                              
##  Mean   :3.176e+13                              
##  3rd Qu.:3.484e+13                              
##  Max.   :3.790e+13                              
##  loi_nhuan_gop_ve_ban_hang_va_cung_cap_dich_vu doanh_thu_hoat_dong_tai_chinh
##  Min.   :7.558e+12                             Min.   :4.186e+11            
##  1st Qu.:8.130e+12                             1st Qu.:6.605e+11            
##  Median :8.683e+12                             Median :9.321e+11            
##  Mean   :8.762e+12                             Mean   :8.880e+11            
##  3rd Qu.:9.261e+12                             3rd Qu.:1.085e+12            
##  Max.   :1.077e+13                             Max.   :1.433e+12            
##  TyLeLoiNhuanGop     QuyMo          
##  Min.   :0.2249   Length:10         
##  1st Qu.:0.2607   Class :character  
##  Median :0.2835   Mode  :character  
##  Mean   :0.2774                     
##  3rd Qu.:0.2969                     
##  Max.   :0.3079
  • Ý nghĩa thống kê:

7.11 Xuất dữ liệu đã xử lý ra file mới

write.csv(bc, "D:/thầy Tường/bctcabs_cleaned.csv", row.names = FALSE)    
  • Ý nghĩa thống kê:

8 Phần 3: Thực hiện thống kê cơ bản

8.1 Tính trung bình tổng tài sản

mean_tts <- mean(bc$tong_tai_san, na.rm = TRUE)
cat("Trung bình Tổng tài sản:", mean_tts, "\n")
## Trung bình Tổng tài sản: 2.719308e+13

8.2 Tính trung vị tổng tài sản

median_tts <- median(bc$tong_tai_san, na.rm = TRUE)
cat("Trung vị Tổng tài sản:", median_tts, "\n")
## Trung vị Tổng tài sản: 2.716872e+13

8.3 Tính độ lệch chuẩn Tổng tài sản

sd_tts <- sd(bc$tong_tai_san, na.rm = TRUE)
cat("Độ lệch chuẩn Tổng tài sản:", sd_tts, "\n")
## Độ lệch chuẩn Tổng tài sản: 5.731623e+12

8.4 Tính phương sai Tổng tài sản

var_tts <- var(bc$tong_tai_san, na.rm = TRUE)
cat("Phương sai Tổng tài sản:", var_tts, "\n")
## Phương sai Tổng tài sản: 3.28515e+25

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

cv_tts <- sd_tts / mean_tts
cat("Hệ số biến thiên CV:", cv_tts, "\n")
## Hệ số biến thiên CV: 0.2107751

8.6 Tính giá trị nhỏ nhất và lớn nhất

min(bc$tong_tai_san, na.rm = TRUE)
## [1] 1.919287e+13
max(bc$tong_tai_san, na.rm = TRUE)
## [1] 3.446508e+13

8.7 Mô tả thống kê cho từng biến chính

summary(select(bc, tong_tai_san, tong_no_phai_tra, von_chu_so_huu))
##   tong_tai_san       tong_no_phai_tra    von_chu_so_huu     
##  Min.   :1.919e+13   Min.   :6.160e+12   Min.   :1.243e+13  
##  1st Qu.:2.210e+13   1st Qu.:6.791e+12   1st Qu.:1.484e+13  
##  Median :2.717e+13   Median :7.550e+12   Median :2.065e+13  
##  Mean   :2.719e+13   Mean   :7.650e+12   Mean   :1.954e+13  
##  3rd Qu.:3.270e+13   3rd Qu.:8.402e+12   3rd Qu.:2.398e+13  
##  Max.   :3.447e+13   Max.   :9.874e+12   Max.   :2.549e+13

8.8 Tính tổng Doanh thu thuần theo từng năm

bc %>% group_by(Nam) %>% summarise(Tong_DT = sum(doanh_thu_thuan_ve_ban_hang_va_cung_cap_dich_vu, na.rm = TRUE))

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

bc %>% group_by(Nam) %>%  
 summarise (LoiNhuanTB = mean(loi_nhuan_gop_ve_ban_hang_va_cung_cap_dich_vu, na.rm = TRUE))

8.10 Xác định năm có Tổng tài sản cao nhất

bc %>% filter(tong_tai_san == max(tong_tai_san, na.rm = TRUE))

8.11 Xác định năm có Tổng tài sản thấp nhất

bc %>% filter(tong_tai_san == min(tong_tai_san, na.rm = TRUE))

8.12 Tính tỷ lệ Tổng nợ / Tổng tài sản

bc <- bc %>% mutate(TyLeNoTaiSan = tong_no_phai_tra / tong_tai_san)
bc[, c("Nam", "tong_tai_san", "tong_no_phai_tra", "TyLeNoTaiSan")]

8.13 Tính trung bình tỷ lệ nợ

mean(bc$TyLeNoTaiSan, na.rm = TRUE)
## [1] 0.2871464

8.14 Tính ROA

bc$ROA <- bc$loi_nhuan_gop_ve_ban_hang_va_cung_cap_dich_vu / bc$tong_tai_san * 100
bc[, c("Nam", "ROA")]

8.15 tính ROE

bc$ROE <- bc$loi_nhuan_gop_ve_ban_hang_va_cung_cap_dich_vu / bc$von_chu_so_huu * 100
bc[, c("Nam", "ROE")]

8.16 ROA và ROE trung bình

mean(bc$ROA, na.rm = TRUE)
## [1] 33.17722
mean(bc$ROE, na.rm = TRUE)
## [1] 47.08056

8.17 Tính tương quan giữa Tổng tài sản và Tổng nợ

cor(bc$tong_tai_san, bc$tong_no_phai_tra, use = "complete.obs")
## [1] 0.7445376

8.18 Xác định doanh nghiệp có ROA cao nhất

bc %>% filter(ROA == max(ROA, na.rm = TRUE))

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

mean(bc$loi_nhuan_gop_ve_ban_hang_va_cung_cap_dich_vu, na.rm = TRUE)
## [1] 8.761549e+12

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

bc <- bc %>%
  arrange(Nam) %>%
  mutate(TangTruong_TS = (tong_tai_san - lag(tong_tai_san)) / lag(tong_tai_san) * 100)
bc[, c("Nam", "tong_tai_san", "TangTruong_TS")]

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

mean(bc$TangTruong_TS, na.rm = TRUE)
## [1] 5.418749

8.22 Tạo bảng tổng hợp chỉ tiêu tài chính quan trọng

tonghop <- bc %>%
  summarise(
    TB_TS = mean(tong_tai_san, na.rm = TRUE),
    TB_TN = mean(tong_no_phai_tra, na.rm = TRUE),
    TB_VCSH = mean(von_chu_so_huu, na.rm = TRUE),
    TB_ROA = mean(ROA, na.rm = TRUE),
    TB_ROE = mean(ROE, na.rm = TRUE)  )
print(tonghop)
## # A tibble: 1 × 5
##     TB_TS   TB_TN TB_VCSH TB_ROA TB_ROE
##     <dbl>   <dbl>   <dbl>  <dbl>  <dbl>
## 1 2.72e13 7.65e12 1.95e13   33.2   47.1

9 Phần 4: Vẽ biểu đồ

9.1 BIỂU ĐỒ 1: Đường xu hướng Doanh thu theo năm

install.packages("ggplot2") 
## Warning: package 'ggplot2' is in use and will not be installed
library(ggplot2)  
library(scales)
ggplot(bc, aes(x = Nam, y = tong_tai_san)) +
  geom_line(linewidth = 1.2, color = "blue") +
  geom_point(size = 3, color = "red") +
  geom_text(aes(label = round(tong_tai_san,0)), vjust = -0.6, size = 3.2) +
  labs(title = "Xu hướng Tổng tài sản (2015–2024)",
       x = "Năm", y = "Tổng tài sản (tỷ đồng)") +
  scale_x_continuous(breaks = 2015:2024) +
  scale_y_continuous(labels = comma) +
  theme_minimal(base_size = 13)

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

ggplot(bc, aes(x = Nam, y = tai_san_ngan_han)) +
  geom_area(fill = "lightblue", alpha = 0.4) +
  geom_line(color = "darkblue", linewidth = 1.2) +
  geom_point(color = "red", size = 3) +
  geom_text(aes(label = round(tai_san_ngan_han,0)), vjust = -0.6) +
  labs(title = "Tài sản ngắn hạn (2015–2024)", x = "Năm", y = "Giá trị (tỷ đồng)") +
  scale_x_continuous(breaks = 2015:2024) +
  theme_light()

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

ggplot(bc, aes(x = Nam, y = tai_san_dai_han)) +
  geom_col(fill = "steelblue") +
  geom_text(aes(label = round(tai_san_dai_han,0)), vjust = -0.6, color = "black") +
  geom_line(aes(y = tai_san_dai_han), color = "red", linewidth = 1) +
  labs(title = "Tài sản dài hạn (2015–2024)", x = "Năm", y = "Tỷ đồng") +
  scale_x_continuous(breaks = 2015:2024) +
  theme_bw()

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

library(scales)
ggplot(bc, aes(x = Nam, y = TyLeNoTaiSan)) +
  geom_area(fill = "pink", alpha = 0.5) +
  geom_line(color = "darkred", linewidth = 1.2) +
  geom_point(color = "black", size = 3) +
  geom_text(aes(label = round(TyLeNoTaiSan,2)), vjust = -0.6) +
  labs(title = "Tỷ lệ Nợ/Tổng tài sản (2015–2024)",
       x = "Năm", y = "Tỷ lệ (%)") +
  scale_x_continuous(breaks = 2015:2024) +
  scale_y_continuous(labels = percent_format(scale = 1)) +
  theme_minimal()

9.5 Biểu đồ 5: Doanh thu thuần theo năm

ggplot(bc, aes(x = Nam, y = doanh_thu_thuan_ve_ban_hang_va_cung_cap_dich_vu)) +
  geom_line(color = "darkgreen", linewidth = 1.2) +
  geom_point(color = "orange", size = 3) +
  geom_text(aes(label = round(doanh_thu_thuan_ve_ban_hang_va_cung_cap_dich_vu,0)), vjust = -0.6) +
  labs(title = "Doanh thu thuần (2015–2024)", x = "Năm", y = "Tỷ đồng") +
  scale_x_continuous(breaks = 2015:2024) +
  theme_classic()

9.6 Biểu đồ 6: Lợi nhuận gộp

ggplot(bc, aes(x = Nam, y = loi_nhuan_gop_ve_ban_hang_va_cung_cap_dich_vu)) +
  geom_col(fill = "seagreen3") +
  geom_text(aes(label = round(loi_nhuan_gop_ve_ban_hang_va_cung_cap_dich_vu, 0)), vjust = -0.6) +
  labs(title = "Lợi nhuận gộp qua các năm", x = "Năm", y = "Tỷ đồng") +
  scale_x_continuous(breaks = 2015:2024) +
  theme_minimal()

9.7 Biểu đồ 7. ROA theo năm

ggplot(bc, aes(x = Nam, y = ROA)) +
  geom_line(color = "purple", linewidth = 1.2) +
  geom_point(color = "darkorange", size = 3) +
  geom_text(aes(label = round(ROA, 2)), vjust = -0.6) +
  labs(title = "Tỷ suất sinh lời trên tài sản (ROA)", x = "Năm", y = "%") +
  scale_x_continuous(breaks = 2015:2024) +
  theme_bw()

9.8 Biểu đồ 8: ROE theo năm

ggplot(bc, aes(x = Nam, y = ROE)) +
  geom_line(color = "darkcyan", linewidth = 1.2) +
  geom_point(color = "black", size = 3) +
  geom_text(aes(label = round(ROE, 2)), vjust = -0.6) +
  labs(title = "Tỷ suất sinh lời trên vốn chủ sở hữu (ROE)", x = "Năm", y = "%") +
  scale_x_continuous(breaks = 2015:2024) +
  theme_light()

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

bc_clean <- na.omit(bc)
ggplot(bc_clean, aes(x = Nam, y = TangTruong_TS)) +
  geom_col(fill = "darkseagreen") +
  geom_text(aes(label = paste0(round(TangTruong_TS,1), "%")), vjust = -0.6) +
  labs(title = "Tăng trưởng tài sản (%)", x = "Năm", y = "%") +
  scale_x_continuous(breaks = 2015:2024) +
  theme_minimal()

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

ggplot(bc, aes(x = Nam, y = TyLeLoiNhuanGop)) +
  geom_line(color = "red", linewidth = 1.2) +
  geom_point(color = "black", size = 3) +
  geom_text(aes(label = paste0(round(TyLeLoiNhuanGop,1), "%")), vjust = -0.6) +
  labs(title = "Tỷ lệ lợi nhuận gộp (%)", x = "Năm", y = "%") +
  scale_x_continuous(breaks = 2015:2024) +
  theme_classic()

9.11 Biểu đồ 11: ROA vs ROE

ggplot(bc, aes(x = Nam)) +
  geom_line(aes(y = ROA, color = "ROA"), linewidth = 1.2) +
  geom_line(aes(y = ROE, color = "ROE"), linewidth = 1.2, linetype = "dashed") +
  geom_point(aes(y = ROA, color = "ROA"), size = 3) +
  geom_point(aes(y = ROE, color = "ROE"), size = 3) +
  labs(title = "So sánh ROA và ROE (2015–2024)",
       x = "Năm", y = "Tỷ lệ (%)",
       subtitle = "Hai chỉ tiêu phản ánh hiệu quả sinh lời khác nhau",
       color = "Chỉ tiêu") +
  scale_x_continuous(breaks = 2015:2024) +
  theme_minimal(base_size = 13)

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

ggplot(bc, aes(x = Nam)) +
  geom_col(aes(y = TyLeNoTaiSan * 100), fill = "lightblue", alpha = 0.6) +
  geom_line(aes(y = ROE, color = "ROE"), linewidth = 1.2) +
  geom_point(aes(y = ROE, color = "ROE"), size = 3) +
  scale_y_continuous(sec.axis = sec_axis(~ ., name = "Tỷ lệ nợ (%)")) +
  labs(title = "Ảnh hưởng của Tỷ lệ nợ đến ROE (2015–2024)",
       subtitle = "Tỷ lệ nợ tăng cao thường kéo ROE biến động mạnh",
       x = "Năm", y = "ROE (%)", color = "") +
  scale_x_continuous(breaks = 2015:2024) +
  theme_light()

9.13 Biểu đồ 13: Doanh thu và lợi nhuận gộp

ggplot(bc, aes(x = Nam)) +
  geom_line(aes(y = doanh_thu_thuan_ve_ban_hang_va_cung_cap_dich_vu/1000, color = "Doanh thu"), linewidth = 1.2) +
  geom_line(aes(y = loi_nhuan_gop_ve_ban_hang_va_cung_cap_dich_vu/1000, color = "Lợi nhuận gộp"), linewidth = 1.2, linetype = "dashed") +
  geom_point(aes(y = doanh_thu_thuan_ve_ban_hang_va_cung_cap_dich_vu/1000, color = "Doanh thu"), size = 3) +
  geom_point(aes(y = loi_nhuan_gop_ve_ban_hang_va_cung_cap_dich_vu/1000, color = "Lợi nhuận gộp"), size = 3) +
  labs(title = "Doanh thu và Lợi nhuận gộp (2015–2024)",
       subtitle = "Quan hệ giữa doanh thu thuần và lợi nhuận gộp",
       x = "Năm", y = "Giá trị (nghìn tỷ đồng)", color = "Chỉ tiêu") +
  scale_x_continuous(breaks = 2015:2024) +
  theme_minimal()

9.14 Biểu đồ 14: Tài sản vs Vốn chủ sở hữu

ggplot(bc, aes(x = Nam)) +
  geom_line(aes(y = tong_tai_san/1000, color = "Tổng tài sản"), linewidth = 1.2) +
  geom_line(aes(y = von_chu_so_huu/1000, color = "Vốn chủ sở hữu"), linewidth = 1.2, linetype = "dashed") +
  geom_point(aes(y = tong_tai_san/1000, color = "Tổng tài sản"), size = 3) +
  geom_point(aes(y = von_chu_so_huu/1000, color = "Vốn chủ sở hữu"), size = 3) +
  labs(title = "Tổng tài sản và Vốn chủ sở hữu (2015–2024)",
       x = "Năm", y = "Giá trị (nghìn tỷ đồng)", color = "Chỉ tiêu") +
  scale_x_continuous(breaks = 2015:2024) +
  theme_bw()

9.15 Biểu đồ 15: Tài sản ngắn hạn vs dài hạn

ggplot(bc, aes(x = Nam)) +
  geom_area(aes(y = tai_san_ngan_han/1000, fill = "Ngắn hạn"), alpha = 0.4) +
  geom_area(aes(y = tai_san_dai_han/1000, fill = "Dài hạn"), alpha = 0.4) +
  geom_line(aes(y = tai_san_ngan_han/1000, color = "Ngắn hạn"), linewidth = 1.2) +
  geom_line(aes(y = tai_san_dai_han/1000, color = "Dài hạn"), linewidth = 1.2) +
  labs(title = "Tài sản ngắn hạn vs dài hạn (2015–2024)",
       x = "Năm", y = "Giá trị (nghìn tỷ đồng)", color = "Loại tài sản") +
  scale_x_continuous(breaks = 2015:2024) +
  theme_minimal()

9.16 Biểu đồ 16: Ảnh hưởng của Quy mô đến Tổng tài sản

ggplot(bc, aes(x = Nam, y = tong_tai_san/1000, color = QuyMo)) +
  geom_line(linewidth = 1.2) +
  geom_point(size = 3) +
  labs(title = "Ảnh hưởng của Quy mô doanh nghiệp đến Tổng tài sản",
       subtitle = "Nhóm quy mô Lớn - Vừa - Nhỏ",
       x = "Năm", y = "Tổng tài sản (nghìn tỷ đồng)", color = "Quy mô") +
  scale_x_continuous(breaks = 2015:2024) +
  theme_light()

9.17 Biểu đồ 17: Quy mô và Doanh thu

ggplot(bc, aes(x = Nam, y = doanh_thu_thuan_ve_ban_hang_va_cung_cap_dich_vu/1000, color = QuyMo)) +
  geom_line(linewidth = 1.2) +
  geom_point(size = 3) +
  labs(title = "Ảnh hưởng của Quy mô đến Doanh thu thuần (2015–2024)",
       x = "Năm", y = "Doanh thu (nghìn tỷ đồng)", color = "Quy mô") +
  scale_x_continuous(breaks = 2015:2024) +
  theme_minimal()

9.18 Biểu đồ 18:Quy mô và ROA

ggplot(bc, aes(x = Nam, y = ROA, color = QuyMo)) +
  geom_line(linewidth = 1.2) +
  geom_point(size = 3) +
  labs(title = "Ảnh hưởng của Quy mô doanh nghiệp đến ROA",
       x = "Năm", y = "ROA (%)", color = "Quy mô") +
  scale_x_continuous(breaks = 2015:2024) +
  theme_classic()

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

ggplot(bc, aes(x = Nam)) +
  geom_col(aes(y = TyLeNoTaiSan * 100), fill = "skyblue", alpha = 0.6) +
  geom_line(aes(y = ROA, color = "ROA"), linewidth = 1.2) +
  geom_point(aes(y = ROA, color = "ROA"), size = 3) +
  labs(title = "Tỷ lệ nợ và ROA (2015–2024)",
       subtitle = "Phản ánh tác động của đòn bẩy tài chính đến hiệu quả sinh lời",
       x = "Năm", y = "Tỷ lệ (%)", color = "") +
  scale_x_continuous(breaks = 2015:2024) +
  theme_minimal()

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

bc_clean <- na.omit(bc)
ggplot(bc_clean, aes(x = Nam)) +
  geom_col(aes(y = TangTruong_TS), fill = "lightgreen", alpha = 0.6) +
  geom_line(aes(y = ROE, color = "ROE"), linewidth = 1.2) +
  geom_point(aes(y = ROE, color = "ROE"), size = 3) +
  labs(title = "Tăng trưởng tài sản và ROE (2015–2024)",
       subtitle = "Mức tăng tài sản thường ảnh hưởng đến hiệu suất sinh lời vốn chủ",
       x = "Năm", y = "Tỷ lệ (%)", color = "") +
  scale_x_continuous(breaks = 2015:2024) +
  theme_light()

9.21 Biểu đồ 21: Phân phối ROA

ggplot(bc, aes(x = ROA)) +
  geom_histogram(fill = "skyblue", color = "black", bins = 8) +
  labs(title = "Phân phối ROA", x = "ROA (%)", y = "Tần suất") +
  theme_light()

9.22 Biểu đồ 22: Phân phối ROE

ggplot(bc, aes(x = ROE)) +
  geom_density(fill = "pink", alpha = 0.5) +
  labs(title = "Phân phối ROE", x = "ROE (%)", y = "Mật độ") +
  theme_minimal()

9.23 Biểu đồ 23: Boxplot ROA theo năm

ggplot(bc, aes(x = factor(Nam), y = ROA)) +
  geom_boxplot(fill = "#9be09b", color = "skyblue", width = 0.6) +
  labs(title = "Phân bố ROA theo năm", x = "Năm", y = "ROA (%)") +
  theme_minimal(base_size = 13) +
  theme( panel.border = element_rect(color = "black", fill = NA, linewidth = 0.8))

9.24 Biểu đồ 24: Doanh thu và Lợi nhuận gộp (song hành)

ggplot(bc) +
  geom_line(aes(x = Nam, y = doanh_thu_thuan_ve_ban_hang_va_cung_cap_dich_vu, color = "Doanh thu")) +
  geom_line(aes(x = Nam, y = loi_nhuan_gop_ve_ban_hang_va_cung_cap_dich_vu, color = "Lợi nhuận")) +
  scale_color_manual(values = c("Doanh thu" = "blue", "Lợi nhuận" = "red")) +
  labs(title = "Doanh thu và Lợi nhuận gộp (2015–2024)", x = "Năm", y = "Tỷ đồng", color = "Chỉ tiêu") +
  scale_x_continuous(breaks = 2015:2024) +
  theme_minimal()

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

ggplot(bc) +
  geom_line(aes(x = Nam, y = tong_tai_san/1000, color = "Tổng tài sản"), linewidth = 1.2) +
  geom_line(aes(x = Nam, y = doanh_thu_thuan_ve_ban_hang_va_cung_cap_dich_vu/1000, color = "Doanh thu"), linewidth = 1.2) +
  geom_line(aes(x = Nam, y = loi_nhuan_gop_ve_ban_hang_va_cung_cap_dich_vu/1000, color = "Lợi nhuận gộp"), linewidth = 1.2) +
  labs(title = "Tổng hợp: Tài sản, Doanh thu, Lợi nhuận (2015–2024)",
       subtitle = "Đơn vị: nghìn tỷ đồng", x = "Năm", y = "Giá trị (nghìn tỷ đồng)", color = "Chỉ tiêu") +
  scale_x_continuous(breaks = 2015:2024) +
  theme_minimal()