knitr::opts_chunk$set(echo = TRUE, warning = FALSE, message = FALSE)
library(readxl)
library(dplyr)
library(tidyr)
library(ggplot2)
library(knitr)
library(stringr)
library(stringi)
library(kableExtra)
library(gridExtra)
library(flextable)
library(grid)
theme_set(theme_minimal(base_family = "Arial"))

PHẦN 1: GIỚI THIỆU VỀ BỘ DỮ LIỆU TIỂU ĐƯỜNG

Nội dung 1: Giới thiệu về bộ dữ liệu

1.1 Giới thiệu tổng quan

Bộ dữ liệu “Diabetes Clinical Data” được thu thập từ nền tảng Kaggle, tổng hợp từ hệ thống ghi nhận thông tin lâm sàng của bệnh nhân. Dữ liệu phản ánh các đặc điểm nhân khẩu học, chỉ số sinh học, thói quen sinh hoạt và tình trạng bệnh lý, với mục tiêu phục vụ nghiên cứu về các yếu tố ảnh hưởng đến bệnh tiểu đường và mối liên hệ giữa lối sống, đặc điểm cá nhân và sức khỏe. Bộ dữ liệu gồm gần 100.000 bản ghi, mỗi bản ghi đại diện cho một bệnh nhân với các biến như tuổi, giới tính, BMI, HbA1c, đường huyết, hút thuốc, bệnh tim, huyết áp và các ghi chú lâm sàng khác.Dữ liệu kết hợp cả biến định tính và định lượng, cho phép phân tích thống kê mô tả, khám phá mối quan hệ giữa các yếu tố sức khỏe và ứng dụng mô hình học máy để dự đoán nguy cơ mắc tiểu đường. Nhờ tính đa dạng và thực tiễn, bộ dữ liệu này vừa có giá trị học thuật, vừa mang ý nghĩa ứng dụng lâm sàng và cộng đồng.

1.2. Kích thước bộ dữ liệu

dim(data)
## [1] 100000     17

Yếu tố kĩ thuật

  • Hàm dim() trả về số hàng và số cột của bộ dữ liệu.

Kết luận

Bộ dữ liệu gồm 100.000 quan sát và 17 biến, cho thấy đây là một tập dữ liệu lớn, giàu thông tin, đủ để thực hiện các phân tích thống kê mô tả và mô hình dự đoán với độ tin cậy cao. Quy mô này giúp kết quả phân tích có tính đại diện tốt và giảm sai số ngẫu nhiên.

1.3. Tên các biến

names(data)
##  [1] "year"                 "gender"               "age"                 
##  [4] "location"             "race:AfricanAmerican" "race:Asian"          
##  [7] "race:Caucasian"       "race:Hispanic"        "race:Other"          
## [10] "hypertension"         "heart_disease"        "smoking_history"     
## [13] "bmi"                  "hbA1c_level"          "blood_glucose_level" 
## [16] "diabetes"             "clinical_notes"
names_tbl <- matrix(names(data), ncol = 3, byrow = TRUE)
knitr::kable(names_tbl, col.names = c("Cột 1", "Cột 2", "Cột 3"), align = "l")
Cột 1 Cột 2 Cột 3
year gender age
location race:AfricanAmerican race:Asian
race:Caucasian race:Hispanic race:Other
hypertension heart_disease smoking_history
bmi hbA1c_level blood_glucose_level
diabetes clinical_notes year

Yếu tố kĩ thuật

  • Hàm names() dùng để liệt kê toàn bộ tên các biến (cột) có trong khung dữ liệu data.

Kết luận

Các biến được tổ chức hợp lý, bao quát thông tin nhân khẩu học (age, gender, location), đặc điểm chủng tộc (race.*), chỉ số y tế (bmi, hbA1c_level, blood_glucose_level, hypertension, heart_disease) và hành vi sức khỏe (smoking_history), cùng biến diabetes thể hiện tình trạng bệnh. Cấu trúc này cho phép phân tích cả yếu tố cá nhân lẫn lối sống ảnh hưởng đến nguy cơ tiểu đường.

1.4 Ý nghĩa của các biến

Tên biến Ý nghĩa
year Năm ghi nhận dữ liệu của bệnh nhân
gender Giới tính của người tham gia (Nam/Nữ)
age Tuổi của người tham gia (năm)
location Khu vực sinh sống hoặc nơi khám bệnh
race:AfricanAmerican Thuộc nhóm chủng tộc Người Mỹ gốc Phi (1: Có, 0: Không)
race:Asian Thuộc nhóm chủng tộc Châu Á (1: Có, 0: Không)
race:Caucasian Thuộc nhóm chủng tộc Da trắng (1: Có, 0: Không)
race:Hispanic Thuộc nhóm chủng tộc gốc Tây Ban Nha (1: Có, 0: Không)
race:Other Thuộc nhóm chủng tộc khác (1: Có, 0: Không)
hypertension Tình trạng tăng huyết áp (1: Có, 0: Không)
heart_disease Bệnh tim mạch (1: Có, 0: Không)
smoking_history Tiền sử hút thuốc (Never, Former, Current, Unknown)
bmi Chỉ số khối cơ thể (BMI) - đo độ béo cơ thể (kg/m²)
hbA1c_level Chỉ số HbA1c - phản ánh đường huyết trung bình 3 tháng gần nhất (%)
blood_glucose_level Nồng độ đường huyết hiện tại (mg/dL)
diabetes Tình trạng tiểu đường (1: Có, 0: Không)
clinical_notes Ghi chú lâm sàng hoặc nhận xét của bác sĩ

Yếu tố kĩ thuật

  • Dùng data.frame(): tạo một bảng gồm hai cột dùng data.frame(): tạo một bảng gồm hai cột tên biến và ý nghĩa
  • stringsAsFactors = FALSE: giữ dữ liệu dạng chữ (tránh tự động chuyển thành dạng số mã hóa).
  • kable(): hiển thị bảng vừa tạo ra theo dạng rõ ràng, dễ đọc.

Kết luận

Các biến được định nghĩa rõ ràng và bao quát các khía cạnh quan trọng trong nghiên cứu tiểu đường. Nhóm age, gender, race.*, location mô tả đặc điểm cá nhân; nhóm bmi, hbA1c_level, blood_glucose_level, hypertension, heart_disease thể hiện tình trạng sức khỏe; smoking_history phản ánh hành vi lối sống; và diabetes là biến mục tiêu cho phân tích dự đoán. Cấu trúc biến rõ ràng, dễ hiểu và phù hợp cho cả mô tả thống kê lẫn mô hình dự báo.

1.5 Kiểm tra kiểu dữ liệu của các biến

df <- read_excel("D:/data.xlsx")
str(df)
## tibble [100,000 × 17] (S3: tbl_df/tbl/data.frame)
##  $ year                : num [1:100000] 2020 2015 2015 2015 2016 ...
##  $ gender              : chr [1:100000] "Female" "Female" "Male" "Male" ...
##  $ age                 : num [1:100000] 32 29 18 41 52 66 49 15 51 42 ...
##  $ location            : chr [1:100000] "Alabama" "Alabama" "Alabama" "Alabama" ...
##  $ race:AfricanAmerican: num [1:100000] 0 0 0 0 1 0 0 0 1 0 ...
##  $ race:Asian          : num [1:100000] 0 1 0 0 0 0 0 0 0 0 ...
##  $ race:Caucasian      : num [1:100000] 0 0 0 1 0 1 1 0 0 1 ...
##  $ race:Hispanic       : num [1:100000] 0 0 0 0 0 0 0 0 0 0 ...
##  $ race:Other          : num [1:100000] 1 0 1 0 0 0 0 1 0 0 ...
##  $ hypertension        : num [1:100000] 0 0 0 0 0 0 0 0 0 0 ...
##  $ heart_disease       : num [1:100000] 0 0 0 0 0 0 0 0 0 0 ...
##  $ smoking_history     : chr [1:100000] "never" "never" "never" "never" ...
##  $ bmi                 : num [1:100000] 27.3 19.9 23.8 27.3 23.8 ...
##  $ hbA1c_level         : num [1:100000] 5 5 4.8 4 6.5 5.7 5.7 5 6 5.7 ...
##  $ blood_glucose_level : num [1:100000] 100 90 160 159 90 159 80 155 100 160 ...
##  $ diabetes            : num [1:100000] 0 0 0 0 0 0 0 0 0 0 ...
##  $ clinical_notes      : chr [1:100000] "Overweight, advised dietary and exercise modifications." "Healthy BMI range." "Young patient, generally lower risk but needs lifestyle assessment. Healthy BMI range. Elevated blood glucose l"| __truncated__ "Overweight, advised dietary and exercise modifications. Elevated blood glucose levels, potential diabetes concern." ...

Yếu tố kĩ thuật

  • str(df): hiển thị cấu trúc tổng quát của bộ dữ liệu.

Kết luận

Bộ dữ liệu gồm hai kiểu biến chính: số thực (num) và chuỗi ký tự (chr). Các biến year, age, bmi, hbA1c_level, blood_glucose_level, hypertension, heart_disease và diabetes ở dạng số thực, phản ánh giá trị đo lường hoặc trạng thái sức khỏe. Ngược lại, gender, location, smoking_history và clinical_notes là chuỗi ký tự, thể hiện thông tin mô tả. Nhóm biến race (AfricanAmerican, Asian, Caucasian, Hispanic, Other) nhận giá trị 0 hoặc 1, tuy là số nhưng mang ý nghĩa nhị phân – cho biết bệnh nhân có hoặc không thuộc nhóm chủng tộc đó.

1.6 Số biến định lượng

sum(sapply(data, is.numeric)) 
## [1] 13

1.7 Số biến định tính

sum(sapply(data, is.character) | sapply(data, is.factor))  
## [1] 4

Yếu tố kĩ thuật1.6 1.7

  • sapply(data, is.numeric): kiểm tra từng cột trong dữ liệu xem có phải là biến định lượng (số) hay không.
  • sapply(data, is.character) và sapply(data, is.factor) dùng để xác định các biến định tính (chuỗi hoặc phân loại).
  • Hàm sum(…): cộng lại số lượng các cột thỏa điều kiện trên, cho biết bao nhiêu biến định lượng, định tính trong bảng.
  • Dấu “|” nghĩa là “hoặc”: nếu biến là chuỗi hoặc phân loại thì sẽ được tính.

Kết luận 1.6 1.7

Bộ dữ liệu “Diabetes Clinical Data” gồm 13 biến định lượng và 4 biến định tính. Phần lớn các biến là số học, phản ánh thông tin như năm, tuổi, chỉ số sinh học (bmi, hbA1c_level, blood_glucose_level) và các biến nhị phân về tình trạng bệnh hoặc chủng tộc (hypertension, heart_disease, race:*, diabetes). Bốn biến định tính gồm giới tính (gender), địa điểm (location), lịch sử hút thuốc (smoking_history) và ghi chú lâm sàng (clinical_notes).

1.8 Kiểm tra dòng trùng lặp

sum(duplicated(data))
## [1] 14

Yếu tố kĩ thuật

  • Hàm duplicated() kiểm tra các dòng trùng lặp, hàm sum() đếm tổng số dòng bị trùng.

Kết luận

Bộ dữ liệu có 14 dòng trùng lặp trên tổng số 100.000 quan sát, chiếm 0.014%. Tỷ lệ này rất nhỏ, cho thấy dữ liệu có tính duy nhất cao và chất lượng tốt.

1.9 Kiểm tra giá trị bị thiếu

any(is.na(data))  
## [1] FALSE
colSums(is.na(data)) 
##                 year               gender                  age 
##                    0                    0                    0 
##             location race:AfricanAmerican           race:Asian 
##                    0                    0                    0 
##       race:Caucasian        race:Hispanic           race:Other 
##                    0                    0                    0 
##         hypertension        heart_disease      smoking_history 
##                    0                    0                    0 
##                  bmi          hbA1c_level  blood_glucose_level 
##                    0                    0                    0 
##             diabetes       clinical_notes 
##                    0                    0

Yếu tố kĩ thuật

  • Hàm is.na() xác định các ô bị thiếu (NA), hàm any() kiểm tra xem dữ liệu có ít nhất một giá trị thiếu hay không.
  • Hàm colSums() đếm số lượng giá trị thiếu trong từng cột.

Kết luận

Kết quả kiểm tra cho thấy hàm any(is.na(data)) trả về FALSE, và tổng số giá trị NA ở tất cả các cột (colSums(is.na(data))) đều bằng 0.

1.10 Phân tích phân phối các biến định lượng.

vars <- c("age", "bmi", "hbA1c_level", "blood_glucose_level")
plots <- list()
for (v in vars) {if (v %in% names(data)) {plot_title <- paste("Phân phối của", v)
    if (v == "blood_glucose_level") {plot_title <- "Phân phối Glucose"}
    p <- ggplot(data, aes(x = .data[[v]])) +
      geom_histogram(fill = "skyblue", color = "black", bins = 30) +
      labs(title = plot_title,x = v,y = "Tần suất") +
      theme_minimal(base_size = 10) + 
      theme(plot.title = element_text(size = 11, face = "bold", hjust = 0.5),plot.margin = unit(c(0.2, 0.2, 0.2, 0.2), "cm") )
    plots[[v]] <- p} else {message("Cột ", v, " không tồn tại trong dataframe!")}}
if (length(plots) > 0) {combined_plot <- gridExtra::arrangeGrob(grobs = plots, nrow = 2, ncol = 2, widths = c(2, 2), padding = unit(3, "line"))
  grid::grid.draw(combined_plot)} else {message("Không có cột nào hợp lệ để vẽ biểu đồ.")}

Yếu tố kĩ thuật

  • vars: Tạo danh sách các biến cần vẽ biểu đồ, plots <- list(): Khởi tạo danh sách rỗng để lưu biểu đồ, for (v in vars): Lặp qua từng biến trong danh sách.
  • if (v %in% names(data)): Kiểm tra xem biến có tồn tại trong dữ liệu không.
  • ggplot(data, aes(x = .data[[v]])) + geom_histogram(): Vẽ biểu đồ tần suất cho từng biến.
  • labs(): Đặt tiêu đề và tên trụtrục, theme_minimal() + theme(): Tùy chỉnh giao diện, căn giữa tiêu đề, plots[[v]] <- p: Lưu từng biểu đồ vào danh sách.
  • arrangeGrob() + grid.draw(): Sắp xếp và hiển thị tất cả biểu đồ (2 hàng × 2 cột).

Kết luận

Kết quả phân tích cho thấy bốn biến định lượng có đặc điểm phân phối khác nhau. Biến age phân bố khá đồng đều, phản ánh mẫu dữ liệu bao gồm người ở nhiều nhóm tuổi. Biến bmi có xu hướng lệch phải và tập trung quanh mức 25, cho thấy phần lớn đối tượng có chỉ số BMI ở mức bình thường hoặc hơi thừa cân. Biến hbA1c_level chủ yếu tập trung quanh giá trị 6, thể hiện phần lớn người tham gia có mức đường huyết tương đối ổn định. Trong khi đó, blood_glucose_level phân bố không đều và xuất hiện một số giá trị cao bất thường, gợi ý khả năng tồn tại các trường hợp đường huyết tăng cao trong mẫu dữ liệu.

NỘI DUNG 2: XỬ LÍ DỮ LIỆU THÔ VÀ CHUẨN HÓA DỮ LIỆU

2.1 Xử lí dữ liệu trùng lặp

sum(duplicated(data))  
## [1] 14
data <- data %>% distinct()
sum(duplicated(data))  
## [1] 0

Yếu tố kĩ thuật

  • Hàm duplicated(data) kiểm tra các dòng trùng lặp trong bộ dữ liệu, hàm sum() đếm tổng số dòng bị trùng.
  • Hàm distinct()loại bỏ các dòng trùng lặp và giữ lại duy nhất một bản ghi cho mỗi trường hợp.

Kết luận

Kết quả kiểm tra cho thấy tập dữ liệu ban đầu có 14 bản ghi trùng lặp, nhóm đã sử dụng hàm distinct() trong R để loại bỏ toàn bộ các dòng trùng. Sau xử lý, phép kiểm tra lại cho kết quả 0 bản ghi trùng lặp, chứng tỏ quá trình làm sạch dữ liệu đạt hiệu quả.

2.2. Chuẩn hóa giá trị chữ trong các biến định tính

cat_vars <- c("gender", "location", "smoking_history", "clinical_notes")
data <- data %>%
mutate(smoking_history = str_to_lower(str_trim(smoking_history)),clinical_notes = str_squish(clinical_notes))
head(data[c("smoking_history", "clinical_notes")])
## # A tibble: 6 × 2
##   smoking_history clinical_notes                                                
##   <chr>           <chr>                                                         
## 1 never           Overweight, advised dietary and exercise modifications.       
## 2 never           Healthy BMI range.                                            
## 3 never           Young patient, generally lower risk but needs lifestyle asses…
## 4 never           Overweight, advised dietary and exercise modifications. Eleva…
## 5 never           Healthy BMI range. High HbA1c level, indicative of diabetes o…
## 6 not current     Elderly patient with increased risk of chronic conditions. Ov…

Yếu tố kĩ thuật

  • Xác định các biến định tính: gender, location, smoking_history, clinical_notes.
  • str_to_lower() chuyển toàn bộ chữ thành chữ thường, str_trim() loại bỏ khoảng trắng đầu và cuối chuỗi, str_squish() loại bỏ khoảng trắng thừa giữa các từ.
  • Sử dụng mutate() để cập nhật dữ liệu sau khi chuẩn hóa, head()để đọc kết quả dữ liệu.

Kết luận

Sau khi chuẩn hóa, hai biến smoking_history và clinical_notes đã được đồng bộ về mặt hình thức và loại bỏ hoàn toàn các lỗi định dạng.

2.3. Chuẩn hóa và chuyển đổi kiểu dữ liệu của nhóm chủng tộc race:*

binary_vars <- c("race:AfricanAmerican", "race:Asian", "race:Caucasian",
"race:Hispanic", "race:Other")
str(data[binary_vars])
## tibble [99,986 × 5] (S3: tbl_df/tbl/data.frame)
##  $ race:AfricanAmerican: num [1:99986] 0 0 0 0 1 0 0 0 1 0 ...
##  $ race:Asian          : num [1:99986] 0 1 0 0 0 0 0 0 0 0 ...
##  $ race:Caucasian      : num [1:99986] 0 0 0 1 0 1 1 0 0 1 ...
##  $ race:Hispanic       : num [1:99986] 0 0 0 0 0 0 0 0 0 0 ...
##  $ race:Other          : num [1:99986] 1 0 1 0 0 0 0 1 0 0 ...
data <- data %>%
mutate(across(all_of(binary_vars), ~ as.factor(.)))
str(data[binary_vars])
## tibble [99,986 × 5] (S3: tbl_df/tbl/data.frame)
##  $ race:AfricanAmerican: Factor w/ 2 levels "0","1": 1 1 1 1 2 1 1 1 2 1 ...
##  $ race:Asian          : Factor w/ 2 levels "0","1": 1 2 1 1 1 1 1 1 1 1 ...
##  $ race:Caucasian      : Factor w/ 2 levels "0","1": 1 1 1 2 1 2 2 1 1 2 ...
##  $ race:Hispanic       : Factor w/ 2 levels "0","1": 1 1 1 1 1 1 1 1 1 1 ...
##  $ race:Other          : Factor w/ 2 levels "0","1": 2 1 2 1 1 1 1 2 1 1 ...

Yếu tố kĩ thuật

  • binary_vars: chứa tên các biến nhị phân cần xử lý
  • str() kiểm tra cấu trúc kiểu dữ liệu của các biến trước và sau khi chuyển đổi.
  • Hàm mutate() kết hợp across() giúp chuyển đổi hàng loạt biến cùng lúc.
  • Biểu thức ~ as.factor(.) biến mỗi biến từ dạng số (0, 1) sang dạng factor (2 mức: 0 và 1).

Kết Luận

Sau khi thực thi mã, năm biến nhị phân đã được chuyển từ dạng số sang factor, giúp R hiểu đúng bản chất phân loại thay vì coi là giá trị định lượng. Nhờ đó, các giá trị “0” và “1” được diễn giải rõ ràng thành “Không” và “Có”, đảm bảo tính chính xác trong phân tích, trực quan hóa

2.4 Phát hiện giá trị ngoại lai cho các biến định lượng

num_vars <- c("age", "bmi", "hbA1c_level", "blood_glucose_level")
plots <- lapply(num_vars, function(v) {if (v %in% names(data)) { title <- paste("Ngoại lai -", if (v == "blood_glucose_level") "Glucose" else v)
    ggplot(data, aes(y = .data[[v]])) +
      geom_boxplot(fill = "lightblue", color = "darkblue", outlier.shape = 1) +
      labs(title = title, y = v, x = "") +
      theme_minimal(base_size = 10) + theme( plot.title = element_text(size = 11, face = "bold", hjust = 0.5), plot.margin = unit(c(0.1, 0.1, 0.1, 0.1), "cm")) +
      coord_flip()
  } else {
    NULL
  }})
plots <- Filter(Negate(is.null), plots)
if (length(plots) > 0) {grid.draw(do.call(gridExtra::arrangeGrob, c(plots, nrow = 2, ncol = 2)))
}

Yếu tố kỹ thuật

  • Num_vars: danh sách các biến số cần kiểm tra ngoại lai; lapply(): lặp qua từng biến để tạo biểu đồ hộp (boxplot).
  • If (v %in% names(data)): kiểm tra biến có trong dữ liệu không; geom_boxplot(): vẽ boxplot hiển thị giá trị ngoại lai.
  • Labs() và theme_minimal(): đặt tiêu đề, nhãn trục và kiểu hiển thị; coord_flip() (trong theme): xay ngang biểu đồ cho dễ nhìn.
  • Filter(Negate(is.null), plots): loại bỏ biểu đồ rỗng (nếu biến không tồn tại).

Kết luận

Phân tích bốn biểu đồ hộp cho thấy biến age phân bố ổn định, không có giá trị ngoại lai rõ rệt. Biến bmi xuất hiện nhiều ngoại lai phía trên, thể hiện một số trường hợp có chỉ số cao bất thường, có thể do béo phì nghiêm trọng hoặc lỗi nhập liệu, cần được kiểm tra kỹ trước khi loại bỏ. Với hbA1c_level, một vài ngoại lai cao cho thấy nhóm bệnh nhân kiểm soát đường huyết kém; đây là tín hiệu y học quan trọng nên được giữ lại. Trong khi đó, blood_glucose_level có nhiều giá trị cao và dao động mạnh, phản ánh sự biến thiên tự nhiên trong dữ liệu y khoa. Nhìn chung, bmi và blood_glucose_level có mức độ biến động lớn nhất, còn age và hbA1c_level ổn định hơn, phù hợp với đặc điểm thực tế của dữ liệu sức khỏe.

2.5 Mã hóa biến “location” sang dạng viết tắt

state_abbrev <- data.frame(state_name = state.name, abbrev = state.abb)
data <- data %>%
  left_join(state_abbrev, by = c("location" = "state_name")) %>%
  mutate(location = ifelse(is.na(abbrev), location, abbrev)) %>% 
  select(-abbrev)

Yếu tố kĩ thuật

  • state.name và state.abb là danh sách tên và mã viết tắt 50 bang có sẵn trong R.
  • left_join() (thuộc dplyr) ghép dữ liệu theo cột location.
  • mutate() thay thế tên bang bằng mã viết tắt, dùng ifelse() để giữ nguyên giá trị nếu không khớp.
  • select(-abbrev) loại bỏ cột tạm abbrev sau khi mã hóa.

Kết luận

Sau khi mã hóa, biến location đã được chuẩn hóa sang dạng viết tắt hai ký tự theo chuẩn USPS, giúp dữ liệu ngắn gọn, đồng nhất và chuyên nghiệp hơn.

2.6 Tạo bảng tần suất xuất hiện của các bang

freq_table_2cols <- data %>%
  group_by(location) %>%
  summarise(Tan_suat = n()) %>%
  select(Ma_viet_tat = location, Tan_suat)
N <- nrow(freq_table_2cols)
num_cols <- 5 
rows_per_col <- ceiling(N / num_cols) 
list_tbls <- list()
header_group <- c() 
new_col_names_base <- c("Mã", "Tần suất")
col_keys_unique <- c()
for (i in 1:num_cols) {
  start_row <- (i - 1) * rows_per_col + 1
  end_row <- min(i * rows_per_col, N)
  temp_tbl <- freq_table_2cols[start_row:end_row, ]
  if (nrow(temp_tbl) < rows_per_col) {
    na_rows <- rows_per_col - nrow(temp_tbl)
    na_df <- data.frame(Ma_viet_tat = rep(NA, na_rows), Tan_suat = rep(NA, na_rows))
    temp_tbl <- rbind(temp_tbl, na_df)}
  current_names_unique <- paste0(c("Ma_viet_tat", "Tan_suat"), "_", i)
  names(temp_tbl) <- current_names_unique
  list_tbls[[i]] <- temp_tbl
  col_keys_unique <- c(col_keys_unique, current_names_unique)
  header_group <- c(header_group, setNames(2, paste("Phần", i))) }
merged_tbl_2cols <- do.call(cbind, list_tbls)
col_ma_keys <- col_keys_unique[c(TRUE, FALSE)]      
col_tan_suat_keys <- col_keys_unique[c(FALSE, TRUE)] 
ft <- flextable(merged_tbl_2cols, col_keys = col_keys_unique) %>%
  set_caption(caption = "Bảng tần suất khu vực ") %>%
  width(j = col_ma_keys, width = 0.4) %>% 
  width(j = col_tan_suat_keys, width = 1.2) %>% 
  add_header_row(values = names(header_group), colwidths = unname(header_group)) %>%
  set_header_labels(values = rep(new_col_names_base, num_cols), keys = col_keys_unique ) %>%
  set_formatter(na_str = "") %>%
  theme_booktabs() %>%
  align(j = col_ma_keys, align = "center", part = "all") %>%
  align(j = col_tan_suat_keys, align = "right", part = "all") %>%
  set_table_properties(layout = "autofit", width = 1.0) 
ft
Bảng tần suất khu vực

Phần 1

Phần 2

Phần 3

Phần 4

Phần 5

Tần suất

Tần suất

Tần suất

Tần suất

Tần suất

AK

2,034

Guam

1,203

ME

2,036

NM

2,032

TN

1,574

AL

2,036

HI

2,038

MI

2,036

NV

1,985

TX

1,337

AR

2,037

IA

2,037

MN

2,037

NY

2,035

UT

1,359

AZ

1,986

ID

1,988

MO

2,035

OH

1,985

United States

1,401

CA

1,986

IL

2,036

MS

2,035

OK

1,985

VA

1,350

CO

2,035

IN

1,987

MT

2,033

OR

2,036

VT

1,338

CT

2,035

KS

2,036

NC

2,035

PA

2,035

Virgin Islands

763

DE

2,036

KY

2,038

ND

2,034

Puerto Rico

1,295

WA

1,363

District of Columbia

2,036

LA

2,036

NE

2,037

RI

2,035

WI

388

FL

2,037

MA

2,036

NH

2,034

SC

1,986

WV

1,132

GA

2,035

MD

2,034

NJ

2,037

SD

2,033

WY

388

Yếu tố kĩ thuật

  • Nhóm và đếm: group_by(location) + summarise(Tan_suat = n()); đổi tên cột: location → Ma_viet_tat
  • Chia bảng 5 cột: tính rows_per_col = ceiling(N/5), điền NA nếu thiếu dòng để mỗi cột đều đủ hàng.
  • Đổi tên cột theo số cột (_1, _2,…) để ghép không trùng, ghép các cột: do.call(cbind, list_tbls).
  • Tạo flextable với:caption (set_caption), chỉnh độ rộng (width), header nhóm (add_header_row), hiển thị NA trống (set_formatter), căn lề (align), theme gọn (theme_booktabs), bảng co giãn (set_table_properties)

Kết luận

Sau khi mã hóa biến location theo chuẩn viết tắt USPS và thống kê tần suất, dữ liệu bao phủ hầu hết các bang của Hoa Kỳ với 55 khu vực, gồm cả các vùng lãnh thổ như Guam, Puerto Rico và Virgin Islands. Phần lớn các bang có khoảng 2000 quan sát, cho thấy phân bố tương đối đồng đều, ngoại trừ một số khu vực như Texas (1337), Wisconsin (388) và Wyoming (388) có số mẫu thấp hơn.

2.7 Chuyển đổi các biến “hyppertension”, “heart_disease”, “diabetes” sang nhãn tiếng việt

data <- data %>%
  mutate(hypertension = as.factor(ifelse(hypertension == 1, "Có", "Không")),heart_disease = as.factor(ifelse(heart_disease == 1, "Có", "Không")), diabetes = as.factor(ifelse(diabetes == 1, "Có tiểu đường", "Không tiểu đường")))
str(data[c("hypertension", "heart_disease", "diabetes")])
## tibble [99,986 × 3] (S3: tbl_df/tbl/data.frame)
##  $ hypertension : Factor w/ 2 levels "Có","Không": 2 2 2 2 2 2 2 2 2 2 ...
##  $ heart_disease: Factor w/ 2 levels "Có","Không": 2 2 2 2 2 2 2 2 2 2 ...
##  $ diabetes     : Factor w/ 2 levels "Có tiểu đường",..: 2 2 2 2 2 2 2 2 2 2 ...

Yếu tố kĩ thuật

  • Hàm mutate()dùng để tạo hoặc cập nhật các cột, cấu trúc ifelse() kiểm tra giá trị từng dòng: Nếu bằng 1 → gán nhãn “Có” hoặc “Có tiểu đường”. Nếu bằng 0 → gán nhãn “Không” hoặc “Không tiểu đường”, hàm as.factor() chuyển các nhãn thành kiểu dữ liệu factor, phù hợp cho phân tích định tính và trực quan hóa.
  • str() kiểm tra lại cấu trúc dữ liệu để xác nhận việc chuyển đổi thành công.

Kết luận

Sau khi chuyển đổi, các biến hypertension, heart_disease và diabetes đã được gán nhãn tiếng việt rõ ràng (“Có” / “Không”)

2.8 Tạo biến tổng hợp phản ánh nguy cơ sức khỏe

data <- data %>%
mutate(risk_score = 0.3 * (age / max(age, na.rm = TRUE)) +0.4 * (bmi / max(bmi, na.rm = TRUE)) +0.3 * (hbA1c_level / max(hbA1c_level, na.rm = TRUE)))
data <- data %>%
mutate(risk_group = case_when(risk_score < 0.35 ~ "Thấp",risk_score < 0.65 ~ "Trung bình",TRUE ~ "Cao"))
table(data$risk_group)
## 
##        Cao       Thấp Trung bình 
##       1935      18529      79522

Yếu tố kĩ thuật

  • Tính risk_score:kết hợp 3 biến age, bmi, hbA1c_level theo trọng số 0.3 – 0.4 – 0.3. Mỗi biến được chuẩn hóa chia cho giá trị lớn nhất (max) để đưa về cùng thang 0–1.
  • Phân nhóm nguy cơ risk_group: < 0.35 → Thấp, 0.35–0.65 → Trung bình, > 0.65 → Cao
  • table(data$risk_group): đếm số người trong từng nhóm nguy cơ.

Kết luận

Sau khi xây dựng biến tổng hợp risk_score dựa trên ba yếu tố tuổi, BMI và HbA1c, các cá nhân được chia thành ba nhóm rủi ro sức khỏe: Thấp, Trung bình và Cao. Kết quả cho thấy nhóm trung bình chiếm tỷ lệ cao nhất (79,5%), phản ánh phần lớn dân số có sức khỏe tương đối ổn định nhưng vẫn tiềm ẩn nguy cơ về BMI và HbA1c. Nhóm thấp chiếm 18,5%, gồm những người có sức khỏe tốt và cân nặng hợp lý, trong khi nhóm cao chỉ chiếm 2%, tuy ít nhưng có nguy cơ cao mắc bệnh mạn tính nếu không được can thiệp sớm.

2.9 Kiểm tra tính tương quan gữa các biến định lượng

num_vars <- data %>% select(age, bmi, hbA1c_level, blood_glucose_level, risk_score)
cor_matrix <- cor(num_vars, use = "complete.obs", method = "pearson")
print(cor_matrix)
##                       age    bmi hbA1c_level blood_glucose_level risk_score
## age                 1.000 0.3374       0.101              0.1107      0.907
## bmi                 0.337 1.0000       0.083              0.0913      0.551
## hbA1c_level         0.101 0.0830       1.000              0.1667      0.434
## blood_glucose_level 0.111 0.0913       0.167              1.0000      0.166
## risk_score          0.907 0.5513       0.434              0.1660      1.000

Yếu tố kĩ thuật

  • Chọn biến số (num_vars): age, bmi, hbA1c_level, blood_glucose_level, risk_score.
  • Tính ma trận tương quan (cor_matrix): dùng Pearson, bỏ giá trị thiếu (complete.obs).
  • print(cor_matrix): hiển thị mối liên hệ tuyến tính giữa các biến.

Kết luận

Kết quả phân tích cho thấy mối tương quan giữa các biến sức khỏe chủ yếu ở mức yếu đến trung bình, trong nhóm biến gốc, các cặp như age–bmi (r=0.34) và hbA1c–blood_glucose (r=0.17) có mối liên hệ yếu, cho thấy các chỉ số này không phụ thuộc chặt chẽ vào nhau. Ở nhóm biến tổng hợp, risk_score có tương quan mạnh nhất với age (r=0.91), tiếp theo là bmi (r=0.55) và hbA1c (r=0.43), khẳng định ba yếu tố này là nền tảng trong việc xác định mức độ rủi ro sức khỏe. Ngược lại, blood_glucose_level (r=0.17) chỉ ảnh hưởng nhẹ đến điểm rủi ro do đặc tính biến động ngắn hạn.

2.10 Kiểm tra lại cấu trúc dữ liệu sau xử lý

str(data)
## tibble [99,986 × 19] (S3: tbl_df/tbl/data.frame)
##  $ year                : num [1:99986] 2020 2015 2015 2015 2016 ...
##  $ gender              : chr [1:99986] "Female" "Female" "Male" "Male" ...
##  $ age                 : num [1:99986] 32 29 18 41 52 66 49 15 51 42 ...
##  $ location            : chr [1:99986] "AL" "AL" "AL" "AL" ...
##  $ race:AfricanAmerican: Factor w/ 2 levels "0","1": 1 1 1 1 2 1 1 1 2 1 ...
##  $ race:Asian          : Factor w/ 2 levels "0","1": 1 2 1 1 1 1 1 1 1 1 ...
##  $ race:Caucasian      : Factor w/ 2 levels "0","1": 1 1 1 2 1 2 2 1 1 2 ...
##  $ race:Hispanic       : Factor w/ 2 levels "0","1": 1 1 1 1 1 1 1 1 1 1 ...
##  $ race:Other          : Factor w/ 2 levels "0","1": 2 1 2 1 1 1 1 2 1 1 ...
##  $ hypertension        : Factor w/ 2 levels "Có","Không": 2 2 2 2 2 2 2 2 2 2 ...
##  $ heart_disease       : Factor w/ 2 levels "Có","Không": 2 2 2 2 2 2 2 2 2 2 ...
##  $ smoking_history     : chr [1:99986] "never" "never" "never" "never" ...
##  $ bmi                 : num [1:99986] 27.3 19.9 23.8 27.3 23.8 ...
##  $ hbA1c_level         : num [1:99986] 5 5 4.8 4 6.5 5.7 5.7 5 6 5.7 ...
##  $ blood_glucose_level : num [1:99986] 100 90 160 159 90 159 80 155 100 160 ...
##  $ diabetes            : Factor w/ 2 levels "Có tiểu đường",..: 2 2 2 2 2 2 2 2 2 2 ...
##  $ clinical_notes      : chr [1:99986] "Overweight, advised dietary and exercise modifications." "Healthy BMI range." "Young patient, generally lower risk but needs lifestyle assessment. Healthy BMI range. Elevated blood glucose l"| __truncated__ "Overweight, advised dietary and exercise modifications. Elevated blood glucose levels, potential diabetes concern." ...
##  $ risk_score          : num [1:99986] 0.401 0.359 0.327 0.401 0.511 ...
##  $ risk_group          : chr [1:99986] "Trung bình" "Trung bình" "Thấp" "Trung bình" ...

Yếu tố kĩ thuật

  • str(data): hiển thị cấu trúc tổng quát của bộ dữ liệu

Kết luận

Sau khi hoàn tất các bước xử lý, chuẩn hóa và tạo biến mới, bộ dữ liệu sức khỏe gồm 99.986 quan sát và 19 biến, trong đó có 17 biến gốc phản ánh đặc điểm nhân khẩu học, hành vi và chỉ số sinh học, cùng 2 biến tổng hợp là risk_score và risk_group.

Các biến định tính như gender, location, race, smoking_history, hypertension, heart_disease và diabetes được mã hóa và chuẩn hóa; các biến định lượng như age, bmi, hbA1c_level và blood_glucose_level được kiểm tra phân phối và xử lý ngoại lai để duy trì độ tin cậy.Biến risk_score được xây dựng như một chỉ số tổng hợp, kết hợp ba yếu tố tuổi (0.3), BMI (0.4) và HbA1c (0.3) để lượng hóa mức rủi ro sức khỏe trên thang điểm 0–1. Từ đó, risk_group phân loại thành ba mức: Thấp, Trung bình và Cao, phản ánh tình trạng sức khỏe tổng thể của từng cá nhân.

NỘI DUNG 3: TÓM TẮT VÀ PHÂN TÍCH TỪNG BIẾN

3.1 Thống kê mô tả các biến

3.1.1. Năm ghi nhận dữ liệu (Year)

summary(data$year)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##    2015    2019    2019    2018    2019    2022

Yếu tố kĩ thuật

  • Hàm summary(data$year) được sử dụng để tóm tắt thống kê biến year

Kết luận

Biến year cho biết năm ghi nhận thông tin bệnh nhân có giá trị từ 2015 đến 2022 (không có năm 2017), phản ánh tốt xu hướng theo thời gian.

3.1.2. Giới tính (Gender)

table(data$gender)
## 
## Female   Male  Other 
##  58546  41422     18

Yếu tố kĩ thuật

  • Hàm table(data$gender) dùng để đếm số lượng từng giới tính trong dữ liệu.

Kết luận

Kết quả thống kê cho thấy trong 99.986 quan sát, nhóm nữ (Female) chiếm tỷ lệ cao nhất với 58.546 trường hợp, tiếp theo là nam (Male) với 41.422 trường hợp, trong khi Other chỉ có 18 trường hợp – tỷ lệ không đáng kể.

3.1.3. Tuổi (Age)

summary(data$age)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##    0.08   24.00   43.00   41.89   60.00   80.00

Yếu tố kĩ thuật

  • Hàm summary(data$age) được sử dụng để tóm tắt thống kê mô tả biến định lượng age

Kết luận

Kết quả cho thấy biến age dao động từ 0.08 đến 80 tuổi, với tuổi trung bình 41.9 và trung vị 43, cho thấy phần lớn đối tượng thuộc nhóm trung niên (30–60 tuổi).

3.1.4. Khu vực (Location)

t(table(data$location))
##       
##          AK   AL   AR   AZ   CA   CO   CT   DE District of Columbia   FL   GA
##   [1,] 2034 2036 2037 1986 1986 2035 2035 2036                 2036 2037 2035
##       
##        Guam   HI   IA   ID   IL   IN   KS   KY   LA   MA   MD   ME   MI   MN
##   [1,] 1203 2038 2037 1988 2036 1987 2036 2038 2036 2036 2034 2036 2036 2037
##       
##          MO   MS   MT   NC   ND   NE   NJ   NM   NV   NY   NH   OH   OK   OR
##   [1,] 2035 2035 2033 2035 2034 2037 2037 2032 1985 2035 2034 1985 1985 2036
##       
##          PA Puerto Rico   RI   SC   SD   TN   TX United States   UT   VA
##   [1,] 2035        1295 2035 1986 2033 1574 1337          1401 1359 1350
##       
##        Virgin Islands   VT   WA   WI   WV   WY
##   [1,]            763 1338 1363  388 1132  388

Yếu tố kỹ thuật

  • Hàm table() dùng để thống kê tần suất xuất hiện của từng khu vực trong biến locatio; sử dụng t(table) để chuyển bảng dọc thành ngang

Kết luận

Biến location cho biết nơi cư trú hoặc cơ sở khám của người tham gia, bao phủ nhiều bang tại Hoa Kỳ. Tuy nhiên, phân bố mẫu không đồng đều, với các bang đông dân như California, Texas, Florida có nhiều quan sát hơn, phản ánh chênh lệch về dân cư và khả năng tiếp cận y tế.

3.1.5. Chủng tộc (Race)

data <- data %>%
  mutate( race = case_when(`race:AfricanAmerican` == 1 ~ "AfricanAmerican",`race:Asian` == 1 ~ "Asian",`race:Caucasian` == 1 ~ "Caucasian",`race:Hispanic` == 1 ~ "Hispanic",`race:Other` == 1 ~ "Other",TRUE ~ NA_character_))
table(data$race)
## 
## AfricanAmerican           Asian       Caucasian        Hispanic           Other 
##           20221           20007           19873           19887           19998

Yếu tố kỹ thuật

  • Chuyển nhiều cột race: thành 1 cột race*; mutate() + case_when(): tạo biến mới theo điều kiện; TRUE ~ NA_character_: xử lý giá trị thiếu.
  • table(data$race): kiểm tra tần suất từng nhóm.

Kết luận

Kết quả cho thấy nhóm AfricanAmerican chiếm tỷ lệ cao nhất, phản ánh sự đa dạng dân tộc trong dữ liệu và cho phép phân tích chênh lệch nguy cơ bệnh giữa các nhóm.

3.1.6. Tăng huyết áp (Hypertension)

summary(data$hypertension)
##    Có Không 
##  7485 92501

Yếu tố kỹ thuật

  • Hàm summary() dùng mô tả phân bố biến định tính hypertension

Kết luận

Kết quả cho thấy giá trị trung bình của biến hypertension là 0.07486, nghĩa là khoảng 7.5% người trong mẫu mắc tăng huyết áp, trong khi phần lớn không mắc bệnh, cho thấy số người không bị tăng huyết áp chiếm tỷ lệ áp đảo, phản ánh xu hướng chung trong quần thể mẫu.

3.1.7. Bệnh tim mạch (Heart disease)

summary(data$heart_disease)
##    Có Không 
##  3942 96044

Yếu tố kỹ thuật

  • Hàm summary() dùng mô tả phân bố biến định tính heart_disease

Kết luận

Giá trị trung bình của biến heart_disease là 0.03943, cho thấy khoảng 3,9% người trong mẫu mắc bệnh tim mạch, trong khi đa số (median = 0) không mắc bệnh, phản ánh rằng tỷ lệ mắc bệnh tim mạch trong quần thể nghiên cứu tương đối thấp so với tổng số mẫu.

3.1.8. Tiền sử hút thuốc (Smoking history)

table(data$smoking_history)
## 
##     current        ever      former       never     no info not current 
##        9286        4004        9352       35091       35806        6447

Yếu tố kỹ thuật

  • table() thống kê tần suất các nhóm thói quen hút thuốc.

Kết luận

Kết quả cho thấy nhóm (never) và (No Info) chiếm tỷ lệ lớn nhất (trên 35.000 mẫu), trong khi các nhóm đang hút, đã từng và đã ngừng có số lượng ít hơn nhiều, điều này cho thấy đa số đối tượng không có thói quen hút thuốc.

3.1.9. Chỉ số khối cơ thể (BMI)

summary(data$bmi)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##    10.0    23.6    27.3    27.3    29.6    95.7

Yếu tố kỹ thuật

  • Hàm summary(data$bmi) được sử dụng để tóm tắt thống kê mô tả biến định lượng bmi

Kết luận

Giá trị BMI dao động từ 10.01 đến 95.69, trung bình 27.32 và trung vị 27.32, cho thấy phần lớn đối tượng thuộc nhóm thừa cân nhẹ (BMI > 25). Một số trường hợp có BMI rất cao, thể hiện sự chênh lệch đáng kể về thể trạng trong mẫu nghiên cứu, có thể ảnh hưởng đến nguy cơ mắc bệnh tim mạch và tiểu đường.

3.1.10. Chỉ số HbA1c (đường huyết trung bình 3 tháng)

summary(data$hbA1c_level)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##    3.50    4.80    5.80    5.53    6.20    9.00

Yếu tố kĩ thuật

  • Hàm summary(data$hbA1c_level) được sử dụng để tóm tắt thống kê mô tả biến định lượng hbA1c_level

Kết luận

Giá trị HbA1c dao động từ 3.5 đến 9.0, với trung bình 5.53 và trung vị 5.8, cho thấy phần lớn người trong mẫu có mức HbA1c trong giới hạn bình thường (<5.7%), trong khi một số trường hợp cao hơn ngưỡng này có thể nguy cơ tiền tiểu đường hoặc tiểu đường.

3.1.11. Nồng độ đường huyết hiện tại (Blood_glucose_level)

summary(data$blood_glucose_level)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##      80     100     140     138     159     300

Yếu tố kỹ thuật

  • Hàm summary(data$blood_glucose_level) được sử dụng để tóm tắt thống kê mô tả biến định lượng blood_glucose_level

Kết luận Chỉ số dao động từ 80–300 mg/dL. Giá trị trung bình khoảng 140, cao hơn ngưỡng bình thường (dưới 125), phù hợp với mẫu bệnh nhân có rối loạn đường huyết

3.1.12. Tình trạng tiểu đường (diabetes)

summary(data$diabetes)
##    Có tiểu đường Không tiểu đường 
##             8500            91486

Yếu tố kỹ thuật

  • Hàm summary() dùng mô tả phân bố biến định tính diabetes

Kết luận

Giá trị trung bình của diabetes là 0.085, tức khoảng 8,5% người mắc tiểu đường, còn lại chủ yếu không mắc (median = 0). Dù tỷ lệ này thấp, dữ liệu vẫn đủ để phân tích mối liên hệ giữa tiểu đường và các yếu tố nguy cơ như tuổi, BMI và huyết áp.

3.1.13. Ghi chú lâm sàng (clinical_notes)

table(data$clinical_notes)

Yếu tố kỹ thuật

  • Biến dạng văn bản có thể được xử lý bằng Natural Language Processing (NLP) để trích xuất thông tin chuyên sâu.

Kết luận

Biến clinical_notes là ghi chú văn bản từ bác sĩ hoặc chuyên viên y tế. Dữ liệu này có thể được khai thác bằng kỹ thuật xử lý ngôn ngữ tự nhiên (NLP)

3.2 Phân tổ các biến

3.2.1 Phân nhóm theo độ tuổi

data <- data %>%
  mutate(age_group = cut(age,breaks = c(-Inf, 29, 44, 59, 74, Inf),labels = c("Dưới 30", "30-44", "45-59", "60-74", "75+"),right = TRUE )) %>%
  filter(!is.na(age_group)) %>%
  mutate(age_group = factor(age_group,levels = c("Dưới 30", "30-44", "45-59", "60-74", "75+")))
age_table <- data %>%
  group_by(age_group) %>%
  summarise(Tan_so = n()) %>%
  mutate(Ty_le = Tan_so / sum(Tan_so))
knitr::kable(age_table, caption = "Bảng 2.1. Phân bố mẫu theo nhóm tuổi")
Bảng 2.1. Phân bố mẫu theo nhóm tuổi
age_group Tan_so Ty_le
Dưới 30 32429 0.324
30-44 19972 0.200
45-59 22535 0.225
60-74 15944 0.159
75+ 9106 0.091

Yếu tố kĩ thuật

  • Dùng mutate(age_group = cut()) để tạo biến gồm các khoảng tuổi trên; filter(!is.na(age_group)) loại bỏ các dòng không có dữ liệu tuổi; mutate(age_group = factor(…)) → Sắp xếp thứ tự logic cho các nhóm tuổi
  • Dùng group_by() để nhóm các nhóm tuổi, summarise(Tan_so = n()) để tính tần suất và tỷ lệ phần trăm từng nhóm, dùng mutate (Ty_le =(..)) để tính tỷ lệ % trên tổng mẫu.
  • Kết quả được trình bày gọn gàng bằng knitr::kable().

Nhận xét

Kết quả cho thấy dữ liệu bao phủ đầy đủ các nhóm tuổi, trong đó dưới 30 tuổi chiếm 32,43%, chủ yếu là người trẻ ít mắc bệnh mạn tính; 30–44 tuổi chiếm 19,97%, bắt đầu xuất hiện yếu tố nguy cơ; 45–59 tuổi chiếm 22,54%, là nhóm trung niên dễ mắc rối loạn chuyển hóa; còn 60–74 tuổi và ≥75 tuổi lần lượt chiếm 15,95% và 9,11%, có nguy cơ cao mắc bệnh mạn tính. Như vậy, nhóm 45–59 tuổi cần được ưu tiên sàng lọc và can thiệp sớm, trong khi nhóm cao tuổi cần quản lý sức khỏe định kỳ.

3.2.2 Phân nhóm theo giới tính

gender_table <- data %>%
  group_by(gender) %>%
  summarise(Tan_so = n()) %>%
  mutate(Ty_le = Tan_so / sum(Tan_so))
knitr::kable(gender_table, caption = "Bảng 2.2. Phân bố mẫu theo giới tính")
Bảng 2.2. Phân bố mẫu theo giới tính
gender Tan_so Ty_le
Female 58546 0.586
Male 41422 0.414
Other 18 0.000

Yếu tố kỹ thuật

  • Dùng group_by(); summarise(); mutate(Ty_le =(..)); knitr::kable() đã giải thích ở 3.2.1

Kết luận

Kết quả cho thấy nữ giới chiếm 58,55%, nam giới 41,43%, và nhóm Other 0,02%, phản ánh sự chênh lệch rõ rệt về giới tính trong mẫu dữ liệu. Điều này có thể do phụ nữ chủ động hơn trong việc theo dõi sức khỏe hoặc do thiên lệch khi thu thập dữ liệu.

3.2.3 Phân nhóm theo khu vực cư trú

data <- data %>%
  mutate(region = case_when(
    location %in% c("NY","NJ","PA","MA","CT","RI","NH","VT","ME") ~ "Northeast",
    location %in% c("IL","IN","OH","MI","WI","MN","IA","MO","ND","SD","NE","KS") ~ "Midwest",
    location %in% c("TX","FL","GA","AL","MS","LA","NC","SC","TN","VA","WV","KY","AR","OK") ~ "South",
    location %in% c("CA","WA","OR","NV","AZ","CO","UT","NM","ID","MT","WY","AK","HI") ~ "West",
    location %in% c("Guam","Puerto Rico","Virgin Islands","District of Columbia") ~ "Territories",
    TRUE ~ "Other"))
region_table <- data %>%
  group_by(region) %>%
  summarise(Tan_so = n()) %>%
  mutate(Ty_le = Tan_so / sum(Tan_so))
knitr::kable(region_table, caption = "Bảng 2.5. Phân bố mẫu theo vùng địa lý")
Bảng 2.5. Phân bố mẫu theo vùng địa lý
region Tan_so Ty_le
Midwest 22681 0.227
Northeast 17621 0.176
Other 5471 0.055
South 25653 0.257
Territories 5297 0.053
West 23263 0.233

Yếu tố kỹ thuật

  • Sử dụng mutate() kết hợp case_when() để gộp các bang thành 5 vùng địa lý chính của Hoa Kỳ.
  • Dùng group_by(), summarise(Tan_so..), mutate(Ty_le..), knitr::kable() giải thích ở 3.2.1

Kết luận

Kết quả cho thấy mẫu phân bố khá đồng đều giữa các vùng, song vẫn có chênh lệch nhất định: miền Nam chiếm 25,66%, miền Tây 23,27%, miền Trung – Tây 22,68%, miền Đông Bắc 17,62%, trong khi Territories và Other lần lượt chỉ chiếm 5,30% và 5,47%. Tỷ lệ cao ở miền Nam và miền Tây phản ánh quy mô dân số lớn, đô thị hóa cao và hệ thống y tế phát triển; ngược lại, tỷ lệ thấp ở Territories có thể do hạn chế về nguồn lực và khó khăn thu thập dữ liệu.

3.2.4 Phân nhóm theo tiền sử hút thuốc

smoking_table <- data %>%
  group_by(smoking_history) %>%
  summarise(Tan_so = n()) %>%
  mutate(Ty_le = Tan_so / sum(Tan_so))
knitr::kable(smoking_table, caption = "Bảng 2.7. Phân bố mẫu theo tiền sử hút thuốc")
Bảng 2.7. Phân bố mẫu theo tiền sử hút thuốc
smoking_history Tan_so Ty_le
current 9286 0.093
ever 4004 0.040
former 9352 0.094
never 35091 0.351
no info 35806 0.358
not current 6447 0.064

Yếu tố kĩ thuật

  • Dùng group_by(), summarise(Tan_so..), mutate(Ty_le..), knitr::kable() giải thích ở 3.2.1

Kết luận

Kết quả cho thấy nhóm “never” (chưa bao giờ hút) chiếm 35,1%, nhóm “no info” (không có thông tin) 35,8%, trong khi “current” (đang hút) và “former” (đã từng hút) lần lượt 9,3% và 9,4%; các nhóm còn lại dưới 7%. Điều này cho thấy đa số có hành vi sức khỏe tốt, nhưng vẫn tồn tại bộ phận từng hoặc đang hút thuốc — yếu tố nguy cơ cần chú ý trong phân tích bệnh tim và tiểu đường. Tỷ lệ “no info” cao cũng cho thấy dữ liệu thiếu hụt, cần được xử lý cẩn trọng để đảm bảo độ tin cậy khi mô hình hóa.

3.3 Xác định nhóm tuổi có tỷ lệ mắc tiểu đường cao nhất

data %>%
  mutate(age_group = cut(age,breaks = c(0, 40, 60, Inf),labels = c("< 40", "40 - 60", "> 60"),right = TRUE)) %>%
  group_by(age_group) %>%
  summarise(Total = n(),Diabetic_Rate = sum(diabetes == "Có tiểu đường") / Total * 100) %>% arrange(desc(Diabetic_Rate))
## # A tibble: 3 × 3
##   age_group Total Diabetic_Rate
##   <fct>     <int>         <dbl>
## 1 > 60      23627         20.0 
## 2 40 - 60   29494         10.2 
## 3 < 40      46865          1.67

Yếu tố kỹ thuật

  • Dùng mutate(… cut(…)) để tạo biến age_group gồm 3 khoảng: < 40, 40 - 60, > 60.
  • Dùng group_by(age_group) đã giải thích ở 3.2.1; summarise(Total = n()) dùng để tính tổng số người trong mỗi nhóm.
  • Kết quả sắp xếp (arrange(desc)) theo Diabetic_Rate giảm dần để dễ thấy nhóm nguy cơ cao nhất.

Kết luận

Kết quả cho thấy tỷ lệ mắc tiểu đường tăng rõ theo tuổi: nhóm trên 60 tuổi cao nhất (≈20%), nhóm 40–60 tuổi khoảng 10,2%, và nhóm dưới 40 tuổi chỉ 1,67%. Xu hướng này khẳng định tuổi là yếu tố nguy cơ quan trọng, do quá trình chuyển hóa và tiết insulin suy giảm theo thời gian. Dù nhóm trẻ có tỷ lệ thấp, nhưng do quy mô lớn nên số ca mắc vẫn đáng chú ý.

3.4 Kiểm tra tỷ lệ bệnh tim giữa nam và nữ

prop.table(table(data$gender, data$heart_disease), 1) * 100
##         
##              Có  Không
##   Female   2.67  97.33
##   Male     5.75  94.25
##   Other    0.00 100.00

Yếu tố kỹ thuật

  • Hàm prop.table(…, 1) tính tỷ lệ phần trăm theo hàng, Sử dụng table() để tạo bảng tần số chéo giữa giới tính và tình trạng bệnh tim, nhân với 100 để chuyển kết quả sang đơn vị phần trăm, giúp so sánh rõ ràng hơn.

Kết luận

Kết quả cho thấy nam giới có tỷ lệ mắc bệnh tim cao gần gấp đôi nữ giới (5,7% so với 2,7%), trong khi nhóm “Other” không ghi nhận ca bệnh nào. Sự chênh lệch này phản ánh cả yếu tố sinh học — như hormone estrogen giúp bảo vệ tim mạch ở nữ giới trước mãn kinh — và yếu tố lối sống, khi nam giới thường hút thuốc, uống rượu và chịu căng thẳng nhiều hơn.

3.5 Phân tích mối liên hệ giữa tăng huyết áp và tiền sử hút thuốc

table(data$smoking_history, data$hypertension)
##              
##                  Có Không
##   current       832  8454
##   ever          419  3585
##   former       1339  8013
##   never        3204 31887
##   no info      1202 34604
##   not current   489  5958

Yếu tố kĩ thuật

  • Dùng table() để tạo bảng chéo giữa hai biến định tính (smoking_history và hypertension)

Kết luận

Kết quả phân tích cho thấy người đang hoặc đã từng hút thuốc (“current”, “former”, “ever”) có tỷ lệ tăng huyết áp cao hơn rõ rệt so với nhóm “never”, phản ánh tác động tích lũy và lâu dài của thuốc lá lên hệ tim mạch. Điều này khẳng định hút thuốc là yếu tố nguy cơ độc lập, mạnh mẽ của tăng huyết áp, và việc cai thuốc sớm đóng vai trò then chốt trong phòng ngừa bệnh tim mạch.

3.6 Mối quan hệ tuổi - tiểu đường có mạnh hơn trong nhóm “rủi ro cao” hay không?

data %>%
  group_by(risk_group, age_group) %>% 
  summarise(`Tỷ lệ Mắc Bệnh (%)` = mean(diabetes == "Có tiểu đường") * 100, .groups = 'drop') %>%
  filter(!is.na(age_group)) %>%
  arrange(risk_group, age_group) %>% 
  knitr::kable(caption = "Tỷ lệ tiểu đường theo nhóm rủi ro và nhóm tuổi")
Tỷ lệ tiểu đường theo nhóm rủi ro và nhóm tuổi
risk_group age_group Tỷ lệ Mắc Bệnh (%)
Cao 30-44 77.778
Cao 45-59 89.899
Cao 60-74 90.632
Cao 75+ 81.156
Thấp Dưới 30 0.154
Thấp 30-44 0.000
Trung bình Dưới 30 1.721
Trung bình 30-44 4.188
Trung bình 45-59 10.221
Trung bình 60-74 15.905
Trung bình 75+ 12.814

Yếu tố kĩ thuật

  • group_by(risk_group, age_group): kết hợp hai biến phân loại để xem xét ảnh hưởng đồng thời của nhóm rủi ro và tuổi.
  • summarise(mean(…)): tính trung bình của điều kiện diabetes == “Có Tiểu Đường”
  • filter(!is.na(age_group)): loại bỏ giá trị thiếu để tránh sai lệch kết quả.
  • arrange(risk_group, age_group): sắp xếp kết quả theo thứ tự tăng dần của nhóm rủi ro và độ tuổi
  • knitr::kable(…): trình bày bảng kết quả gọn gàng, có chú thích (“caption”) để dễ đọc và trích dẫn trong báo cáo..

Kết luận

Kết quả cho thấy nhóm rủi ro cao có tỷ lệ mắc tiểu đường rất cao ở mọi độ tuổi, từ 77,8% (30–44 tuổi) đến trên 90% (60–74 tuổi), chỉ giảm nhẹ ở nhóm ≥75 tuổi (81,2%). Trong khi đó, nhóm rủi ro trung bình có tỷ lệ thấp hơn nhiều (khoảng 15,9%), còn nhóm rủi ro thấp hầu như không ghi nhận ca mắc. Điều này cho thấy mối quan hệ giữa tuổi và tiểu đường được “khuếch đại” trong nhóm rủi ro cao, khi các yếu tố như béo phì, tăng huyết áp hay hút thuốc kết hợp với tác động của tuổi già làm suy giảm khả năng chuyển hóa glucose. Nói cách khác, tuổi không chỉ là yếu tố độc lập mà còn làm tăng cường tác động của các rối loạn chuyển hoá bệnh nền.

3.7 Xu hướng bệnh tim ở tuổi trung niên qua từng năm

middle_age_data <- data %>%
  filter(age_group == "45-59")
heart_middle_age_by_year <- middle_age_data %>%
  group_by(year) %>%
  summarise(total = n(),heart_count = sum(heart_disease == "Có"),heart_rate = mean(heart_disease == "Có") * 100) %>% 
  arrange(year)
kable(heart_middle_age_by_year, caption = "Tỷ lệ mắc bệnh tim theo năm ở nhóm tuổi trung niên (45-59)")
Tỷ lệ mắc bệnh tim theo năm ở nhóm tuổi trung niên (45-59)
year total heart_count heart_rate
2015 1946 60 3.08
2016 1992 70 3.51
2018 618 27 4.37
2019 17971 637 3.54
2020 4 0 0.00
2021 3 0 0.00
2022 1 0 0.00

Yếu tố kĩ thuật

  • Dữ liệu lọc nhóm tuổi trung niên filter(age_group == “45-59”).
  • Sử dụng group_by(year), knitr::kable() đã giải thích ở 3.2.1
  • Summarise(total..) đã giải thích ở 3.3

Kết luận

Kết quả phân tích cho thấy trong nhóm trung niên (45–59 tuổi), tỷ lệ mắc bệnh tim dao động khoảng 3–4% giai đoạn 2015–2019 và nhìn chung ổn định. Dù tỷ lệ không biến động lớn, số ca mắc tuyệt đối vẫn cao, đặc biệt ở các năm có quy mô mẫu lớn. Điều này cho thấy nhóm trung niên vẫn là nhóm nguy cơ cao do tích lũy các yếu tố như tăng huyết áp, tiểu đường, hút thuốc, ít vận động và căng thẳng Các năm 2020–2022 có số mẫu nhỏ nên tỷ lệ biến động mạnh, chưa đủ tin cậy để kết luận xu hướng.

3.8 Tỷ lệ mắc tiểu đường có tăng dần theo cấp độ rủi ro này hay không ?

data %>%
  group_by(risk_group) %>%
  summarise(`Tỷ lệ Mắc Bệnh (%)` = mean(diabetes == "Có tiểu đường") * 100) %>%
  arrange(desc(`Tỷ lệ Mắc Bệnh (%)`)) %>%
  kable(caption = "Tỷ lệ tiểu đường theo nhóm rủi ro (risk_group)")
Tỷ lệ tiểu đường theo nhóm rủi ro (risk_group)
risk_group Tỷ lệ Mắc Bệnh (%)
Cao 86.202
Trung bình 8.556
Thấp 0.151

Yếu tố kĩ thuật

  • group_by(risk_group), summarise(Tỷ lệ mắc bệnh..), kable() đã giải thích ở 3.2.1; arrange(desc(…)): đã giải thích ở 3.3

Kết luận

Kết quả cho thấy tỷ lệ mắc tiểu đường tăng mạnh theo cấp độ rủi ro: nhóm thấp chỉ 0,15%, nhóm trung bình 8,56%, trong khi nhóm cao lên tới 86,2%. Điều này chứng tỏ chỉ số risk_score phản ánh chính xác nguy cơ bệnh, khi các yếu tố như tuổi, BMI và HbA1c cùng tăng sẽ làm xác suất mắc tiểu đường tăng đột biến.

3.9 Đếm số lượng người có tuổi cao (> 60) và đường huyết vượt ngưỡng (> 180)

nrow(filter(data, age > 60 & diabetes == "Có tiểu đường"))
## [1] 4719

yếu tố kĩ thuật

  • Sử dụng filter() để lọc các quan sát thỏa mãn hai điều kiện; dùng nrow() để đếm số lượng dòng trong dữ liệu sau khi lọc, tương ứng với số người cao tuổi mắc tiểu đường.

Kết luận

Kết quả cho thấy 4.719 người trên 60 tuổi mắc tiểu đường nên cần được ưu tiên theo dõi, quản lý và can thiệp sớm, đồng thời triển khai các chương trình phòng ngừa, nâng cao nhận thức và lối sống lành mạnh để giảm nguy cơ biến chứng và cải thiện chất lượng sống.

3.10 Tính tỷ lệ bệnh nhân có cả tăng huyết áp và bệnh tim

nrow(filter(data, hypertension == "Có" & heart_disease == "Có")) / nrow(data) * 100
## [1] 0.916

Yếu tố kĩ thuật

  • Lọc các cá nhân vừa mắc tăng huyết áp vừa mắc bệnh tim bằng filter(); dùng nrow() chia cho tổng số mẫu để tính tỷ lệ phần trăm.

Kết quả

Khoảng 0,92% bệnh nhân mắc đồng thời tăng huyết áp và bệnh tim mạch. Dù tỷ lệ nhỏ, nhóm này chịu gánh nặng bệnh lý cao, với nguy cơ biến chứng, nhập viện và tử vong đáng kể.

3.11 Tính tỷ lệ người không hút thuốc nhưng vẫn mắc bệnh tim

nrow(filter(data, smoking_history == "never" & heart_disease == "Có")) / nrow(data) * 100
## [1] 1.1

Yếu tố kĩ thuật

  • nrow(filter(data, …)) / nrow(data) * 100 đã giải thích ở 3.10

Kết luận

Kết quả phân tích cho thấy khoảng 1,1% bệnh nhân chưa từng hút thuốc vẫn mắc bệnh tim, chứng tỏ bệnh tim không chỉ do hút thuốc mà còn do nhiều yếu tố phối hợp như tuổi cao, tăng huyết áp, tiểu đường, béo phì, di truyền và lối sống (ăn nhiều muối, ít vận động, căng thẳng). Mặc dù tỷ lệ nhỏ, nhóm này vẫn có nguy cơ biến chứng nghiêm trọng.

3.12 Tính độ lệch chuẩn của các biến định lượng

sapply(select(data, age, bmi, hbA1c_level, blood_glucose_level), sd, na.rm = TRUE)
##                 age                 bmi         hbA1c_level blood_glucose_level 
##               22.52                6.64                1.07               40.71

Yếu tố kĩ thuật

  • Dùng select() để chọn các biến định lượng cần tính độ lệch chuẩn; dùng sapply() kết hợp với sd, na.rm = TRUE) để tính độ lệch chuẩn cho từng biến, bỏ qua giá trị thiếu.

Kết luận

Kết quả cho thấy các biến định lượng trong mẫu có độ phân tán khác nhau: tuổi (ĐLC = 22,52) phản ánh đa dạng từ thanh thiếu niên đến người cao tuổi, ảnh hưởng đến nguy cơ tiểu đường, tăng huyết áp và tim mạch; BMI (6,64) cho thấy sự khác biệt về cân nặng, liên quan đến rủi ro chuyển hóa; HbA1c (1,07) và đường huyết (40,71) phản ánh mức kiểm soát đường huyết đa dạng giữa các cá nhân.

3.13 So sánh số lượng nam giới hút thuốc ở 2 khu vực có tần suất cao nhất

top_2_locations <- data %>% 
  count(location, sort = TRUE) %>% 
  head(2) %>% 
  pull(location)
male_data_top_2_locations <- data %>%
  filter(gender == "Male" & location %in% top_2_locations) %>%
  mutate(is_smoker = smoking_history %in% c("current", "ever", "not current", "former"))
cat(paste0("### 1. Số lượng nam giới hút thuốc (Top 2 Khu vực: ", paste(top_2_locations, collapse = " vs "), ")\n"))
## ### 1. Số lượng nam giới hút thuốc (Top 2 Khu vực: HI vs KY)
result_count <- male_data_top_2_locations %>%
  filter(is_smoker == TRUE) %>%
  group_by(location) %>%
  summarise(`Số lượng Nam giới Hút thuốc` = n()) %>%
  arrange(desc(`Số lượng Nam giới Hút thuốc`))
print(result_count)
## # A tibble: 2 × 2
##   location `Số lượng Nam giới Hút thuốc`
##   <chr>                            <int>
## 1 KY                                 293
## 2 HI                                 260

Yếu tố kĩ thuật

  • count(location, sort = TRUE) %>% head(2) %>% pull(location): Xác định 2 khu vực đông dân nhất.
  • filter(….) lọc nam giới ở 2 khu vực; mutate(is_smoker = (…)) tạo biến is_smoker đánh dấu ai từng/đang hút thuốc.
  • cat(paste0(..) in tiêu đề kết quả, nối tên 2 khu vực.
  • result_count (): Đếm số nam giới hút thuốc theo khu vực; arrange(desc()) sắp xếp giảm dần; print(result_count): hiển thị kết quả cuối cùng.

Kết luận

Phân tích cho thấy Kentucky có số nam giới hút thuốc cao nhất (293 người), tiếp theo là Hawaii (260 người). Mặc dù Hawaii nổi tiếng với các chiến dịch phòng chống thuốc lá, thói quen văn hóa, stress công việc và du lịch đông đúc vẫn duy trì tỷ lệ hút cao. Ở Kentucky, yếu tố kinh tế – xã hội như thu nhập thấp, giáo dục về tác hại thuốc lá chưa đồng đều và áp lực nghề nghiệp góp phần làm tăng tỷ lệ hút thuốc. Kết quả nhấn mạnh rằng tỷ lệ hút thuốc bị ảnh hưởng bởi môi trường sống, văn hóa và điều kiện kinh tế, đồng thời cho thấy cần các biện pháp phòng chống thuốc lá địa phương hóa, kết hợp giáo dục, kiểm soát môi trường và hỗ trợ hành vi thay thế, đặc biệt nhắm vào nam giới trong độ tuổi lao động để giảm nguy cơ bệnh mạn tính và cải thiện sức khỏe cộng đồng.

3.14 Phân tích tỷ lệ tiểu đường theo nhóm rủi ro

data %>%
  group_by(risk_group) %>%
  summarise(`Tỷ lệ Mắc Bệnh (%)` = mean(diabetes == "Có tiểu đường") * 100, .groups = 'drop') %>%
  arrange(desc(`Tỷ lệ Mắc Bệnh (%)`)) %>%
  knitr::kable(caption = "Tỷ lệ tiểu đường theo nhóm rủi ro")
Tỷ lệ tiểu đường theo nhóm rủi ro
risk_group Tỷ lệ Mắc Bệnh (%)
Cao 86.202
Trung bình 8.556
Thấp 0.151

Yếu tố kĩ thuật

  • Sử dụng group_by(risk_group), summarise(Tỷ lệ mắc bệnh), knitr::kable()đã giải thích ở 3.2.1
  • arrange(desc(…)) giúp sắp xếp bảng theo tỷ lệ mắc giảm dần

Kết luận

Phân tích cho thấy tỷ lệ mắc tiểu đường tăng dần theo nhóm rủi ro: cao 86,2%, trung bình 8,56%, thấp 0,15%. Nguy cơ cao tập trung ở người tuổi cao, BMI vượt chuẩn và HbA1c cao, trong khi nhóm thấp gần như không mắc bệnh, nhấn mạnh tầm quan trọng của lối sống lành mạnh.

3.15 Top 3 Khu vực có tỷ lệ mắc tăng huyết áp cao nhất trong nhóm tuổi trẻ (< 40)

data %>%
  filter(age < 40) %>%
  group_by(location) %>%
  summarise(`Tỷ lệ Tăng huyết áp (%)` = mean(hypertension == "Có") * 100) %>%
  arrange(desc(`Tỷ lệ Tăng huyết áp (%)`)) %>%
  head(3) %>%
  knitr::kable(caption = "Top 3 khu vực có tăng huyết áp cao ở người trẻ (Mã viết tắt)")
Top 3 khu vực có tăng huyết áp cao ở người trẻ (Mã viết tắt)
location Tỷ lệ Tăng huyết áp (%)
OR 2.06
Virgin Islands 2.04
NH 1.73

Yếu tố kỹ thuật

  • Sử dụng filter(age < 40) để lọc nhóm tuổi trẻ.
  • Dùng group_by(location), summarise(Tỷ lệ mắc bệnh), knitr::kable()đã giải thích ở 3.2.1
  • Dùng arrange(desc(…)) và head(3) giúp xác định 3 khu vực đứng đầu về tỷ lệ mắc bệnh.

Kết luận

Dữ liệu cho thấy tăng huyết áp đang xuất hiện ở người trẻ (<40 tuổi), với tỷ lệ cao nhất tại Oregon (2,06%), Virgin Islands (2,04%) và New Hampshire (1,73%). Nguyên nhân chủ yếu đến từ lối sống hiện đại, ăn mặn, ít vận động, căng thẳng và điều kiện môi trường.

3.16 Giới tính nào chiếm ưu thế trong nhóm “rủi ro cao”?

data %>%
  filter(risk_group == "Cao") %>% 
  count(gender) %>% 
  mutate(`Tỷ lệ (%)` = n / sum(n) * 100) %>%
  select(gender, `Tỷ lệ (%)`) %>%
  arrange(desc(`Tỷ lệ (%)`)) %>%
  knitr::kable(caption = "Phân bố giới tính trong nhóm rủi ro cao")
Phân bố giới tính trong nhóm rủi ro cao
gender Tỷ lệ (%)
Female 59
Male 41

Yếu tố kỹ thuật

  • Dùng filter() để tách nhóm “Cao” từ biến risk_group.
  • Hàm count() tính tần suất từng giới tính trong nhóm.
  • mutate() tính tỷ lệ phần trăm trên tổng nhóm, giúp so sánh trực quan.
  • arrange(desc(…)) sắp xếp từ giới chiếm ưu thế đến thấp hơn, hiển thị bảng gọn gàng với knitr::kable().

Kết luận

Phân tích nhóm “rủi ro cao” cho thấy nữ giới chiếm 58,97%, cao hơn nam giới (41,03%). Kết quả này phần nào phản ánh cơ cấu mẫu có tỷ lệ nữ nhiều hơn, nhưng đồng thời cho thấy nữ giới có xu hướng BMI và HbA1c cao hơn, làm tăng risk_score và đưa họ vào nhóm rủi ro cao. Điều đó chứng minh rằng sự khác biệt không chỉ do ngẫu nhiên mà xuất phát từ đặc điểm sinh học và chuyển hóa. Ngoài ra, nữ giới thường chủ động khám sức khỏe định kỳ hơn, nên các yếu tố rủi ro được ghi nhận đầy đủ, trong khi nam giới có thể bị bỏ sót do ít kiểm tra y tế.

3.17 Tỷ lệ mắc tiểu đường theo từng khu vực địa lý

data %>%
  group_by(location) %>%
  summarise(`Tỷ lệ mắc bệnh (%)` = mean(diabetes == "Có tiểu đường") * 100) %>%
  arrange(desc(`Tỷ lệ mắc bệnh (%)`)) %>%
  head(5) %>%
  knitr::kable(caption = "Top 5 khu vực có tỷ lệ mắc tiểu đường cao nhất")
Top 5 khu vực có tỷ lệ mắc tiểu đường cao nhất
location Tỷ lệ mắc bệnh (%)
DE 9.82
KS 9.77
IL 9.58
MT 9.54
WV 9.45

Yếu tố kĩ thuật

  • Sử dụng group_by(location) để nhóm dữ liệu theo khu vực; arrange(desc(…)) và head(5) đã giải thích ở 3.15
  • Dùng summarise(Tỷ lệ mắc bệnh), knitr::kable() đã giải thích ở 3.2.1

Kết luận

Phân tích dữ liệu cho thấy 5 khu vực có tỷ lệ tiểu đường cao nhất là Delaware (9,82%), Kansas (9,77%), Illinois (9,58%), Montana (9,54%) và West Virginia (9,45%). Kết quả phản ánh rõ ảnh hưởng của môi trường sống, lối sống và đặc điểm dân số. Delaware dẫn đầu do đô thị hóa cao và ít vận động; Kansas và Illinois chịu tác động từ chế độ ăn và béo phì; Montana và West Virginia bị ảnh hưởng bởi dân số già, thói quen ăn uống kém lành mạnh và hạn chế y tế.

3.18 Tỷ lệ tiền sử hút thuốc tại California qua từng năm

smoking_CA <- data %>%
  filter(location == "CA") %>%
  group_by(year, smoking_history) %>%
  summarise(Tổng_số = n(), .groups = "drop") %>%
  group_by(year) %>%
  mutate(Tỷ_lệ = Tổng_số / sum(Tổng_số) * 100) %>%
  select(Năm = year, `Tiền sử` = smoking_history, Tổng_số, `Tỷ lệ (%)` = Tỷ_lệ) %>%
  arrange(Năm, desc(`Tỷ lệ (%)`))
rows_per_col <- ceiling(nrow(smoking_CA) / 2)
merged_tbl <- do.call(cbind, lapply(1:2, function(i) {
  part <- smoking_CA[((i - 1) * rows_per_col + 1):min(i * rows_per_col, nrow(smoking_CA)), ]
  if (nrow(part) < rows_per_col)
    part <- rbind(part, setNames(data.frame(matrix(NA, rows_per_col - nrow(part), 4)), names(part)))
  names(part) <- paste0(names(part), "_", i)
  part}))
flextable(merged_tbl) %>%
  set_caption("Tỷ lệ tiền sử hút thuốc tại California (Chia 2 cột)") %>%
  add_header_row(values = c("Phần 1", "Phần 2"), colwidths = c(4, 4)) %>%
  set_header_labels(values = rep(c("Năm", "Tiền sử", "Tổng số", "Tỷ lệ (%)"), 2)) %>%
  theme_booktabs() %>%
  autofit()
Tỷ lệ tiền sử hút thuốc tại California (Chia 2 cột)

Phần 1

Phần 2

Năm

Tiền sử

Tổng số

Tỷ lệ (%)

Năm

Tiền sử

Tổng số

Tỷ lệ (%)

2,015

never

68

40.48

2,018

never

20

40.00

2,015

no info

51

30.36

2,018

no info

17

34.00

2,015

former

18

10.71

2,018

former

6

12.00

2,015

current

13

7.74

2,018

current

3

6.00

2,015

not current

10

5.95

2,018

not current

3

6.00

2,015

ever

8

4.76

2,018

ever

1

2.00

2,016

no info

69

41.07

2,019

no info

580

36.25

2,016

never

51

30.36

2,019

never

569

35.56

2,016

not current

17

10.12

2,019

current

156

9.75

2,016

former

12

7.14

2,019

former

132

8.25

2,016

ever

10

5.95

2,019

not current

92

5.75

2,016

current

9

5.36

2,019

ever

71

4.44

Yếu tố kĩ thuật

  • Filter(location == “CA”) lọc dữ liệu chỉ lấy ở California.
  • Dùng group_by(year, smoking_history), summarise(Tổng_số = n()), mutate(Tỷ_lệ =) đã giải thích ở 3.2.1
  • Sử dụng select(…) để đặt lại tên cột tiếng việt; arrange(Năm, desc(Tỷ_lệ)) để sắp xếp theo năm và tỷ lệ giảm dần.
  • Chia bảng làm 2 phần để hiển thị song song 2 cột dữ liệu trong flextable; cbind() ghép 2 bảng nhỏ thành 1 bảng ngang hoàn chỉnh.
  • flextable() tạo bảng trình bày đẹp, có tiêu đề(set_caption…), nhãn(theme), và tự căn chỉnh độ rộng (autofit)

Kết luận

Phân tích dữ liệu tại California giai đoạn 2015–2019 cho thấy nhóm “never” chiếm tỷ lệ cao nhất, khoảng 30–40% mỗi năm; trong khi nhóm “current” và “former” chỉ khoảng 5–10%. Nhóm “no info” cũng chiếm 30–40%, cho thấy hạn chế trong thu thập dữ liệu Xu hướng này phản ánh hiệu quả của các chính sách kiểm soát thuốc lá nghiêm ngặt tại California như cấm hút nơi công cộng, tăng thuế và đẩy mạnh tuyên truyền sức khỏe, giúp duy trì tỷ lệ không hút thuốc ổn định. Tuy nhiên, tỷ lệ “no info” cao có thể khiến mức độ hút thuốc thực tế bị đánh giá thấp.

3.19 Phân tích tỷ lệ tăng huyết áp trong nhóm mắc tiểu đường theo tiền sử hút thuốc

data %>%
  filter(diabetes == "Có tiểu đường") %>% 
  count(smoking_history, hypertension) %>%
  group_by(smoking_history) %>%
  mutate(Total = sum(n),Hypertension_Rate = n[hypertension == "Có"] / Total * 100) %>%
  filter(hypertension == "Có") %>%
  select(smoking_history, Hypertension_Rate) %>%
  arrange(desc(Hypertension_Rate)) %>%
  knitr::kable(caption = "Tỷ lệ tăng huyết áp trong nhóm mắc tiểu đường theo tiền sử hút thuốc")
Tỷ lệ tăng huyết áp trong nhóm mắc tiểu đường theo tiền sử hút thuốc
smoking_history Hypertension_Rate
former 27.5
never 27.0
ever 24.4
current 23.3
not current 22.9
no info 17.3

Yếu tố kĩ thuật

  • Dùng filter(diabetes..) để chỉ lấy nhóm bệnh nhân mắc tiểu đường (diabetes == “Có Tiểu Đường”).
  • Dùng count() tạo bảng tần số chéo giữa tiền sử hút thuốc và tình trạng tăng huyết áp.
  • Dùng group_by() kết hợp mutate(Total =..), knitr::kable() đã giải thích ở 3.2.1
  • Select(..) kết hợp arrange(desc(..)) chọn ra biến và sắp xếp theo thứ tự giảm dần

Kết luận

Kết quả cho thấy trong nhóm bệnh nhân tiểu đường, tỷ lệ tăng huyết áp cao nhất ở người từng hút thuốc (27,55%), tiếp theo là nhóm chưa từng hút (27,02%), và thấp nhất ở nhóm không cung cấp thông tin (17,33%).Tỷ lệ cao ở nhóm “former” phản ánh tác động lâu dài của thuốc lá lên mạch máu, trong khi nhóm “never” vẫn chịu ảnh hưởng từ tuổi, BMI hoặc di truyền.Các nhóm “current” và “ever” duy trì mức trên 23%, cho thấy hiệu ứng cộng hưởng giữa hút thuốc và tiểu đường đối với biến chứng tim mạch.

3.20 Phân tích tổng hợp: Tỷ lệ mắc tăng huyết áp, tiểu đường và bệnh tim theo tuổi và giới tính

summary_table <- data %>%
  group_by(age_group, gender) %>%
  summarise(Hypertension_Rate = mean(hypertension == "Có") * 100,Diabetes_Rate = mean(diabetes == "Có tiểu đường") * 100, Heart_Disease_Rate = mean(heart_disease == "Có") * 100,.groups = "drop") %>%
  arrange(age_group, gender)
knitr::kable(summary_table, caption = "Tỷ lệ mắc tăng huyết áp, tiểu đường và bệnh tim theo tuổi và giớ tính")
Tỷ lệ mắc tăng huyết áp, tiểu đường và bệnh tim theo tuổi và giớ tính
age_group gender Hypertension_Rate Diabetes_Rate Heart_Disease_Rate
Dưới 30 Female 0.317 0.862 0.033
Dưới 30 Male 0.376 0.823 0.050
Dưới 30 Other 0.000 0.000 0.000
30-44 Female 2.989 3.716 0.452
30-44 Male 5.176 4.912 0.843
30-44 Other 0.000 0.000 0.000
45-59 Female 9.096 9.338 2.035
45-59 Male 11.350 13.197 5.659
45-59 Other 0.000 0.000 0.000
60-74 Female 16.027 17.824 6.283
60-74 Male 16.938 22.657 14.508
75+ Female 20.127 17.929 11.989
75+ Male 17.412 21.411 21.883

Yếu tố kĩ thuật

  • Sử dụng group_by(age_group, gender) để phân nhóm theo độ tuổi và giới tính.
  • Dùng summarise() kết hợp với điều kiện logic (mean(hypertension == “Có”), mean(diabetes == “Có Tiểu Đường”), mean(heart_disease == “Có”)) nhân với 100 để tính tỷ lệ phần trăm mắc từng bệnh trong mỗi nhóm.
  • Sử dụng .groups = “drop” để trả về dataframe không giữ nhóm, giúp dễ dàng sắp xếp và trình bày.
  • Kết quả được hiển thị bằng knitr::kable() với caption rõ ràng, dễ đọc.
  • arrange(age_group, gender) sắp xếp giúp bảng kết quả trình bày tuần tự theo độ tuổi và giới tính

Kết luận

Phân tích cho thấy nguy cơ mắc tăng huyết áp, tiểu đường và bệnh tim tăng rõ rệt theo tuổi, đặc biệt từ nhóm trung niên (45–59 tuổi) trở lên. Ở nhóm này, nam giới có tỷ lệ mắc cao hơn nữ do căng thẳng nghề nghiệp, hút thuốc, uống rượu và ít vận động. Sang nhóm cao tuổi(60+), nữ có xu hướng mắc tăng huyết áp và tiểu đường cao hơn, trong khi nam lại chiếm ưu thế về bệnh tim – phản ánh sự khác biệt sinh học và tác động tích lũy của lối sống.

NỘI DUNG 4: TRỰC QUAN HÓA MÔ HÌNH

4.1 Biểu đồ phân bố theo nhóm tuổi

ggplot(data, aes(x = age)) +
  geom_histogram(aes(y = ..density..), bins = 30, fill = "#A6CEE3", color = "black", alpha = 0.6) + 
  geom_density(size = 1, color = "darkblue") +                                                       
  geom_rug(alpha = 0.2) +                                                                           
  geom_vline(xintercept = median(data$age, na.rm = TRUE), linetype = "dashed", color = "red") +      
  stat_ecdf(aes(y = ..y.. * max(density(data$age, na.rm=TRUE)$y)), geom = "step", color = "darkgreen") +
  labs( title = "Phân bố tuổi của người tham gia", x = "Tuổi", y = "Mật độ" ) +
  theme_minimal()

Yếu tố kĩ thuật

  • Dùng aes(x = age) làm trục hoành.
  • Geom_histogram(…, aes(y = ..density..)) vẽ biểu đồ tần suất dạng mật độ (thay vì số lượng), chia 30 cột.
  • Geom_density() thêm đường mật độ mượt để thấy dạng phân bố; geom_rug() hiển thị vạch nhỏ dưới trục X minh họa vị trí từng điểm dữ liệu.
  • Geom_vline(…median…) thêm đường dọc đỏ nét đứt tại trung vị tuổi.
  • Stat_ecdf() vẽ đường hàm phân phối tích lũy (ECDF) trên cùng trục.
  • Labs() đặt tiêu đề và nhãn trục; theme_minimal() giao diện tối giản, dễ nhìn.

Kết luận

Biểu đồ cho thấy phân bố tuổi lệch phải, đa đỉnh, với trung vị khoảng 45 tuổi, tập trung nhiều ở nhóm 45–60(cấp 1) và đỉnh cao nhất ở nhóm trên 75 tuổi(cấp 3). Điều này phản ánh hai giai đoạn then chốt trong dịch tễ học bệnh mạn tính: nhóm trung niên là giai đoạn khởi phát các rối loạn chuyển hóa, cần can thiệp phòng ngừa sớm (cấp 1), trong khi nhóm cao tuổi mang gánh nặng bệnh tật và biến chứng, cần quản lý chuyên sâu (cấp 3). Sự phân bố này cho thấy dữ liệu phù hợp để nghiên cứu chiến lược y tế công cộng đa tầng – từ phòng ngừa đến chăm sóc toàn diện.

4.2 Biểu đồ theo xu hướng tỷ lệ mắc tiểu đường qua các năm

diabetes_by_year <- data %>%
  group_by(year) %>%
  summarise(Rate = mean(diabetes == "Có tiểu đường") * 100,.groups = 'drop')
ggplot(diabetes_by_year, aes(x = year, y = Rate)) + 
  geom_line(linewidth = 1.5, color = "darkblue") +
  geom_point(size = 4, color = "red") +
  labs(title = "Xu hướng tỷ lệ tiểu đường qua các năm ",x = "Năm ghi nhận",y = "Tỷ lệ Mắc Tiểu đường (%)") +
  scale_x_continuous(breaks = unique(diabetes_by_year$year)) +
  theme_minimal(base_size = 14)

Yếu tố kĩ thuật

  • Group_by(year) gom nhóm dữ liệu theo năm ghi nhận.
  • Summarise(Rate = mean(diabetes == “Có tiểu đường”) * 100) tính tỷ lệ (%) người bị tiểu đường mỗi năm.
  • Geom_line() vẽ đường xu hướng của tỷ lệ tiểu đường theo năm; geom_point() thêm điểm dữ liệu cho từng năm.
  • Labs() → Đặt tiêu đề và nhãn trục cho biểu đồ; scale_x_continuous(breaks = unique(…)) hiển thị tất cả các năm có trong dữ liệu.
  • Theme_minimal(base_size = 14) áp dụng giao diện tối giản, chữ rõ ràng, dễ đọc

Kết luận

Biểu đồ cho thấy tỷ lệ mắc tiểu đường ổn định dưới 10% giai đoạn 2015–2020, sau đó tăng vọt lên 14,5% năm 2021 và đạt đỉnh 25% năm 2022. Sự bùng phát này có thể bắt nguồn từ hai nguyên nhân: (1) tác động hậu COVID-19 làm thay đổi lối sống và sức khỏe chuyển hóa; hoặc (2) thay đổi trong phương pháp sàng lọc và tiêu chuẩn chẩn đoán.

4.3 Biểu đồ top 10 khu vực có tỷ lệ tiểu đường cao nhất

top_10_locations <- data %>%
  group_by(location) %>%
  summarise(Rate = mean(diabetes == "Có tiểu đường") * 100, Count = n(), .groups = 'drop') %>%
  arrange(desc(Rate)) %>%
  filter(Count > 1000) %>% 
  head(10) %>%
  mutate(location = factor(location, levels = location[order(Rate)]))
ggplot(top_10_locations, aes(x = location, y = Rate, fill = Rate)) +
  geom_col() +
  geom_text(aes(label = paste0(round(Rate, 1), "%")), hjust = 0.5, size = 4, color = "black") + 
  labs(title = "Top 10 khu vực mắc tiểu đường",subtitle = "(Chỉ tính các khu vực có số lượng mẫu > 1000)",x = "Mã viết tắt khu vực",y = "Tỷ lệ mắc tiểu đường (%)") +
  coord_flip() +
  scale_fill_gradient(low = "yellow", high = "red") +
  theme_bw(base_size = 14) + 
  theme(legend.position = "none",plot.title = element_text(hjust = 0, face = "bold"),plot.subtitle = element_text(hjust = 0),
    plot.margin = unit(c(1, 0.5, 0.5, 0.5), "cm"))

Yếu tố kĩ thuật

  • Group_by(location) gom dữ liệu theo khu vực.
  • Summarise(Rate = mean(..) tính tỷ lệ tiểu đường (%) và số mẫu mỗi khu vực; arrange(desc(Rate)) sắp xếp giảm dần theo tỷ lệ tiểu đường.
  • Filter(Count > 1000) chỉ giữ các khu vực có >1000 mẫu; head(10) lấy 10 khu vực có tỷ lệ cao nhất.
  • Mutate(location = factor(..) chuyển location thành factor để hiển thị đúng thứ tự khi vẽ.
  • Ggplot(…, aes(..)) thiết lập trục x, y và màu theo tỷ lệ; geom_col() vẽ biểu đồ cột; geom_text(aes…) hiển thị nhãn tỷ lệ trên cột.
  • Labs(…) thêm tiêu đề, phụ đề, nhãn trục; coord_flip() đổi trục x và y để cột nằm ngang; scale_fill_gradient(low=“yellow”, high=“red”) màu gradient từ vàng → đỏ theo tỷ lệ; theme_bw(base_size=14) + theme(…) định dạng giao diện, tắt legend, căn chỉnh tiêu đề, lề biểu đồ.

Kết luận

Biểu đồ cho thấy sự phân bố không đồng đều của bệnh tiểu đường giữa các bang, với Delaware, Kansas, Illinois, Montana và West Virginia dẫn đầu (khoảng 9,5–9,8%). Điều này phản ánh rằng nguy cơ mắc bệnh chịu tác động đa chiều, không chỉ từ yếu tố sinh học mà còn từ kinh tế – xã hội và lối sống. Các bang có SES thấp như West Virginia cho thấy mối liên hệ giữa điều kiện sống khó khăn, hạn chế dinh dưỡng và tiếp cận y tế với sức khỏe chuyển hóa kém. Trong khi đó, các bang Trung Tây và nông nghiệp như Kansas, Illinois, Montana lại chịu ảnh hưởng từ chế độ ăn giàu calo và mức vận động thấp.

4.4 Biểu đồ đường: Chỉ số BMI theo thứ tự quan sát

data$index <- 1:nrow(data)
plot(data$index,data$bmi,type = "l", col = "darkblue", main = "Xu hướng chỉ số BMI theo thứ tự bệnh nhân", xlab = "Thứ tự quan sát", ylab = "Chỉ số BMI (kg/m²)")          

Yếu tố kĩ thuật

  • Tạo cột index đánh số thứ tự cho từng quan sát dùng data$index <- 1:nrow(data)
  • Vẽ đồ thị đường (line chart) biểu diễn chỉ số BMI theo thứ tự bệnh nhân dùng plot(…, type = “l”) , col = “darkblue” màu đường là xanh đậm.
  • Main, xlab, ylab đặt tiêu đề biểu đồ và nhãn trục X, Y.

Kết luận

Phân tích dữ liệu BMI với khoảng 100.000 quan sát cho thấy chỉ số này biến động mạnh giữa các cá nhân, phản ánh sự đa dạng về sức khỏe trong quần thể. Không phát hiện xu hướng tăng hay giảm theo thứ tự quan sát, nên trình tự không phải là yếu tố dự báo đáng kể. Phân bố BMI chủ yếu trong khoảng 20–60 kg/m², xuất hiện một số ngoại lai vượt 80–90 kg/m² — dấu hiệu của béo phì cấp độ III cần can thiệp y tế.

4.5 Biểu đồ hộp: So sánh HbA1c giữa người mắc và không mắc tiểu đường

boxplot(hbA1c_level ~ diabetes, data = data,main = "So sánh HbA1c theo tình trạng tiểu đường", xlab = "Tình trạng tiểu đường", ylab = "Mức HbA1c (%)", col = c("lightblue", "orange"))

Yếu tố kĩ thuật

  • Boxplot(hbA1c_level ~ diabetes, data = data) vẽ biểu đồ hộp so sánh mức HbA1c giữa các nhóm có và không tiểu đường.
  • Main, xlab, ylab đặt tiêu đề và nhãn trục X, Y; col = c(“lightblue”, “orange”) tô màu hai nhóm bằng xanh nhạt và cam.

Kết luận

Biểu đồ hộp cho thấy mức HbA1c trung vị của nhóm “Có tiểu đường” (≈6.6–6.7%) cao hơn rõ rệt so với nhóm “Không tiểu đường” (≈5.8%), phản ánh mức đường huyết trung bình cao hơn trong 2–3 tháng gần nhất. Nhóm mắc bệnh có IQR rộng hơn (6.2–7.5%), thể hiện sự khác biệt lớn trong khả năng kiểm soát đường huyết — một số kiểm soát tốt, trong khi số khác có HbA1c tới 9.0%. Ngược lại, nhóm không mắc bệnh có phân bố hẹp hơn (dưới 6.5%).

4.6 Biểu đồ tương quan log-log: tuổi và đường huyết

data_clean <- data %>% filter(age > 0, blood_glucose_level > 0)
plot(log(data_clean$age), log(data_clean$blood_glucose_level), main = "Mối quan hệ giữa tuổi và đường huyết", xlab = "log(Tuổi)",ylab = "log(Mức đường huyết)",col = "darkred",pch = 19)
abline(lm(log(blood_glucose_level) ~ log(age), data = data_clean),col = "red",lwd = 3)

Yếu tố kĩ thuật

  • Filter(age > 0, blood_glucose_level > 0) loại bỏ giá trị âm hoặc bằng 0 của tuổi và đường huyết.
  • Plot(log(age), log(blood_glucose_level)) vẽ biểu đồ phân tán giữa log tuổi và log mức đường huyết.
  • abline(lm(…)) thêm đường hồi quy tuyến tính (màu đỏ, nét dày) thể hiện xu hướng quan hệ giữa hai biến.

Kết luận

Biểu đồ log-log plot cho thấy mối tương quan dương yếu giữa \(\log(\text{Tuổi})\)\(\log(\text{Mức đường huyết})\), phản ánh xu hướng tăng nhẹ đường huyết theo tuổi. Tuy nhiên, sự phân tán rộng và hiện tượng dữ liệu bị phân loại hoặc làm tròn cho thấy mô hình hồi quy tuyến tính đơn không đủ mạnh để giải thích biến thiên của đường huyết.

4.7 Biểu đồ cột về số lượng bệnh nhân theo nhóm tuổi & tình trạng tiểu đường

data <- data %>%
  mutate(age_group = cut(age,breaks = c(-Inf, 29, 44, 59, 74, Inf), labels = c("Dưới 30", "30-44", "45-59", "60-74", "75+"),right = TRUE)) %>%
  filter(!is.na(age_group)) %>%
  mutate(age_group = factor(age_group,levels = c("Dưới 30", "30-44", "45-59", "60-74", "75+")))
ggplot(data, aes(x = age_group, fill = diabetes)) +
  geom_bar(position = "dodge") +
  geom_text(stat = "count", aes(label = after_stat(count)),position = position_dodge(width = 0.9), vjust = -0.3, size = 3) +
  scale_fill_manual(values = c("Có tiểu đường" = "#D55E00","Không tiểu đường" = "#56B4E9")) +
  labs(title = "Số bệnh nhân theo tuổi và tình trạng tiểu đường", x = "Nhóm tuổi",y = "Số lượng bệnh nhân") +
  theme_minimal(base_family = "Arial")  

Yếu tố kĩ thuật

  • Chia biến tuổi thành 5 nhóm tuổi (“Dưới 30”, “30–44”, … “75+”) dùng mutate(cut(…))
  • Loại bỏ giá trị thiếu nhóm tuổi bằng filter(!is.na(age_group)); factor(…, levels=…) sắp xếp thứ tự hiển thị nhóm tuổi.
  • Vẽ biểu đồ cột so sánh số bệnh nhân theo nhóm tuổi và tình trạng tiểu đường dùng ggplot(…) + geom_bar(position=“dodge”)
  • Geom_text(…) hiển thị số lượng bệnh nhân trên mỗi cột.
  • Scale_fill_manual(…) gán màu riêng cho 2 nhóm; labs(…), theme_minimal(…) đã giải thích ở 4.1

Kết luận

Kết quả phân tích cho thấy tuổi tác ảnh hưởng rõ rệt đến nguy cơ mắc tiểu đường. Ở nhóm dưới 30 tuổi, tỷ lệ mắc bệnh rất thấp do cơ thể còn khỏe mạnh và khả năng điều hòa đường huyết tốt. Từ 30–44 tuổi, tỷ lệ mắc bắt đầu tăng nhẹ, phản ánh tác động ban đầu của lối sống và dinh dưỡng. Giai đoạn 45–74 tuổi ghi nhận tỷ lệ mắc cao nhất, khi khả năng chuyển hóa giảm và các yếu tố nguy cơ như ít vận động, ăn uống mất cân đối, căng thẳng tích lũy rõ rệt hơn. Ở nhóm trên 75 tuổi, số ca mắc giảm nhẹ do quy mô dân số nhỏ hơn và yếu tố tuổi thọ

4.8 Biểu đồ cột ngang: Phân bố tần suất và tỷ lệ của biến tiền sử hút thuốc

smoking_freq_table <- data %>%
  filter(smoking_history != "No Info") %>% 
  count(smoking_history, name = "Count") %>%
  mutate(`Tỷ lệ (%)` = round(100 * Count / sum(Count), 1)) %>%
  rename(`Tiền sử hút thuốc` = smoking_history) %>%
  mutate(`Tiền sử hút thuốc` = factor(`Tiền sử hút thuốc`, levels = `Tiền sử hút thuốc`[order(Count)]))
ggplot(smoking_freq_table, aes(x = `Tiền sử hút thuốc`, y = Count, fill = `Tiền sử hút thuốc`)) + 
  geom_col(width = 0.6) + 
  coord_flip() + 
  geom_text(aes(label = Count), hjust = 0, size = 4) + 
  labs(title = "Tần suất tiền sử hút thuốc",x = "Tiền sử hút thuốc",y = "Số lượng (Count)") +
  scale_y_continuous(expand = expansion(mult = c(0, 0.15))) + 
  theme_minimal() + theme(legend.position = "none",plot.title = element_text(hjust = 0.5, face = "bold"))

Yếu tố kĩ thuật

  • Loại bỏ giá trị thiếu thông tin hút thuốc (filter(smoking_history != “No Info”))
  • Count(smoking_history, name=“Count”) đếm số người theo từng nhóm hút thuốc; mutate(…Tỷ lệ (%)…) tính tỷ lệ phần trăm từng nhóm.
  • Rename(…) đổi tên cột hiển thị thành “Tiền sử hút thuốc”; factor(…, levels=…) sắp xếp thứ tự nhóm theo số lượng tăng dần.
  • Ggplot(…) + geom_col(width=0.6) vẽ cột ngang thể hiện số lượng từng nhóm; coord_flip() đảo trục x và y để cột nằm ngang.
  • Geom_text(aes(label=Count), hjust=0, size=4) hiển thị số lượng trên cột; labs(…) thêm tiêu đề và nhãn trục
  • Scale_y_continuous(expand=…) giãn khoảng trống trên trục y để chữ không bị chồng; theme_minimal() + theme(…) → Giao diện tối giản, ẩn legend, căn giữa tiêu đề và in đậm.

Kết luận

Biểu đồ cho thấy phân bố thói quen hút thuốc trong mẫu nghiên cứu. Nhóm “never” chiếm tỷ lệ cao nhất với 35.094 người, cho thấy phần lớn đối tượng không hút thuốc. Hai nhóm “former” và “current” có quy mô tương đương, lần lượt 9.352 và 9.286 người, phản ánh một bộ phận dân số có hoặc từng có hành vi hút thuốc. Nhóm “ever” chiếm tỷ lệ nhỏ (4.004 người), trong khi “no info” lại rất lớn (35.083 người), có thể ảnh hưởng đến độ tin cậy khi phân tích.

4.9 Biểu đồ đường (Line Plot): Xu hướng BMI và HbA1c theo tuổi

df_trend <- data %>%
  select(age, bmi, hbA1c_level, diabetes) %>%
  group_by(age, diabetes) %>%
  summarise(Avg_BMI = mean(bmi, na.rm = TRUE),Avg_HbA1c = mean(hbA1c_level, na.rm = TRUE),.groups = "drop" )
df_trend_long <- df_trend %>%
  pivot_longer(cols = c(Avg_BMI, Avg_HbA1c),names_to = "Chi_so",values_to = "Gia_tri_trung_binh")
plot_trend <- ggplot(df_trend_long, aes(x = age, y = Gia_tri_trung_binh, color = diabetes)) +
  geom_line(linewidth = 1) +
  geom_point(alpha = 0.5, size = 1.5) +
  facet_wrap(~ Chi_so,scales = "free_y",labeller = as_labeller(c(Avg_BMI = "Chỉ số BMI",Avg_HbA1c = "Mức HbA1c"))) +
  labs(title = "Xu hướng BMI & HbA1c theo độ tuổi",x = "Tuổi (Age)",y = "Giá trị trung bình",color = "diabetes") +
  scale_color_manual(values = c("Không tiểu đường" = "#1F77B4", "Có tiểu đường" = "#D62728")) +
  theme_minimal(base_size = 14) +
  theme(
    plot.title = element_text(hjust = 0.5, face = "bold", size = 16),
    legend.position = "top",
    legend.title = element_text(face = "bold", size = 13),
    legend.text = element_text(size = 12),
    strip.text = element_text(face = "bold", size = 13),
    axis.title = element_text(face = "bold"),
    plot.margin = margin(t = 20, r = 15, b = 15, l = 15))
plot_trend

Yếu tố kĩ thuật

  • Select(age, bmi, hbA1c_level, diabetes) chọn các cột cần phân tích; group_by(age, diabetes) gom dữ liệu theo tuổi và tình trạng tiểu đường.
  • Summarise(Avg_BMI = ..) tính BMI và HbA1c trung bình cho mỗi nhóm.; pivot_longer(cols = c(..)“) chuyển dữ liệu từ dạng rộng sang dài để vẽ nhiều biến trên cùng biểu đồ.
  • Ggplot(…, aes(x=age,..)) thiết lập trục x, y và màu theo tình trạng tiểu đường; geom_line(linewidth=1) + geom_point(alpha=0.5, size=1.5) vẽ đường xu hướng và các điểm dữ liệu.
  • Facet_wrap(~Chi_so, scales=“free_y”, labeller=…) tạo 2 biểu đồ riêng cho BMI và HbA1c với trục y độc lập.
  • Labs(…), theme_minimal(…) đã giải thích ở 4.1; scale_color_manual(…) chỉ định màu sắc cho từng nhóm.

Kết luận

Chỉ số BMI tăng dần từ tuổi trẻ đến khoảng 40–50 rồi giảm nhẹ ở tuổi cao. Nhóm có tiểu đường (màu đỏ) luôn có BMI cao hơn rõ rệt (khoảng 33–35) so với nhóm không tiểu đường (khoảng 27–29). Mức HbA1c ở nhóm có tiểu đường duy trì ổn định quanh mức 7% trở lên, trong khi nhóm không tiểu đường ổn định ở khoảng 5%. Sự khác biệt ổn định giữa hai nhóm khẳng định rằng thừa cân – béo phì và kiểm soát đường huyết kém là hai đặc trưng lâm sàng điển hình của bệnh tiểu đường type 2. Kết quả này nhấn mạnh tầm quan trọng của duy trì BMI hợp lý và theo dõi HbA1c định kỳ trong chiến lược phòng ngừa và quản lý bệnh.

4.10 Biểu đồ Cột: Tần suất mắc bệnh tim và tăng huyết áp ở khu vực USA

disease_counts_usa_filtered <- data %>%
  select(location, hypertension, heart_disease) %>%
  pivot_longer(cols = c(hypertension, heart_disease),names_to = "Disease", values_to = "Status") %>%
  filter(Status == "Có") %>%
  mutate(Disease = case_when(Disease == "hypertension" ~ "Tăng huyết áp",Disease == "heart_disease" ~ "Bệnh tim",TRUE ~ Disease)) %>%
  count(Disease, name = "Count")
max_count <- max(disease_counts_usa_filtered$Count)
ggplot(disease_counts_usa_filtered, aes(x = Disease, y = Count, fill = Disease)) +
  geom_col() +
  geom_text(aes(label = Count),vjust = -0.5, size = 5, fontface = "bold") + scale_y_continuous(limits = c(0, max_count * 1.10)) + 
  labs(title = "Tần suất bệnh tim & tăng huyết áp ",x = "Bệnh lý",y = "Số lượng bệnh nhân") +
  scale_fill_manual(values = c("Tăng huyết áp" = "#0072B2", "Bệnh tim" = "#D55E00")) +
  theme_minimal(base_size = 14) + 
  theme(legend.position = "none",plot.margin = unit(c(2.5, 0.5, 0.5, 2.0), "cm"),plot.title = element_text(hjust = 0.5, face = "bold", size = 16), axis.title.y = element_text(size = 14, margin = margin(r = 15)))

Yếu tố kĩ thuật

  • Dữ liệu được chọn ba biến: location, hypertension, heart_disease.
  • Sử dụng pivot_longer() để chuyển hai biến bệnh nền sang dạng dài
  • Lọc các quan sát mắc bệnh (filter()) và đổi nhãn bệnh sang tiếng việt (mutate..())
  • Xác định giá trị lớn nhất (max_count) để mở rộng trục Y 10% cho biểu đồ tránh nhãn bị tràn.
  • Biểu đồ vẽ bằng ggplot2: geom_col() tạo cột chiều cao theo số lượng bệnh nhân; geom_text() hiển thị nhãn số lượng trên đỉnh cột ; in đậm (fontface = “bold”) để trực quan.
  • Scale_fill_manual() gán màu riêng cho từng bệnh: Tăng huyết áp → xanh dương (#0072B2) Bệnh tim → cam (#D55E00).
  • Biểu đồ sử dụng theme_minimal(), ẩn chú giải (legend.position = “none”), căn giữa tiêu đề (hjust = 0.5), làm đậm và tăng kích thước tiêu đề, đồng thời điều chỉnh lề và trục Y

Kết luận

Biểu đồ cho thấy sự chênh lệch rõ rệt giữa hai nhóm bệnh lý tim mạch: 7.485 người mắc tăng huyết áp, gần gấp đôi 3.942 người mắc bệnh tim. Điều này phản ánh thực tế rằng tăng huyết áp là bệnh phổ biến và là yếu tố khởi đầu của nhiều biến chứng nghiêm trọng như tim mạch, đột quỵ hay suy thận. Mặc dù số ca bệnh tim thấp hơn, nhưng vẫn chiếm tỷ lệ đáng kể, cho thấy gánh nặng tim mạch vẫn hiện hữu trong cộng đồng.

4.11 Biểu đồ Histogram: phân phối tuổi trong tập dữ liệu

df_age_hist <- data %>%
  select(age) %>%
  na.omit()
num_bins <- 15
bin_width_age <- (max(df_age_hist$age) - min(df_age_hist$age)) / num_bins
ggplot(df_age_hist, aes(x = age)) +
  geom_histogram(aes(y = after_stat(density)),binwidth = 5, fill = "#2C3E50", color = "white", alpha = 0.7) +
  geom_density(color = "#E74C3C", linewidth = 1.2) + 
  geom_vline(xintercept = mean(df_age_hist$age), color = "#27AE60", linetype = "dashed", linewidth = 1) +
  labs(title = "Phân phối nhóm tuổi", x = "Tuổi (Age)", y = "Mật độ (Density)") +
  theme_minimal(base_size = 15) + 
  theme(plot.title = element_text(hjust = 0.5, face = "bold"))

Yếu tố kĩ thuật

  • Dữ liệu được chọn biến age và loại bỏ giá trị thiếu bằng na.omit().
  • Xác định số lượng bin (15) và chiều rộng bin dựa trên khoảng tuổi để tạo histogram.
  • Biểu đồ được vẽ bằng ggplot2 kết hợp: geom_histogram(aes(y = after_stat(density))) vẽ histogram chuẩn hóa theo mật độ, với màu cột xanh đậm (#2C3E50), viền trắng và alpha 0.7 để tăng trực quan; geom_density() vẽ đường mật độ kernel màu đỏ (#E74C3C) để quan sát xu hướng phân phối liên tục; geom_vline() thêm đường trung bình tuổi màu xanh lá (#27AE60), kẻ đứt, giúp nhận diện vị trí trung tâm của phân phối.
  • Biểu đồ sử dụng theme_minimal(), căn giữa tiêu đề (hjust = 0.5) và làm đậm (face = “bold”)

Kết luận

Biểu đồ phân phối độ tuổi cho thấy mẫu nghiên cứu bao gồm nhiều nhóm tuổi, với độ tuổi trung bình khoảng 40 (đường xanh lá). Đường mật độ màu đỏ cho thấy phân bố không hoàn toàn chuẩn, tập trung chủ yếu ở nhóm 30–60 tuổi – giai đoạn dễ mắc các bệnh mạn tính như tăng huyết áp, tiểu đường, bệnh tim. Nhóm dưới 20 và trên 70 tuổi chiếm tỷ lệ nhỏ, phản ánh đối tượng nghiên cứu chủ yếu là người trưởng thành.

4.12 Biểu đồ Violin Plot: Phân bố chỉ số BMI giữa nam và nữ

library(tidyverse)
df_bmi_gender <- data %>%
  select(gender, bmi) %>%
  na.omit() %>%
  mutate(gender = factor(gender, levels = c("Female", "Male"), labels = c("Nữ", "Nam")))
ggplot(df_bmi_gender, aes(x = gender, y = bmi, fill = gender)) +
  geom_violin(trim = FALSE, alpha = 0.6) + 
  geom_boxplot(width = 0.15, fill = "white", alpha = 0.8, outlier.shape = NA) + 
  labs(title = "Phân bố chỉ số BMI giữa nam và nữ",x = "Giới tính",y = "Chỉ số Khối cơ thể (BMI)",fill = "Giới tính") +
  scale_fill_manual(values = c("Nữ" = "#E69F00", "Nam" = "#56B4E9")) + 
  theme_minimal(base_size = 15) + 
  theme(legend.position = "none", plot.title = element_text(hjust = 0.5, face = "bold"))

Yếu tố kĩ thuật

  • Dữ liệu được chọn hai biến: gender và bmi, sau đó loại bỏ giá trị thiếu bằng na.omit().
  • Biến gender được chuyển thành factor với nhãn tiếng việt: “Nữ” và “Nam” để hiển thị trực quan trên biểu đồ.
  • Biểu đồ được vẽ bằng ggplot2 kết hợp: geom_violin(trim = FALSE) thể hiện phân phối dữ liệu BMI cho từng giới tính, giữ toàn bộ dạng phân phối (không cắt bớt). geom_boxplot() đặt bên trong violin để hiển thị thống kê tóm tắt (trung vị, tứ phân vị), loại bỏ điểm ngoại lai (outlier.shape = NA) để biểu đồ gọn hơn.
  • Màu sắc cột được định nghĩa bằng scale_fill_manual(): Nữ → cam (#E69F00) Nam → xanh (#56B4E9).
  • Biểu đồ sử dụng theme_minimal(), ẩn chú giải (legend.position = “none”), căn giữa tiêu đề (hjust = 0.5) và in đậm (face = “bold”)

Kết luận

Biểu đồ violin cho thấy phân bố chỉ số BMI giữa nam và nữ khá tương đồng, tập trung chủ yếu trong khoảng 25–30, phản ánh tình trạng thừa cân nhẹ phổ biến trong mẫu dữ liệu. Nam giới có phạm vi biến động rộng hơn, cho thấy sự dao động BMI lớn hơn nữ – có thể do khác biệt về thể hình hoặc lối sống. Một số ngoại lai BMI cao xuất hiện nhiều hơn ở nam. Nhóm NA có phân bố chưa rõ do mẫu nhỏ.

4.13 Biểu đồ Violin Plot: Phân phối mức HbA1c theo mức độ tiểu đường

df_hba1c_diabetes <- data %>%
  select(hbA1c_level, diabetes) %>%
  na.omit() %>%
  mutate(diabetes = factor(diabetes, levels = c("Không tiểu đường", "Có tiểu đường")))
nguong_tien_tieu_duong_hba1c <- 5.7
nguong_tieu_duong_hba1c <- 6.5
ggplot(df_hba1c_diabetes, aes(x = diabetes, y = hbA1c_level, fill = diabetes)) +
  geom_rect(aes(xmin = -Inf, xmax = Inf, ymin = nguong_tien_tieu_duong_hba1c, ymax = nguong_tieu_duong_hba1c - 0.1), fill = "#FEE08B", alpha = 0.5, inherit.aes = FALSE) +
  geom_rect(aes(xmin = -Inf, xmax = Inf, ymin = nguong_tieu_duong_hba1c, ymax = Inf), fill = "#F46D43", alpha = 0.5, inherit.aes = FALSE) +
  geom_violin(trim = FALSE, alpha = 0.6) +
  geom_boxplot(width = 0.15, fill = "white", alpha = 0.8, outlier.shape = NA) + 
  labs(title = "Phân phối HbA1c theo tiểu đường",x = "Mức độ tiểu đường",y = "Mức HbA1c (%)") +
  scale_fill_manual(values = c("Không tiểu đường" = "#1B9E77", "Có tiểu đường" = "#D95F02")) + 
  theme_minimal(base_size = 15) + theme(legend.position = "none", plot.title = element_text(hjust = 0.5, face = "bold"))

Yếu tố kĩ thuật

  • Dữ liệu được chọn hai biến: hbA1c_level và diabetes, sau đó loại bỏ giá trị thiếu bằng na.omit().
  • Biến diabetes được chuyển thành factor với thứ tự: “Không tiểu đường” → “Có tiểu đường”, để biểu đồ hiển thị logic theo nhóm.
  • Xác định ngưỡng HbA1c: 5.7% → tiền tiểu đường, 6.5% → tiểu đường, dùng geom_rect() để tô màu vùng tương ứng trên biểu đồ.
  • Biểu đồ sử dụng geom_violin() thể hiện phân phối dữ liệu HbA1c, kết hợp với geom_boxplot() đặt phía trong violin để hiển thị đặc điểm tóm tắt trung vị, tứ phân vị và loại bỏ điểm ngoại lai (outlier.shape = NA).
  • Nhãn ngưỡng HbA1c được thêm bằng geom_text() để trực quan hóa giới hạn phân loại bệnh.
  • Màu sắc được định nghĩa bằng scale_fill_manual():“Không tiểu đường” → xanh (#1B9E77); “Có tiểu đường” → cam (#D95F02).
  • Biểu đồ dùng theme_minimal(), loại bỏ chú giải (legend.position = “none”) và căn giữa tiêu đề (hjust = 0.5) với font đậm (face = “bold”)

Kết luận

Biểu đồ cho thấy sự khác biệt rõ rệt về mức HbA1c giữa hai nhóm “Có tiểu đường” và “Không tiểu đường”. Nhóm không tiểu đường có HbA1c tập trung trong khoảng 4–6%, trung vị khoảng 5,5%, phản ánh đường huyết ổn định. Ngược lại, nhóm có tiểu đường có HbA1c dao động 6,5–8%, nhiều trường hợp vượt 8%, cho thấy kiểm soát đường huyết kém và biến động cao. Phần lớn người bệnh nằm trong vùng đỏ (tiểu đường), trong khi nhóm bình thường tập trung ở vùng xanh – vàng, phù hợp với tiêu chuẩn chẩn đoán y tế và khẳng định HbA1c là chỉ báo đáng tin cậy để phân biệt hai nhóm.

4.14 Biểu đồ cột ngang: Tỷ lệ tiền sử hút thuốc trong mẫu nghiên cứu

smoking_data <- data %>%
  filter(smoking_history != "No Info") %>%
  count(smoking_history) %>%
  mutate(percent = round(100 * n / sum(n), 1),labels = paste0(percent, "%"),smoking_history = fct_reorder(smoking_history, n) )
ggplot(smoking_data, aes(x = smoking_history, y = percent, fill = smoking_history)) +
  geom_col() +
  geom_text(aes(label = labels),hjust = -0.2,size = 4, fontface = "bold") +
  coord_flip() +
  scale_y_continuous(limits = c(0, max(smoking_data$percent) * 1.15)) +
  labs(title = "Phân bố mẫu theo tiền sử hút thuốc", x = "Tiền sử hút thuốc", y = "Tỷ lệ (%)") +
  theme_minimal(base_size = 14) +
  theme(legend.position = "none",plot.title = element_text(hjust = 0))

Yếu tố kĩ thuật

  • Dữ liệu được lọc loại bỏ giá trị không xác định (smoking_history != “No Info”); sử dụng count(smoking_history) để đếm số quan sát trong từng nhóm hút thuốc.
  • Tính tỷ lệ phần trăm (%) cho mỗi nhóm bằng mutate(percent roung..) và tạo nhãn hiển thị (labels = paste0(percent, “%”)).
  • Dữ liệu được sắp xếp theo số lượng (fct_reorder()) để các cột biểu đồ hiển thị theo thứ tự từ thấp đến cao.
  • Biểu đồ được vẽ bằng ggplot2: geom_col() tạo cột dọc biểu diễn tỷ lệ phần trăm từng nhóm; geom_text() hiển thị nhãn tỷ lệ trên cột, in đậm để dễ quan sát; coord_flip() xoay biểu đồ để cột nằm ngang, giúp đọc tên nhóm dễ hơn.
  • Trục Y được mở rộng 15% so với giá trị lớn nhất (scale_y_continuous(limits = c(0, max(…) * 1.15))) để tránh nhãn bị tràn.
  • Biểu đồ dùng theme_minimal(), loại bỏ chú giải (legend.position = “none”) và căn tiêu đề sang trái (hjust = 0).

Kết luận

Kết quả thống kê cho thấy phân bố mẫu theo biến “Tiền sử hút thuốc” có sự chênh lệch đáng kể. Nhóm không cung cấp thông tin chiếm tỷ lệ cao nhất (35,8%), phản ánh tình trạng thiếu dữ liệu phổ biến. Tiếp theo là nhóm chưa từng hút thuốc (35,1%), cho thấy phần lớn mẫu có lối sống lành mạnh. Hai nhóm đã bỏ thuốc và đang hút thuốc chiếm tỷ lệ gần tương đương (9,4% và 9,3%), trong khi nhóm không còn hút (6,4%) và đã từng hút (4%) chiếm tỷ lệ nhỏ.

4.15 Biểu đồ đường: Tỷ lệ mắc bệnh nền theo tuổi

df_prevalence_age <- data %>%
  select(age, hypertension, heart_disease) %>%
  na.omit() %>%
  mutate(hypertension_binary = ifelse(hypertension == "Có", 1, 0), heart_disease_binary = ifelse(heart_disease == "Có", 1, 0)) %>%
  group_by(age) %>%
  summarise(Prevalence_HTN = mean(hypertension_binary) * 100, Prevalence_HD = mean(heart_disease_binary) * 100,  groups = 'drop')
library(tidyr)
df_prevalence_long <- df_prevalence_age %>%
  pivot_longer(cols = starts_with("Prevalence"),names_to = "Benh_ly", values_to = "Ty_le_mac_benh") %>%
  mutate(Benh_ly = factor(Benh_ly,levels = c("Prevalence_HTN", "Prevalence_HD"), labels = c("Tăng huyết áp", "Bệnh tim")))
ggplot(df_prevalence_long, aes(x = age, y = Ty_le_mac_benh, color = Benh_ly)) +
  geom_line(linewidth = 1.2) +
  labs(title = "Xu hướng tỷ lệ bệnh nền theo tuổi",x = "Tuổi (Age)",y = "Tỷ lệ mắc bệnh (%)",color = "Bệnh lý") +
  scale_color_manual(values = c("Tăng huyết áp" = "#E74C3C", "Bệnh tim" = "#2ECC71")) +
  theme_minimal(base_size = 15) +
  theme(plot.title = element_text(hjust = 0.5, face = "bold"),legend.position = "top")

Yếu tố kĩ thuật

  • Dữ liệu được chọn ba biến: age, hypertension, heart_disease, sau đó loại bỏ giá trị thiếu bằng na.omit().
  • Chuyển hai biến bệnh nền sang dạng nhị phân (1 = Có, 0 = Không) bằng ifelse() để tính tỷ lệ phần trăm.
  • Sử dụng group_by(age) và summarise() để tính tỷ lệ mắc tăng huyết áp (Prevalence_HTN) và bệnh tim (Prevalence_HD) theo từng tuổi, nhân 100 để biểu thị phần trăm.
  • Dữ liệu được chuyển sang dạng dài bằng pivot_longer() để thuận tiện vẽ nhiều đường trên cùng một biểu đồ, đồng thời đổi nhãn factor để hiển thị tên bệnh lý rõ ràng: “Tăng huyết áp” và “Bệnh tim”.
  • Biểu đồ được vẽ bằng ggplot2: geom_line() thể hiện xu hướng tỷ lệ mắc bệnh nền theo tuổi. scale_color_manual() gán màu sắc riêng cho từng bệnh nền: Tăng huyết áp → đỏ (#E74C3C) Bệnh tim → xanh lá (#2ECC71).
  • Biểu đồ sử dụng theme_minimal(), căn giữa tiêu đề (hjust = 0.5) và làm đậm tiêu đề (face = “bold”), đồng thời đặt chú giải lên trên (legend.position = “top”) để tăng tính trực quan học thuật.

Kết luận

Biểu đồ cho thấy tỷ lệ mắc tăng huyết áp và bệnh tim đều tăng dần theo tuổi, đặc biệt rõ sau độ tuổi 40. Nhóm tăng huyết áp (đường đỏ) luôn có tỷ lệ mắc cao hơn bệnh tim (đường xanh) ở mọi độ tuổi, và khoảng cách này mở rộng rõ rệt ở người trung niên và cao tuổi. Điều này phản ánh rằng tăng huyết áp thường xuất hiện sớm hơn và là yếu tố nguy cơ chính dẫn đến bệnh tim.

4.16 Biểu đồ: Mức BMI trung bình theo lịch sử hút thuốc

df_lollipop <- data %>%
  select(bmi, smoking_history) %>%
  na.omit() %>%
  group_by(smoking_history) %>%
  summarise(Avg_BMI = mean(bmi),.groups = 'drop') %>%
  arrange(Avg_BMI) %>%
  mutate(smoking_history = factor(smoking_history, levels = smoking_history))
ggplot(df_lollipop, aes(x = Avg_BMI, y = smoking_history)) +
  geom_segment(aes(x = 0, xend = Avg_BMI, y = smoking_history, yend = smoking_history),color = "#A9CCE3", linewidth = 1.5) +
  geom_point(size = 4, color = "#1A5276") +
  geom_text(aes(label = round(Avg_BMI, 2)), hjust = -0.5, size = 4.5, fontface = "bold", color = "#1A5276") +
  labs(title = "Mức BMI theo lịch sử hút thuốc",x = "BMI trung bình (kg/m²)",y = "Lịch sử hút thuốc") +
  theme_minimal(base_size = 15) +
  theme(panel.grid.major.y = element_blank(), panel.grid.minor.x = element_blank(),plot.title = element_text(hjust = 0.5, face = "bold")) +
  scale_x_continuous(limits = c(0, max(df_lollipop$Avg_BMI) * 1.2))

Yếu tố kĩ thuật

  • Dữ liệu được chọn hai biến: bmi và smoking_history, sau đó loại bỏ các giá trị thiếu bằng na.omit().
  • Sử dụng group_by(smoking_history) và summarise() để tính BMI trung bình (Avg_BMI) cho từng nhóm hút thuốc.
  • Dữ liệu được sắp xếp tăng dần theo Avg_BMI và chuyển smoking_history thành factor có thứ tự, đảm bảo biểu đồ hiển thị đúng thứ tự từ thấp đến cao.
  • Biểu đồ lollipop chart được vẽ bằng ggplot2: geom_segment() tạo các thanh ngang từ gốc đến giá trị trung bình BMI. geom_point() đánh dấu giá trị trung bình bằng điểm tròn ở đầu thanh. geom_text() hiển thị nhãn số BMI, làm tròn 2 chữ số, in đậm để dễ đọc. Biểu đồ dùng theme_minimal(), loại bỏ đường lưới ngang (panel.grid.major.y) và đường lưới nhỏ (panel.grid.minor.x), căn giữa tiêu đề và làm đậm (face = “bold”) để tăng tính trực quan học thuật.
  • Trục X được mở rộng 20% so với giá trị lớn nhất (scale_x_continuous(limits = c(0, max(…) * 1.2))) để tránh tràn nhãn

Kết luận

Biểu đồ cho thấy sự khác biệt rõ rệt về chỉ số BMI trung bình giữa các nhóm tiền sử hút thuốc. Nhóm đã từng hút thuốc (former) có BMI cao nhất (≈29,6 kg/m²), cho thấy xu hướng thừa cân nhẹ. Các nhóm đang hút (current) và đã từng hút (ever) cũng có BMI cao hơn mức bình thường, lần lượt khoảng 28,4–28,8 kg/m². Trong khi đó, nhóm chưa từng hút (never) và đã ngừng hút (not current) có BMI thấp hơn (≈28,1–28,2 kg/m²), còn nhóm “no info” có BMI trung bình thấp nhất (≈25,3 kg/m²).

4.17 Biểu đồ đường với cảnh báo về age với diabetes

df_prev_age <- data %>%
  select(age, diabetes) %>%
  na.omit() %>%
  group_by(age) %>%
  summarise(Ty_le_Diabetes = mean(diabetes == "Có") * 100, .groups = 'drop')
ggplot(df_prev_age, aes(x = age, y = Ty_le_Diabetes)) +
  geom_rect(aes(xmin = 50, xmax = Inf, ymin = -Inf, ymax = Inf),fill = "#FAD7A0", alpha = 0.5) +
  geom_line(color = "#AF601A", linewidth = 1.5) +
  geom_point(data = df_prev_age %>% filter(age %% 10 == 0),size = 3,color = "#AF601A") +
  geom_text(aes(x = 65, y = 25, label = " (Age > 50)"),color = "#AF601A", size = 5, fontface = "bold") +
  labs(title = "Tỷ lệ mắc bệnh tiểu đường theo tuổi",x = "Tuổi (Age)",y = "Tỷ lệ mắc tiểu đường (%)") +
  theme_minimal(base_size = 15) +
  theme(plot.title = element_text(hjust = 0.5, face = "bold"))

Yếu tố kĩ thuật

  • Dữ liệu được chọn hai biến: age và diabetes, sau đó loại bỏ các giá trị thiếu bằng na.omit().
  • Sử dụng group_by(age) và summarise() để tính tỷ lệ mắc tiểu đường (Ty_le_Diabetes) theo từng tuổi, tính bằng trung bình logic diabetes == “Có” và nhân 100 để biểu thị phần trăm.
  • Biểu đồ được vẽ bằng ggplot2: geom_line() nối các điểm theo tuổi, geom_point() đánh dấu các tuổi chia hết cho 10 ,geom_rect() tạo vùng tô màu vàng nhạt (age > 50), geom_text() thêm nhãn giải thích “Tỷ lệ tăng vọt (Age > 50)”
  • Biểu đồ dùng theme_minimal(), căn giữa tiêu đề và làm đậm (face = “bold”)
  • Trục Y thể hiện tỷ lệ (%), trục X là tuổi.

Kết luận

Biểu đồ “Tỷ lệ mắc bệnh tiểu đường theo tuổi” cho thấy tỷ lệ mắc bệnh tăng rõ rệt theo độ tuổi, đặc biệt sau 50 tuổi, khi tỷ lệ tiểu đường tăng mạnh so với các nhóm trẻ hơn (dưới 40 tuổi) gần như không đáng kể. Điều này khẳng định tuổi tác là yếu tố nguy cơ quan trọng, do quá trình lão hóa làm giảm hiệu quả sử dụng insulin và rối loạn chuyển hóa đường huyết.

4.18 Biểu đồ cột tương tác (Interaction Bar Chart)

df_interaction <- data %>%
  select(diabetes, hypertension, heart_disease) %>%
  na.omit() %>%
  mutate(hypertension_char = as.character(hypertension),heart_disease_char = as.character(heart_disease),Nhom_benh = paste(hypertension_char, heart_disease_char, sep = " & ")) %>%
  group_by(Nhom_benh) %>%
  summarise(Ty_le_Diabetes = mean(diabetes == "Có tiểu đường"), .groups = 'drop') %>%
  filter(Nhom_benh %in% c("Không & Không", "Không & Có", "Có & Không", "Có & Có")) %>%
  arrange(Ty_le_Diabetes) %>%
  mutate(Nhom_benh = factor(Nhom_benh, levels = Nhom_benh))
plot_interaction <- ggplot(df_interaction, aes(x = Nhom_benh, y = Ty_le_Diabetes, fill = Nhom_benh)) +
  geom_col(width = 0.6) + 
  geom_text(aes(label = paste0(round(Ty_le_Diabetes*100,1), "%")),
          vjust = -0.5, size = 4)+
  labs(title = "Tỷ lệ mắc tiểu đường theo bệnh nền",x = "Nhóm bệnh nền (Tăng huyết áp & Bệnh tim)",y = "Tỷ lệ mắc tiểu đường (%)") +
  scale_y_continuous(labels = scales::percent, 
                     limits = c(0, max(df_interaction$Ty_le_Diabetes) * 1.15)) +
  scale_fill_manual(values = c("Không & Không" = "#A3E4D7", "Không & Có" = "#5DADE2","Có & Không" = "#F4D03F", "Có & Có" = "#E74C3C")) + theme_minimal(base_size = 15) + theme(legend.position ="none",plot.title = element_text(hjust = 0.5, face = "bold"))
plot_interaction

Yếu tố kĩ thuật

  • Dữ liệu được chọn ba biến: diabetes, hypertension, heart_disease, sau đó loại bỏ các giá trị thiếu bằng na.omit().
  • Tạo biến mới Nhom_benh kết hợp hai bệnh nền: tăng huyết áp & bệnh tim bằng paste().
  • Sử dụng group_by(Nhom_benh) và summarise() để tính tỷ lệ mắc tiểu đường (Ty_le_Diabetes) trong mỗi nhóm, bằng cách tính trung bình giá trị logic diabetes == “Có tiểu đường”.
  • Biểu đồ được vẽ bằng ggplot2: geom_col() tạo cột tỷ lệ cho từng nhóm bệnh nền. geom_text() hiển thị nhãn phần trăm trên đỉnh cột, làm tròn 0.1% và in đậm (fontface = “bold”).
  • Trục Y được định dạng phần trăm bằng scale_y_continuous(labels = scales::percent) và mở rộng 15% so với giá trị lớn nhất (limits = c(0, max(…) * 1.15)) để tránh tràn nhãn.
  • Màu sắc cột được định nghĩa bằng scale_fill_manual(), mỗi nhóm bệnh nền có một màu riêng, trực quan và dễ phân biệt: “Không & Không” → xanh nhạt, “Không & Có” → xanh dương, “Có & Không” → vàng, “Có & Có” → đỏ.
  • Biểu đồ sử dụng theme_minimal(), loại bỏ chú giải (legend.position = “none”), căn giữa tiêu đề và làm đậm tiêu đề.

Kết luận

Biểu đồ cho thấy mối liên hệ rõ rệt giữa bệnh nền và nguy cơ mắc tiểu đường. Ở nhóm không mắc tăng huyết áp hay bệnh tim, tỷ lệ tiểu đường chỉ 6,2%, trong khi nhóm chỉ có tăng huyết áp là 26,3%, chỉ có bệnh tim là 30,0%, và mắc cả hai lên tới 39,1%. Điều này chứng tỏ các bệnh tim mạch và huyết áp làm tăng đáng kể nguy cơ tiểu đường, đặc biệt khi cùng tồn tại.

4.19 Biểu đồ cột nhóm (Grouped Bar): Mức đường huyết theo giới tính và tăng huyết áp

df_grouped_glucose <- data %>%
  select(blood_glucose_level, gender, hypertension) %>%
  na.omit() %>% 
  group_by(gender, hypertension) %>%
  summarise(Avg_Glucose = mean(blood_glucose_level),groups = 'drop') %>%
  mutate(gender = factor(gender, levels = c("Female", "Male"), labels = c("Nữ", "Nam")) )
max_y_value <- max(df_grouped_glucose$Avg_Glucose)
ggplot(df_grouped_glucose, aes(x = gender, y = Avg_Glucose, fill = hypertension)) +
  geom_col(position = "dodge") +
  geom_text(aes(label = round(Avg_Glucose, 1)),position = position_dodge(width = 0.9), vjust = -0.5, size = 4) +
  geom_hline(yintercept = 140, linetype = "dashed", color = "red", linewidth = 1) +
  scale_y_continuous(limits = c(0, max_y_value * 1.05)) + 
  labs(title = "Đường huyết theo giới & huyết áp",x = "Giới tính",y = "Đường huyết trung bình (mg/dL)",fill = "Tăng huyết áp") +
  scale_fill_manual(values = c("Có" = "#FF7F0E", "Không" = "#1F77B4")) +
  theme_minimal(base_size = 15) +
  theme(plot.title = element_text(hjust = 0.5, face = "bold"),legend.position = "top")

Yếu tố kĩ thuật

  • Dữ liệu được chọn ba biến: blood_glucose_level, gender và hypertension, sau đó loại bỏ các giá trị thiếu bằng na.omit().
  • Sử dụng group_by(gender, hypertension) và summarise() để tính mức đường huyết trung bình (Avg_Glucose) cho từng nhóm kết hợp giới tính và tình trạng tăng huyết áp.
  • Biến gender được chuyển thành factor với nhãn tiếng Việt “Nữ” và “Nam” để hiển thị trực quan trên biểu đồ.
  • Biểu đồ bar chart được vẽ bằng ggplot2: geom_col(position = “dodge”) tạo các cột song song cho từng nhóm tăng huyết áp; geom_text() hiển thị giá trị trung bình trên đầu mỗi cột, làm tròn 1 chữ số; geom_hline(yintercept = 140, …) thêm đường tham chiếu đỏ đứt tại mức 140 mg/dL, ngưỡng cảnh báo đường huyết cao.
  • Màu sắc được định nghĩa bằng scale_fill_manual(): “Có” → cam #FF7F0E, “Không” → xanh #1F77B4.
  • Biểu đồ được định dạng bằng theme_minimal(), loại bỏ các yếu tố dư thừa, căn giữa tiêu đề và đặt chú giải lên trên.

Kết luận

Biểu đồ cho thấy cả nam và nữ bị tăng huyết áp đều có mức đường huyết trung bình cao hơn rõ rệt so với nhóm không tăng huyết áp. Cụ thể, nữ tăng huyết áp khoảng 150 mg/dL so với 136,5 mg/dL ở nhóm không tăng; nam giới lần lượt là 150,3 mg/dL và 137,9 mg/dL. Đường tham chiếu đỏ (~140 mg/dL) cho thấy các nhóm tăng huyết áp đều vượt ngưỡng đường huyết bình thường, trong khi nhóm còn lại chủ yếu nằm dưới ngưỡng này.

4.20 Biểu đồ Lollipop Ngang: Xếp hạng chỉ số HbA1c trung bình theo tình trạng hút thuốc

df_hba1c_lollipop <- data %>%
  select(hbA1c_level, smoking_history) %>%
  na.omit() %>%
  group_by(smoking_history) %>%
  summarise(Avg_HbA1c = mean(hbA1c_level),.groups = 'drop') %>%
  arrange(Avg_HbA1c) %>%
  mutate(smoking_history = factor(smoking_history, levels = smoking_history))
ggplot(df_hba1c_lollipop, aes(x = Avg_HbA1c, y = smoking_history)) +
  geom_segment(aes(x = 0, xend = Avg_HbA1c, y = smoking_history, yend = smoking_history),color = "#85929E", linewidth = 1.5) +
  geom_point(size = 4, color = "#2C3E50") +
  geom_text(aes(label = round(Avg_HbA1c, 2)),hjust = -0.5, size = 4.5, fontface = "bold", color = "#2C3E50") +
  labs(title = "Mức HbA1c trung bình theo lịch sử hút thuốc",x = "HbA1c trung bình (%)",y = "Lịch sử hút thuốc") +
  theme_minimal(base_size = 15) +
  theme(panel.grid.major.y = element_blank(),plot.title = element_text(hjust = 0.5, face = "bold")) +
  scale_x_continuous(limits = c(0, max(df_hba1c_lollipop$Avg_HbA1c) * 1.2))

Yếu tố kĩ thuật

  • Dữ liệu được chọn hai biến liên quan: hbA1c_level và smoking_history, sau đó loại bỏ giá trị thiếu bằng na.omit().
  • Dùng group_by(smoking_history) và summarise() để tính HbA1c trung bình (Avg_HbA1c) cho từng nhóm lịch sử hút thuốc.
  • Sắp xếp dữ liệu tăng dần theo Avg_HbA1c và chuyển smoking_history thành factor có thứ tự để hiển thị đúng trên biểu đồ.
  • Biểu đồ lollipop chart được vẽ bằng ggplot2:geom_segment() tạo các thanh ngang nối từ gốc đến giá trị trung bình; geom_point() đánh dấu giá trị trung bình bằng điểm tròn; geom_text() hiển thị nhãn số giá trị trung bình (làm tròn 2 chữ số).
  • Biểu đồ được định dạng bằng theme_minimal(), loại bỏ đường lưới ngang và căn giữa tiêu đề; trục X được mở rộng 20% để tránh tràn nhãn bằng scale_x_continuous(limits = c(0, max(…) * 1.2)).

Kết luận

Biểu đồ cho thấy mức HbA1c trung bình có sự khác biệt nhẹ giữa các nhóm hút thuốc. Nhóm đã từng hút (former) có HbA1c cao nhất (5.65%), tiếp theo là ever (5.58%) và not current (5.57%), trong khi never và current thấp hơn (5.54%–5.55%). Dù chênh lệch nhỏ (0.1–0.2%), xu hướng này cho thấy người có tiền sử hút thuốc thường duy trì HbA1c cao hơn, gợi ý ảnh hưởng dài hạn của hút thuốc đến chuyển hóa glucose. Tuy vậy, hút thuốc chỉ là yếu tố phụ, trong khi tuổi tác, huyết áp và béo phì có thể đóng vai trò mạnh hơn.

PHẦN 2: PHÂN TÍCH BỘ DỮ LIỆU MÃ CHỨNG KHOÁN PET

NỘI DUNG 1: GIỚI THIỆU VỀ BỘ DỮ LIỆU

1.1 Giới thiệu tổng quan về bộ dữ liệu

Bộ dữ liệu được sử dụng trong bài tiểu luận là dữ liệu tài chính của Tổng công ty cổ phần dịch vụ tổng hợp dầu khí (PET), mã chứng khoán PET, thu thập trong giai đoạn 2015–2024 từ các nguồn chính thống và được xử lý thủ công. Dữ liệu tổng hợp từ ba báo cáo tài chính cơ bản: Bảng cân đối kế toán, báo cáo kết quả hoạt động kinh doanh, và báo cáo lưu chuyển tiền tệ. Mục tiêu của bộ dữ liệu là phân tích hiệu quả hoạt động và sức khỏe tài chính của PET, đặc biệt là mối quan hệ giữa cấu trúc vốn, chi phí và khả năng sinh lời trong các chu kỳ kinh tế.

1.2 Đọc bộ dữ liệu

cdkt <- read_excel("C:/Users/hothi/Downloads/PET.xlsx", sheet = "CDKT")
lctt <- read_excel("C:/Users/hothi/Downloads/PET.xlsx", sheet = "LCTT")
kqkd <- read_excel("C:/Users/hothi/Downloads/PET.xlsx", sheet = "KQKD")
head(cdkt)
## # A tibble: 6 × 11
##   `Bảng cân đối kế toán`     `2015`   `2016`   `2017`   `2018`   `2019`   `2020`
##   <chr>                       <dbl>    <dbl>    <dbl>    <dbl>    <dbl>    <dbl>
## 1 TÀI SẢN                  NA       NA       NA       NA       NA       NA      
## 2 A- TÀI SẢN NGẮN HẠN       5.28e12  4.74e12  4.79e12  4.33e12  3.72e12  5.08e12
## 3 I. Tiền và các khoản tư…  2.05e12  1.69e12  1.34e12  1.04e12  8.16e11  1.81e12
## 4 1. Tiền                   1.02e12  3.89e11  3.39e11  3.49e11  4.35e11  6.95e11
## 5 2. Các khoản tương đươn…  1.03e12  1.31e12  1.00e12  6.90e11  3.81e11  1.11e12
## 6 II. Các khoản đầu tư tà…  1.84e10  1.35e10  2.82e10  2.80e10  9.92e10  2.18e11
## # ℹ 4 more variables: `2021` <dbl>, `2022` <dbl>, `2023` <dbl>, `2024` <dbl>
head(lctt)
## # A tibble: 6 × 11
##   Bảng lưu chuyển tiền t…¹   `2015`   `2016`   `2017`   `2018`   `2019`   `2020`
##   <chr>                       <dbl>    <dbl>    <dbl>    <dbl>    <dbl>    <dbl>
## 1 I. Lưu chuyển tiền từ h…  3.87e11  2.03e11  4.11e 9  1.97e11  3.12e11 -4.05e10
## 2 1. Lợi nhuận trước thuế   2.74e11  2.12e11  2.02e11  1.83e11  1.85e11  2.07e11
## 3 2. Điều chỉnh cho các k… NA       NA       NA       NA       NA       NA      
## 4 - Khấu hao TSCĐ và BĐSĐT  3.06e10  4.70e10  8.26e10  6.83e10  6.63e10  6.42e10
## 5 - Các khoản dự phòng      2.30e10 -1.36e10  5.50e10  7.05e10  6.91e10  6.82e10
## 6 - Lãi lỗ chênh lệch tỷ …  5.00e 9  1.28e 9  5.86e 8 -3.38e 9  2.41e 7  1.05e 9
## # ℹ abbreviated name: ¹​`Bảng lưu chuyển tiền tệ`
## # ℹ 4 more variables: `2021` <dbl>, `2022` <dbl>, `2023` <dbl>, `2024` <dbl>
head(kqkd)
## # A tibble: 6 × 11
##   Bảng báo cáo kết quả…¹  `2015`  `2016`  `2017`  `2018`  `2019`  `2020`  `2021`
##   <chr>                    <dbl>   <dbl>   <dbl>   <dbl>   <dbl>   <dbl>   <dbl>
## 1 1. Doanh thu bán hàng… 1.09e13 1.01e13 1.08e13 1.12e13 1.01e13 1.37e13 1.79e13
## 2 2. Các khoản giảm trừ… 2.54e11 1.77e11 1.31e11 1.45e11 1.40e11 2.13e11 3.23e11
## 3 3. Doanh thu thuần về… 1.07e13 9.88e12 1.07e13 1.11e13 1.00e13 1.35e13 1.76e13
## 4 4. Giá vốn hàng bán    9.83e12 9.22e12 9.96e12 1.04e13 9.40e12 1.28e13 1.67e13
## 5 5. Lợi nhuận gộp về b… 8.19e11 6.58e11 7.48e11 6.62e11 6.11e11 6.69e11 9.39e11
## 6 6. Doanh thu hoạt độn… 8.06e10 6.81e10 6.63e10 8.07e10 3.77e10 4.86e10 1.08e11
## # ℹ abbreviated name: ¹​`Bảng báo cáo kết quả kinh doanh`
## # ℹ 3 more variables: `2022` <dbl>, `2023` <dbl>, `2024` <dbl>

Giải thích kỹ thuật:

  • Read_excel() đọc từng bảng riêng biệt trong file excel.
  • head() giúp xem vài dòng đầu tiên để xác nhận dữ liệu đọc đúng.

Kết luận

Dữ liệu đọc vào có cấu trúc đúng, với các cột năm (2015 đến 2024) đã được R nhận dạng dưới định dạng số (), sẵn sàng cho việc tính toán.

1.3 Kiểm tra kích thước và kiểu dữ liệu

dim(cdkt)
## [1] 119  11
dim(lctt)
## [1] 42 11
dim(kqkd)
## [1] 23 11
str(cdkt)
## tibble [119 × 11] (S3: tbl_df/tbl/data.frame)
##  $ Bảng cân đối kế toán: chr [1:119] "TÀI SẢN" "A- TÀI SẢN NGẮN HẠN" "I. Tiền và các khoản tương đương tiền" "1. Tiền" ...
##  $ 2015                : num [1:119] NA 5.28e+12 2.05e+12 1.02e+12 1.03e+12 ...
##  $ 2016                : num [1:119] NA 4.74e+12 1.69e+12 3.89e+11 1.31e+12 ...
##  $ 2017                : num [1:119] NA 4.79e+12 1.34e+12 3.39e+11 1.00e+12 ...
##  $ 2018                : num [1:119] NA 4.33e+12 1.04e+12 3.49e+11 6.90e+11 ...
##  $ 2019                : num [1:119] NA 3.72e+12 8.16e+11 4.35e+11 3.81e+11 ...
##  $ 2020                : num [1:119] NA 5.08e+12 1.81e+12 6.95e+11 1.11e+12 ...
##  $ 2021                : num [1:119] NA 7.16e+12 2.58e+12 7.81e+11 1.80e+12 ...
##  $ 2022                : num [1:119] NA 7.76e+12 1.09e+12 5.81e+11 5.11e+11 ...
##  $ 2023                : num [1:119] NA 8.26e+12 1.07e+12 9.52e+11 1.21e+11 ...
##  $ 2024                : num [1:119] NA 9.02e+12 1.77e+12 9.98e+11 7.68e+11 ...
str(lctt)
## tibble [42 × 11] (S3: tbl_df/tbl/data.frame)
##  $ Bảng lưu chuyển tiền tệ: chr [1:42] "I. Lưu chuyển tiền từ hoạt động kinh doanh" "1. Lợi nhuận trước thuế" "2. Điều chỉnh cho các khoản" "- Khấu hao TSCĐ và BĐSĐT" ...
##  $ 2015                   : num [1:42] 3.87e+11 2.74e+11 NA 3.06e+10 2.30e+10 ...
##  $ 2016                   : num [1:42] 2.03e+11 2.12e+11 NA 4.70e+10 -1.36e+10 ...
##  $ 2017                   : num [1:42] 4.11e+09 2.02e+11 NA 8.26e+10 5.50e+10 ...
##  $ 2018                   : num [1:42] 1.97e+11 1.83e+11 NA 6.83e+10 7.05e+10 ...
##  $ 2019                   : num [1:42] 3.12e+11 1.85e+11 NA 6.63e+10 6.91e+10 ...
##  $ 2020                   : num [1:42] -4.05e+10 2.07e+11 NA 6.42e+10 6.82e+10 ...
##  $ 2021                   : num [1:42] 8.71e+10 4.15e+11 NA 6.23e+10 8.82e+10 ...
##  $ 2022                   : num [1:42] -1.68e+11 2.13e+11 NA 6.65e+10 2.91e+10 ...
##  $ 2023                   : num [1:42] -3.00e+11 1.82e+11 NA 6.90e+10 -6.27e+10 ...
##  $ 2024                   : num [1:42] 4.00e+11 2.83e+11 NA 7.31e+10 1.49e+09 ...
str(kqkd)
## tibble [23 × 11] (S3: tbl_df/tbl/data.frame)
##  $ Bảng báo cáo kết quả kinh doanh: chr [1:23] "1. Doanh thu bán hàng và cung cấp dịch vụ" "2. Các khoản giảm trừ doanh thu" "3. Doanh thu thuần về bán hàng và cung cấp dịch vụ (10 = 01 - 02)" "4. Giá vốn hàng bán" ...
##  $ 2015                           : num [1:23] 1.09e+13 2.54e+11 1.07e+13 9.83e+12 8.19e+11 ...
##  $ 2016                           : num [1:23] 1.01e+13 1.77e+11 9.88e+12 9.22e+12 6.58e+11 ...
##  $ 2017                           : num [1:23] 1.08e+13 1.31e+11 1.07e+13 9.96e+12 7.48e+11 ...
##  $ 2018                           : num [1:23] 1.12e+13 1.45e+11 1.11e+13 1.04e+13 6.62e+11 ...
##  $ 2019                           : num [1:23] 1.01e+13 1.40e+11 1.00e+13 9.40e+12 6.11e+11 ...
##  $ 2020                           : num [1:23] 1.37e+13 2.13e+11 1.35e+13 1.28e+13 6.69e+11 ...
##  $ 2021                           : num [1:23] 1.79e+13 3.23e+11 1.76e+13 1.67e+13 9.39e+11 ...
##  $ 2022                           : num [1:23] 1.78e+13 2.31e+11 1.75e+13 1.66e+13 9.67e+11 ...
##  $ 2023                           : num [1:23] 1.75e+13 2.65e+11 1.72e+13 1.65e+13 7.22e+11 ...
##  $ 2024                           : num [1:23] 1.94e+13 -3.28e+11 1.90e+13 -1.82e+13 8.90e+11 ...

Yếu tố kỹ thuật

  • Dim() trả về (số dòng, số cột) giúp biết quy mô dữ liệu; str() cho biết kiểu dữ liệu từng biến (numeric, character, date…)

Kết luận

Kiểm tra kích thước và kiểu dữ liệu cho thấy bộ dữ liệu có cấu trúc chuỗi thời gian hoàn chỉnh và đồng nhất. Cả ba báo cáo (CDKT: 119 dòng, LCTT: 42 dòng, KQKD: 23 dòng) đều có 11 cột, gồm một cột khoản mục và 10 cột năm từ 2015 đến 2024. Tất cả các cột dữ liệu tài chính đều được định dạng chính xác ở kiểu số (num), đảm bảo sẵn sàng cho các phép tính và phân tích tài chính.

1.4 Lọc 10 biến chính cần phân tích

file_path <- "C:/Users/hothi/Downloads/PET.xlsx"
cdkt <- read_excel(file_path, sheet = "CDKT")
lctt <- read_excel(file_path, sheet = "LCTT")
kqkd <- read_excel(file_path, sheet = "KQKD")
names(cdkt)[1] <- "Khoan_muc"
names(lctt)[1] <- "Khoan_muc"
names(kqkd)[1] <- "Khoan_muc"
vars_to_select <- c(
  "3. Doanh thu thuần về bán hàng và cung cấp dịch vụ (10 = 01 - 02)",
  "5. Lợi nhuận gộp về bán hàng và cung cấp dịch vụ(20=10-11)",
  "9. Chi phí bán hàng",
  "10. Chi phí quản lý doanh nghiệp",
  "TỔNG CỘNG TÀI SẢN",
  "I. Vốn chủ sở hữu",
  "1. Hàng tồn kho",
  "1. Phải thu ngắn hạn của khách hàng",
  "10. Vay và nợ thuê tài chính ngắn hạn",
  "I. Lưu chuyển tiền từ hoạt động kinh doanh")
clean_name <- function(x) {
  toupper(stri_trans_general(x, "Latin-ASCII")) %>%
    gsub("[^A-Z0-9]", "", .)}
vars_clean <- clean_name(vars_to_select)
filter_df <- function(df) {df %>%
    mutate(Khoan_muc_clean = clean_name(Khoan_muc)) %>%
    filter(Khoan_muc_clean %in% vars_clean) %>%
    select(-Khoan_muc_clean)}
cdkt_sel <- filter_df(cdkt)
lctt_sel <- filter_df(lctt)
kqkd_sel <- filter_df(kqkd)
financial_selected <- bind_rows(cdkt_sel, lctt_sel, kqkd_sel, .id = "Nguon")
cols_years <- names(financial_selected)[!names(financial_selected) %in% c("Nguon", "Khoan_muc")]
financial_long <- financial_selected %>%
  pivot_longer(cols = all_of(cols_years),names_to = "Nam",values_to = "Gia_tri") %>%
  mutate(Nam = as.numeric(gsub("[^0-9]", "", Nam)), Khoan_muc_VN = case_when(
      grepl("Doanh thu thuần", Khoan_muc, ignore.case = TRUE) ~ "DT",
      grepl("Lợi nhuận gộp", Khoan_muc, ignore.case = TRUE) ~ "LNG",
      grepl("Chi phí bán hàng", Khoan_muc, ignore.case = TRUE) ~ "CPBH",
      grepl("Chi phí quản lý", Khoan_muc, ignore.case = TRUE) ~ "CPQL",
      grepl("TỔNG CỘNG TÀI SẢN", Khoan_muc, ignore.case = TRUE) ~ "TTS",
      grepl("VỐN CHỦ SỞ HỮU", Khoan_muc, ignore.case = TRUE) ~ "VCSH",
      grepl("Hàng tồn kho", Khoan_muc, ignore.case = TRUE) ~ "HTK",
      grepl("Phải thu ngắn hạn", Khoan_muc, ignore.case = TRUE) ~ "PTNH",
      grepl("Vay và nợ thuê tài chính ngắn hạn", Khoan_muc, ignore.case = TRUE) ~ "NVNH",
      grepl("Lưu chuyển tiền", Khoan_muc, ignore.case = TRUE) ~ "CFO",
      TRUE ~ Khoan_muc)) %>%
  select(Nam, Khoan_muc_VN, Gia_tri)
financial_long_unique <- financial_long %>%
  group_by(Nam, Khoan_muc_VN) %>%
  summarise(Gia_tri = first(Gia_tri), .groups = "drop")
financial_wide <- financial_long_unique %>%
  pivot_wider(names_from = Khoan_muc_VN,values_from = Gia_tri) %>%
  arrange(Nam)
cat("## KẾT QUẢ: TÊN CỘT TRONG FINANCIAL_WIDE\n")
## ## KẾT QUẢ: TÊN CỘT TRONG FINANCIAL_WIDE
names(financial_wide)
##  [1] "Nam"  "CFO"  "CPBH" "CPQL" "DT"   "HTK"  "LNG"  "NVNH" "PTNH" "TTS" 
## [11] "VCSH"

Yếu tố kĩ thuật

  • Hàm chuẩn hóa tên (clean_name()) tạo hàm tùy chỉnh (toupper + stri_trans_general) để loại bỏ dấu tiếng Việt và ký tự đặc biệt trong tên các khoản mục.
  • Hàm filter_df() sử dụng hàm này để lọc tự động 10 khoản mục chính dựa trên tên đã chuẩn hóa.
  • Hàm bind_rows() gộp dữ liệu của 3 báo cáo đã được lọc (cdkt_sel, lctt_sel, kqkd_sel) thành một bảng dài duy nhất (financial_selected).
  • Pivot_longer() chuyển dữ liệu từ dạng rộng sang dạng dài; pivot_wider()chuyển dữ liệu lại sang dạng rộng cuối cùng (financial_wide), với mỗi khoản mục tài chính (CFO, DT, LNG,…) là một cột độc lập, tạo nên cấu trúc Tidy Data lý tưởng cho phân tích.
  • Hàm case_when() sử dụng trong bước pivot_longer để mã hóa tên khoản mục tiếng việt dài thành các ký hiệu tài chính ngắn gọn, dễ gọi
  • Hàm group_by() và summarise() đảm bảo loại bỏ các giá trị trùng lặp bằng cách chọn giá trị đầu tiên, giữ cho dữ liệu sạch và chính xác trước khi chuyển sang định dạng cuối cùng.

Kết luận

Bảng financial_wide có cấu trúc “tidy data” với 10 biến tài chính cốt lõi (CFO, CPBH, CPQL, DT, HTK, LNG, NVNH, PTNH, TTS, VCSH) và cột thời gian (Nam), cho phép thực hiện trực tiếp các phân tích chuỗi thời gian, tính toán chỉ số ROE, ROA.

1.5 Kích thước bộ dữ liệu

num_rows <- nrow(financial_wide)
num_cols <- ncol(financial_wide)
cat("Số dòng:", num_rows, "\n")
## Số dòng: 10
cat("Số cột:", num_cols, "\n")
## Số cột: 11

Yếu tố kĩ thuật

  • Xác định kích thước: Các hàm nrow(), ncol(), và phép toán nhân (num_rows * num_cols)

Kết luận

Kết quả cho thấy bảng financial_wide gồm 10 dòng và 11 cột,110 quan sát tương ứng với 10 năm (2015–2024) và 10 chỉ tiêu tài chính cốt lõi cùng cột thời gian (Nam).

1.6 Xem cấu trúc bộ dữ liệu

str(financial_wide)
## tibble [10 × 11] (S3: tbl_df/tbl/data.frame)
##  $ Nam : num [1:10] 2015 2016 2017 2018 2019 ...
##  $ CFO : num [1:10] 3.87e+11 2.03e+11 4.11e+09 1.97e+11 3.12e+11 ...
##  $ CPBH: num [1:10] 2.82e+11 2.37e+11 2.56e+11 2.41e+11 2.24e+11 ...
##  $ CPQL: num [1:10] 2.18e+11 1.76e+11 2.47e+11 2.03e+11 2.51e+11 ...
##  $ DT  : num [1:10] 1.07e+13 9.88e+12 1.07e+13 1.11e+13 1.00e+13 ...
##  $ HTK : num [1:10] 1.56e+12 9.86e+11 7.91e+11 1.03e+12 1.14e+12 ...
##  $ LNG : num [1:10] 8.19e+11 6.58e+11 7.48e+11 6.62e+11 6.11e+11 ...
##  $ NVNH: num [1:10] 2.16e+12 2.30e+12 2.03e+12 1.61e+12 1.27e+12 ...
##  $ PTNH: num [1:10] 1.13e+12 1.43e+12 1.68e+12 1.60e+12 1.34e+12 ...
##  $ TTS : num [1:10] 5.76e+12 6.23e+12 6.17e+12 5.56e+12 4.97e+12 ...
##  $ VCSH: num [1:10] 1.35e+12 1.65e+12 1.66e+12 1.62e+12 1.64e+12 ...

Yếu tố kĩ thuật

  • Hàm str() sử dụng để hiển thị cấu trúc nội tại của đối tượng dữ liệu, xác nhận số dòng, số cột, và kiểu dữ liệu của từng biến.

Kết luận

Kết quả str() cho thấy bảng financial_wide có cấu trúc tidy data chuẩn, gồm 11 biến (1 cột thời gian và 10 chỉ tiêu tài chính), tất cả đều ở dạng số (num).

1.7 Xem vài dòng đầu bộ dữ liệu

head(financial_wide)
## # A tibble: 6 × 11
##     Nam      CFO    CPBH    CPQL      DT     HTK     LNG    NVNH    PTNH     TTS
##   <dbl>    <dbl>   <dbl>   <dbl>   <dbl>   <dbl>   <dbl>   <dbl>   <dbl>   <dbl>
## 1  2015  3.87e11 2.82e11 2.18e11 1.07e13 1.56e12 8.19e11 2.16e12 1.13e12 5.76e12
## 2  2016  2.03e11 2.37e11 1.76e11 9.88e12 9.86e11 6.58e11 2.30e12 1.43e12 6.23e12
## 3  2017  4.11e 9 2.56e11 2.47e11 1.07e13 7.91e11 7.48e11 2.03e12 1.68e12 6.17e12
## 4  2018  1.97e11 2.41e11 2.03e11 1.11e13 1.03e12 6.62e11 1.61e12 1.60e12 5.56e12
## 5  2019  3.12e11 2.24e11 2.51e11 1.00e13 1.14e12 6.11e11 1.27e12 1.34e12 4.97e12
## 6  2020 -4.05e10 2.32e11 2.02e11 1.35e13 8.11e11 6.69e11 2.51e12 1.65e12 6.32e12
## # ℹ 1 more variable: VCSH <dbl>

Yếu tố kĩ thuật

  • Hàm head(): Được sử dụng để kiểm tra chất lượng nội dung; hàm unique(): Xác nhận tính riêng biệt của từng năm và từng quan sát.

Kết luận

Kết quả cho thấy: (1) doanh thu và tổng tài sản tăng mạnh giai đoạn 2015–2020, phản ánh sự mở rộng quy mô; (2) dòng tiền hoạt động (CFO) biến động lớn, thể hiện sự thiếu ổn định tài chính; (3) năm 2024 xuất hiện giá trị âm ở CPBH và CPQL, cho thấy dấu hiệu bất thường cần được xem xét kỹ.

1.8 Kiểm tra các giá trị NA

colSums(is.na(financial_wide))
##  Nam  CFO CPBH CPQL   DT  HTK  LNG NVNH PTNH  TTS VCSH 
##    0    0    0    0    0    0    0    0    0    0    0

Yếu tố kĩ thuật

  • Hàm colSums(is.na(df)) kiểm tra tính đầy đủ của dữ liệu nó tính tổng số lượng giá trị thiếu (NA) trong từng cột.

Kết luận

Kết quả colSums(is.na(financial_wide)) bằng 0 cho tất cả 11 biến, cho thấy dữ liệu hoàn chỉnh và không có giá trị thiếu.

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

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

Yếu tố kĩ thuật

  • Hàm sum(duplicated(df)) kiểm tra xem có bất kỳ dòng nào trùng khớp hoàn toàn với các dòng khác trong bộ dữ liệu hay không. Trả về 0 xác nhận tính độc lập của các quan sát.

Kết luận

Kết quả sum(duplicated(financial_wide)) bằng 0 cho thấy không có dòng dữ liệu trùng lặp, đảm bảo mỗi năm chỉ có một quan sát duy nhất.

1.10 Kiểm tra giá trị duy nhất của một biến

unique(financial_wide)
## # A tibble: 10 × 11
##      Nam           CFO     CPBH     CPQL      DT     HTK     LNG    NVNH    PTNH
##    <dbl>         <dbl>    <dbl>    <dbl>   <dbl>   <dbl>   <dbl>   <dbl>   <dbl>
##  1  2015  386645716621  2.82e11  2.18e11 1.07e13 1.56e12 8.19e11 2.16e12 1.13e12
##  2  2016  202890081636  2.37e11  1.76e11 9.88e12 9.86e11 6.58e11 2.30e12 1.43e12
##  3  2017    4108698885  2.56e11  2.47e11 1.07e13 7.91e11 7.48e11 2.03e12 1.68e12
##  4  2018  197237097985  2.41e11  2.03e11 1.11e13 1.03e12 6.62e11 1.61e12 1.60e12
##  5  2019  312122602166  2.24e11  2.51e11 1.00e13 1.14e12 6.11e11 1.27e12 1.34e12
##  6  2020  -40515989177  2.32e11  2.02e11 1.35e13 8.11e11 6.69e11 2.51e12 1.65e12
##  7  2021   87140672273  2.85e11  2.71e11 1.76e13 1.48e12 9.39e11 3.56e12 2.52e12
##  8  2022 -167839738647  3.36e11  1.47e11 1.75e13 2.47e12 9.67e11 4.35e12 2.08e12
##  9  2023 -299922459113  3.36e11  1.94e11 1.72e13 1.92e12 7.22e11 4.52e12 2.39e12
## 10  2024  399996509899 -4.07e11 -2.08e11 1.90e13 1.73e12 8.90e11 4.98e12 2.45e12
## # ℹ 2 more variables: TTS <dbl>, VCSH <dbl>

Yếu tố kĩ thuật

  • Hàm unique(df) sử dụng để in ra tất cả các dòng dữ liệu duy nhất.

Kết luận

Việc sử dụng unique(financial_wide) xác nhận toàn bộ 10 dòng dữ liệu đều không trùng lặp và thể hiện rõ sự khác biệt giữa các năm.

NỘI DUNG 2: XỬ LÍ DỮ LIỆU THÔ VÀ MÃ HÓA DỮ LIỆU

2.1 Tên và ý nghĩa các biến

variable_meaning_2col <- tibble::tribble(~Ten_Bien, ~Y_Nghia_Chi_Tiet, 
  "Nam", "Năm tài chính",
  "DT", "Doanh thu thuần về bán hàng và cung cấp dịch vụ", 
  "LNG", "Lợi nhuận gộp về bán hàng và cung cấp dịch vụ", 
  "CFO", "Lưu chuyển tiền tệ thuần từ hoạt động kinh doanh", 
  "TTS", "Tổng Cộng Tài sản", 
  "VCSH", "Vốn Chủ Sở Hữu (tổng cộng)", 
  "HTK", "Hàng tồn kho", 
  "PTNH", "Phải thu ngắn hạn của khách hàng", 
  "NVNH", "Vay và nợ thuê tài chính ngắn hạn (Nợ vay ngắn hạn)", 
  "CPBH", "Chi phí bán hàng", 
  "CPQL", "Chi phí quản lý doanh nghiệp")
variable_meaning_2col %>%
  flextable() %>%
  set_header_labels(Ten_Bien = "Mã Biến", Y_Nghia_Chi_Tiet = "Ý nghĩa Chi tiết") %>%
  set_caption(caption = "Bảng giải thích tên và ý nghĩa các biến") %>%
  theme_vanilla() %>%
  align(align = "left", part = "all") %>%
  autofit()
Bảng giải thích tên và ý nghĩa các biến

Mã Biến

Ý nghĩa Chi tiết

Nam

Năm tài chính

DT

Doanh thu thuần về bán hàng và cung cấp dịch vụ

LNG

Lợi nhuận gộp về bán hàng và cung cấp dịch vụ

CFO

Lưu chuyển tiền tệ thuần từ hoạt động kinh doanh

TTS

Tổng Cộng Tài sản

VCSH

Vốn Chủ Sở Hữu (tổng cộng)

HTK

Hàng tồn kho

PTNH

Phải thu ngắn hạn của khách hàng

NVNH

Vay và nợ thuê tài chính ngắn hạn (Nợ vay ngắn hạn)

CPBH

Chi phí bán hàng

CPQL

Chi phí quản lý doanh nghiệp

Yếu tố kĩ thuật

  • Dùng tribble() để nhập mã biến và ý nghĩa chi tiết theo hàng; flextable(): tạo bảng kiểu Word/HTML.
  • set_header_labels(): đổi tên cột hiển thị; set_caption(): thêm chú thích/bảng tiêu đề.
  • theme_vanilla(): theme đơn giản, đẹp; align(align=“left”, part=“all”): căn trái toàn bộ bảng; autofit(): tự động điều chỉnh độ rộng cột cho vừa nội dung.

Kết luận

Quá trình xử lý dữ liệu thô đã hoàn tất thành công bằng việc xây dựng bảng giải thích biến này, cung cấp ngôn ngữ chung cho các phân tích định lượng tiếp theo.

2.2 Kiểm tra giá trị ngoại lai cho các biến

vars <- c("DT", "LNG", "CPBH", "CPQL", "CFO", "HTK", "NVNH", "PTNH", "TTS", "VCSH")
outliers_wide <- sapply(vars, function(var) {x <- financial_wide[[var]]
  Q1 <- quantile(x, 0.25, na.rm = TRUE)
  Q3 <- quantile(x, 0.75, na.rm = TRUE)
  IQR_val <- Q3 - Q1
  out <- x[x < (Q1 - 1.5*IQR_val) | x > (Q3 + 1.5*IQR_val)]
  if(length(out) == 0) out <- NA
  out
}, simplify = FALSE) %>%
  {max_len <- max(sapply(., length)); 
   lapply(., function(x){length(x) <- max_len; x}) %>% as.data.frame()}
outliers_wide
##   DT LNG      CPBH      CPQL CFO HTK NVNH PTNH TTS VCSH
## 1 NA  NA -4.07e+11 -2.08e+11  NA  NA   NA   NA  NA   NA

Yếu tố kĩ thuật

  • Xác định các biến cần kiểm tra outlier: vars <- c(“..”)
  • Tạo list chứa outlier của từng biến: outliers_lis; sapply() dùng để lặp qua từng biến, kết quả lưu trong list
  • Tính phân vị và IQR: Q1 phân vị 25%; Q3 phân vị 75%; IQR_val <- Q3-Q1: khoảng tứ phân vị
  • Lọc outlier theo công thức 1.5*IQR
  • Xử lý trường hợp không có outlier: if(length(out)==0) out <- NA; max(sapply(outliers_list,length)) → tìm chiều dài lớn nhất
  • Lapply(outliers_list,function(x){length(x)<-max_len;x}) → padding NA
  • Chuyển list thành bảng: as.data.frame() mỗi cột là 1 biến, mỗi hàng là outlier hoặc NA
  • Outliers_wide: bảng tổng hợp outlier

Kết luận

Kết quả kiểm tra ngoại lai bằng IQR cho thấy dữ liệu ổn định, các chỉ tiêu quy mô và dòng tiền (DT, LNG, CFO, TTS, VCSH, HTK, NVNH, PTNH) không có ngoại lai nghiêm trọng.Ngoại lệ chỉ xuất hiện ở CPBH và CPQL với giá trị âm lớn, phản ánh khả năng hoàn nhập chi phí bất thường năm 2024.

2.3 Tạo biến phụ ROA

financial_wide <- financial_wide %>%
  mutate(ROA = LNG / TTS)
financial_wide %>%
  select(Nam, ROA) %>%
  print(n = Inf)
## # A tibble: 10 × 2
##      Nam    ROA
##    <dbl>  <dbl>
##  1  2015 0.142 
##  2  2016 0.106 
##  3  2017 0.121 
##  4  2018 0.119 
##  5  2019 0.123 
##  6  2020 0.106 
##  7  2021 0.111 
##  8  2022 0.107 
##  9  2023 0.0762
## 10  2024 0.0875

Yếu tố kĩ thuật

  • Hàm mutate() dùng để thêm cột mới vào dataframe; select() chọn cột cần hiển thị; print() hiện thị toàn bộ kết quả.

Kết luận

Dữ liệu 10 năm cho thấy hiệu suất sử dụng tài sản của công ty chia thành hai giai đoạn rõ rệt: giai đoạn 2015–2022: ROA duy trì cao và ổn định (trên 10.5%), đạt đỉnh 14.2% năm 2015, phản ánh khả năng sử dụng tài sản hiệu quả. Giai đoạn 2023–2024: ROA giảm mạnh xuống 7.62% rồi hồi nhẹ 8.75%, cho thấy áp lực sinh lời do tài sản tăng nhanh hơn lợi nhuận

2.4 Tạo biến phụ ROE

financial_wide <- financial_wide %>%
  mutate(ROE = LNG / VCSH)
financial_wide %>%
  select(Nam, ROE) %>%
  print(n = Inf)
## # A tibble: 10 × 2
##      Nam   ROE
##    <dbl> <dbl>
##  1  2015 0.606
##  2  2016 0.400
##  3  2017 0.450
##  4  2018 0.409
##  5  2019 0.373
##  6  2020 0.402
##  7  2021 0.484
##  8  2022 0.469
##  9  2023 0.330
## 10  2024 0.381

Yếu tố kĩ thuật

  • Tương tự mục 2.3 của ROA

Kết luận

Dữ liệu 10 năm cho thấy ROE của công ty chia thành hai giai đoạn: 2015–2022: ROE duy trì rất cao (40–60%), phản ánh việc sử dụng đòn bẩy tài chính hiệu quả, tối đa hóa lợi nhuận cho cổ đông. 2023 - 2024: ROE giảm mạnh xuống 33%, báo hiệu áp lực lợi nhuận và thay đổi trong cấu trúc vốn. Tóm lại, công ty duy trì hiệu suất sinh lời cao nhờ quản lý đòn bẩy tốt, nhưng cần chú ý rủi ro suy giảm lợi nhuận từ năm 2023.

2.5 Tạo biến tỷ lệ biến đổi

financial_wide <- financial_wide %>%
  arrange(Nam) %>%
  mutate(
    DT_pct = (DT - lag(DT)) / lag(DT) * 100,
    LNG_pct = (LNG - lag(LNG)) / lag(LNG) * 100,
    CPBH_pct = (CPBH - lag(CPBH)) / lag(CPBH) * 100,
    CPQL_pct = (CPQL - lag(CPQL)) / lag(CPQL) * 100,
    CFO_pct = (CFO - lag(CFO)) / lag(CFO) * 100,
    HTK_pct = (HTK - lag(HTK)) / lag(HTK) * 100,
    NVNH_pct = (NVNH - lag(NVNH)) / lag(NVNH) * 100,
    PTNH_pct = (PTNH - lag(PTNH)) / lag(PTNH) * 100,
    TTS_pct = (TTS - lag(TTS)) / lag(TTS) * 100,
    VCSH_pct = (VCSH - lag(VCSH)) / lag(VCSH) * 100)
financial_wide %>%
  select(ends_with("_pct"))
## # A tibble: 10 × 10
##    DT_pct LNG_pct  CPBH_pct CPQL_pct CFO_pct HTK_pct NVNH_pct PTNH_pct TTS_pct
##     <dbl>   <dbl>     <dbl>    <dbl>   <dbl>   <dbl>    <dbl>    <dbl>   <dbl>
##  1 NA       NA      NA          NA      NA     NA       NA       NA     NA    
##  2 -7.23   -19.6   -16.0       -19.5   -47.5  -36.7      6.67    26.4    8.02 
##  3  8.31    13.6     8.11       40.4   -98.0  -19.8    -11.8     18.2   -0.871
##  4  3.63   -11.4    -5.86      -17.8  4700.    30.8    -20.5     -4.90  -9.88 
##  5 -9.77    -7.75   -7.14       23.9    58.2   10.7    -21.3    -16.7  -10.7  
##  6 34.4      9.45    3.95      -19.7  -113.   -29.1     97.4     23.9   27.3  
##  7 30.8     40.4    22.6        34.4  -315.    82.1     42.0     52.1   34.4  
##  8 -0.312    2.97   18.0       -45.9  -293.    67.5     22.1    -17.5    6.43 
##  9 -1.86   -25.3     0.0842     31.9    78.7  -22.6      3.92    15.3    4.87 
## 10 10.6     23.2  -221.       -208.   -233.    -9.73    10.3      2.21   7.23 
## # ℹ 1 more variable: VCSH_pct <dbl>

Yếu tố kỹ thuật

  • Hàm arrange(Nam): sắp xếp theo năm để tính biến đổi liên tiếp đúng; mutate() + lag(): tạo các cột % biến đổi; select(ends_with(“_pct”)): chỉ lấy các cột tỷ lệ biến đổi để kiểm tra kết quả.

Kết luận

Bảng tỷ lệ biến đổi cho thấy sự trái ngược giữa tăng trưởng quy mô và bất ổn dòng tiền. CFO_pct biến động mạnh nhất (đỉnh 4700% năm 2018, nhiều năm âm sâu), phản ánh áp lực thanh khoản lớn. Trong khi đó, DT_pct và LNG_pct tăng mạnh giai đoạn 2020–2021, cho thấy mở rộng quy mô hiệu quả. Tuy nhiên, CPBH_pct và CPQL_pct âm bất thường năm 2024 (–221%, –208%) gợi ý hoàn nhập chi phí.

2.6 Lưu dữ liệu sạch vào file excel

library(writexl)  
write_xlsx(financial_wide, path = "C:/Users/hothi/Downloads/financial_clean.xlsx")

Yếu tố kĩ thuật

  • Lệnh library(writexl) được sử dụng để nạp thư viện cho việc xuất dữ liệu sang định dạng Excel; write_xlsx() được sử dụng để lưu trữ dữ liệu

2.7 Kiểm tra cấu trúc tổng thể của bộ dữ liệu sau xử lý

str(financial_wide)
## tibble [10 × 23] (S3: tbl_df/tbl/data.frame)
##  $ Nam     : num [1:10] 2015 2016 2017 2018 2019 ...
##  $ CFO     : num [1:10] 3.87e+11 2.03e+11 4.11e+09 1.97e+11 3.12e+11 ...
##  $ CPBH    : num [1:10] 2.82e+11 2.37e+11 2.56e+11 2.41e+11 2.24e+11 ...
##  $ CPQL    : num [1:10] 2.18e+11 1.76e+11 2.47e+11 2.03e+11 2.51e+11 ...
##  $ DT      : num [1:10] 1.07e+13 9.88e+12 1.07e+13 1.11e+13 1.00e+13 ...
##  $ HTK     : num [1:10] 1.56e+12 9.86e+11 7.91e+11 1.03e+12 1.14e+12 ...
##  $ LNG     : num [1:10] 8.19e+11 6.58e+11 7.48e+11 6.62e+11 6.11e+11 ...
##  $ NVNH    : num [1:10] 2.16e+12 2.30e+12 2.03e+12 1.61e+12 1.27e+12 ...
##  $ PTNH    : num [1:10] 1.13e+12 1.43e+12 1.68e+12 1.60e+12 1.34e+12 ...
##  $ TTS     : num [1:10] 5.76e+12 6.23e+12 6.17e+12 5.56e+12 4.97e+12 ...
##  $ VCSH    : num [1:10] 1.35e+12 1.65e+12 1.66e+12 1.62e+12 1.64e+12 ...
##  $ ROA     : num [1:10] 0.142 0.106 0.121 0.119 0.123 ...
##  $ ROE     : num [1:10] 0.606 0.4 0.45 0.409 0.373 ...
##  $ DT_pct  : num [1:10] NA -7.23 8.31 3.63 -9.77 ...
##  $ LNG_pct : num [1:10] NA -19.58 13.58 -11.43 -7.75 ...
##  $ CPBH_pct: num [1:10] NA -15.97 8.11 -5.86 -7.14 ...
##  $ CPQL_pct: num [1:10] NA -19.5 40.4 -17.8 23.9 ...
##  $ CFO_pct : num [1:10] NA -47.5 -98 4700.5 58.2 ...
##  $ HTK_pct : num [1:10] NA -36.7 -19.8 30.8 10.7 ...
##  $ NVNH_pct: num [1:10] NA 6.67 -11.79 -20.52 -21.29 ...
##  $ PTNH_pct: num [1:10] NA 26.4 18.2 -4.9 -16.7 ...
##  $ TTS_pct : num [1:10] NA 8.023 -0.871 -9.878 -10.727 ...
##  $ VCSH_pct: num [1:10] NA 21.895 0.772 -2.419 1.229 ...
glimpse(financial_wide)
## Rows: 10
## Columns: 23
## $ Nam      <dbl> 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023, 2024
## $ CFO      <dbl> 3.87e+11, 2.03e+11, 4.11e+09, 1.97e+11, 3.12e+11, -4.05e+10, …
## $ CPBH     <dbl> 2.82e+11, 2.37e+11, 2.56e+11, 2.41e+11, 2.24e+11, 2.32e+11, 2…
## $ CPQL     <dbl> 2.18e+11, 1.76e+11, 2.47e+11, 2.03e+11, 2.51e+11, 2.02e+11, 2…
## $ DT       <dbl> 1.07e+13, 9.88e+12, 1.07e+13, 1.11e+13, 1.00e+13, 1.35e+13, 1…
## $ HTK      <dbl> 1.56e+12, 9.86e+11, 7.91e+11, 1.03e+12, 1.14e+12, 8.11e+11, 1…
## $ LNG      <dbl> 8.19e+11, 6.58e+11, 7.48e+11, 6.62e+11, 6.11e+11, 6.69e+11, 9…
## $ NVNH     <dbl> 2.16e+12, 2.30e+12, 2.03e+12, 1.61e+12, 1.27e+12, 2.51e+12, 3…
## $ PTNH     <dbl> 1.13e+12, 1.43e+12, 1.68e+12, 1.60e+12, 1.34e+12, 1.65e+12, 2…
## $ TTS      <dbl> 5.76e+12, 6.23e+12, 6.17e+12, 5.56e+12, 4.97e+12, 6.32e+12, 8…
## $ VCSH     <dbl> 1.35e+12, 1.65e+12, 1.66e+12, 1.62e+12, 1.64e+12, 1.66e+12, 1…
## $ ROA      <dbl> 0.1420, 0.1057, 0.1212, 0.1191, 0.1231, 0.1058, 0.1106, 0.107…
## $ ROE      <dbl> 0.606, 0.400, 0.450, 0.409, 0.373, 0.402, 0.484, 0.469, 0.330…
## $ DT_pct   <dbl> NA, -7.232, 8.310, 3.632, -9.768, 34.418, 30.810, -0.312, -1.…
## $ LNG_pct  <dbl> NA, -19.58, 13.58, -11.43, -7.75, 9.45, 40.41, 2.97, -25.31, …
## $ CPBH_pct <dbl> NA, -15.9679, 8.1067, -5.8622, -7.1424, 3.9483, 22.5678, 18.0…
## $ CPQL_pct <dbl> NA, -19.5, 40.4, -17.8, 23.9, -19.7, 34.4, -45.9, 31.9, -207.6
## $ CFO_pct  <dbl> NA, -47.5, -98.0, 4700.5, 58.2, -113.0, -315.1, -292.6, 78.7,…
## $ HTK_pct  <dbl> NA, -36.72, -19.77, 30.76, 10.69, -29.12, 82.11, 67.51, -22.6…
## $ NVNH_pct <dbl> NA, 6.67, -11.79, -20.52, -21.29, 97.36, 41.98, 22.05, 3.92, …
## $ PTNH_pct <dbl> NA, 26.40, 18.17, -4.90, -16.67, 23.92, 52.13, -17.53, 15.29,…
## $ TTS_pct  <dbl> NA, 8.023, -0.871, -9.878, -10.727, 27.272, 34.369, 6.433, 4.…
## $ VCSH_pct <dbl> NA, 21.895, 0.772, -2.419, 1.229, 1.393, 16.629, 6.325, 6.005…

Yếu tố kĩ thuật

  • Hàm str() và glimpse(): được sử dụng để kiểm tra cấu trúc cuối cùng của dataframe, xác nhận số lượng dòng/cột và kiểu dữ liệu của từng biến.

Kết luận

Bảng dữ liệu có 10 dòng (10 năm) và 23 cột (11 chỉ tiêu gốc + 12 tỷ lệ phụ), tất cả đều ở dạng số học (num/).

NỘI DUNG 3: PHÂN TÍCH DỮ LIỆU CƠ BẢN

3.1 Thống kê tóm tắt cơ bản của dữ liệu

summary(financial_wide)
##       Nam            CFO                 CPBH                CPQL          
##  Min.   :2015   Min.   :-3.00e+11   Min.   :-4.07e+11   Min.   :-2.08e+11  
##  1st Qu.:2017   1st Qu.:-2.94e+10   1st Qu.: 2.33e+11   1st Qu.: 1.80e+11  
##  Median :2020   Median : 1.42e+11   Median : 2.48e+11   Median : 2.02e+11  
##  Mean   :2020   Mean   : 1.08e+11   Mean   : 2.02e+11   Mean   : 1.70e+11  
##  3rd Qu.:2022   3rd Qu.: 2.85e+11   3rd Qu.: 2.84e+11   3rd Qu.: 2.40e+11  
##  Max.   :2024   Max.   : 4.00e+11   Max.   : 3.36e+11   Max.   : 2.71e+11  
##                                                                            
##        DT                HTK                LNG                NVNH         
##  Min.   :9.88e+12   Min.   :7.91e+11   Min.   :6.11e+11   Min.   :1.27e+12  
##  1st Qu.:1.07e+13   1st Qu.:9.98e+11   1st Qu.:6.64e+11   1st Qu.:2.06e+12  
##  Median :1.23e+13   Median :1.31e+12   Median :7.35e+11   Median :2.41e+12  
##  Mean   :1.37e+13   Mean   :1.39e+12   Mean   :7.69e+11   Mean   :2.93e+12  
##  3rd Qu.:1.75e+13   3rd Qu.:1.69e+12   3rd Qu.:8.72e+11   3rd Qu.:4.15e+12  
##  Max.   :1.90e+13   Max.   :2.47e+12   Max.   :9.67e+11   Max.   :4.98e+12  
##                                                                             
##       PTNH               TTS                VCSH               ROA        
##  Min.   :1.13e+12   Min.   :4.97e+12   Min.   :1.35e+12   Min.   :0.0762  
##  1st Qu.:1.47e+12   1st Qu.:5.87e+12   1st Qu.:1.64e+12   1st Qu.:0.1058  
##  Median :1.67e+12   Median :6.27e+12   Median :1.66e+12   Median :0.1088  
##  Mean   :1.83e+12   Mean   :7.22e+12   Mean   :1.81e+12   Mean   :0.1098  
##  3rd Qu.:2.31e+12   3rd Qu.:8.90e+12   3rd Qu.:2.03e+12   3rd Qu.:0.1206  
##  Max.   :2.52e+12   Max.   :1.02e+13   Max.   :2.34e+12   Max.   :0.1420  
##                                                                           
##       ROE            DT_pct         LNG_pct          CPBH_pct        
##  Min.   :0.330   Min.   :-9.77   Min.   :-25.31   Min.   :-220.8927  
##  1st Qu.:0.385   1st Qu.:-1.86   1st Qu.:-11.43   1st Qu.:  -7.1424  
##  Median :0.405   Median : 3.63   Median :  2.97   Median :   0.0842  
##  Mean   :0.430   Mean   : 7.62   Mean   :  2.84   Mean   : -21.9036  
##  3rd Qu.:0.464   3rd Qu.:10.60   3rd Qu.: 13.58   3rd Qu.:   8.1067  
##  Max.   :0.606   Max.   :34.42   Max.   : 40.41   Max.   :  22.5678  
##                  NA's   :1       NA's   :1        NA's   :1          
##     CPQL_pct         CFO_pct          HTK_pct          NVNH_pct     
##  Min.   :-207.6   Min.   :-315.1   Min.   :-36.72   Min.   :-21.29  
##  1st Qu.: -19.7   1st Qu.:-233.4   1st Qu.:-22.61   1st Qu.:-11.79  
##  Median : -17.8   Median : -98.0   Median : -9.73   Median :  6.67  
##  Mean   : -20.0   Mean   : 415.3   Mean   :  8.12   Mean   : 14.30  
##  3rd Qu.:  31.9   3rd Qu.:  58.2   3rd Qu.: 30.76   3rd Qu.: 22.05  
##  Max.   :  40.4   Max.   :4700.5   Max.   : 82.11   Max.   : 97.36  
##  NA's   :1        NA's   :1        NA's   :1        NA's   :1       
##     PTNH_pct        TTS_pct           VCSH_pct    
##  Min.   :-17.5   Min.   :-10.727   Min.   :-2.42  
##  1st Qu.: -4.9   1st Qu.: -0.871   1st Qu.: 1.23  
##  Median : 15.3   Median :  6.433   Median : 6.01  
##  Mean   : 11.0   Mean   :  7.413   Mean   : 6.53  
##  3rd Qu.: 23.9   3rd Qu.:  8.023   3rd Qu.: 6.95  
##  Max.   : 52.1   Max.   : 34.369   Max.   :21.90  
##  NA's   :1       NA's   :1         NA's   :1

Yếu tố kĩ thuật

  • Hàm summary() được sử dụng để nhanh chóng xác định phân bố dữ liệu (tứ phân vị, Min, Max, Mean, Median)

Kết luận

Bảng thống kê 10 năm cho thấy PET tăng trưởng mạnh về quy mô và hiệu suất sinh lời, song tiềm ẩn rủi ro thanh khoản và bất thường chi phí. Các chỉ tiêu như doanh thu (DT), tổng tài sản (TTS) và vốn chủ sở hữu (VCSH) đều tăng ổn định, trong khi ROE và ROA duy trì ở mức cao, phản ánh khả năng sinh lời vững chắc. Tuy nhiên, dòng tiền hoạt động (CFO) biến động mạnh, có nhiều năm âm sâu, cho thấy khó khăn trong việc chuyển lợi nhuận kế toán thành tiền mặt. Bên cạnh đó, các giá trị âm bất thường ở chi phí bán hàng (CPBH) và chi phí quản lý (CPQL) – do hoàn nhập chi phí – là dấu hiệu kế toán cần được xem xét kỹ lưỡng.

3.2 Phân nhóm cho bộ dữ liệu

3.2.1 Nhóm hiệu quả hoạt động kinh doanh

hieuqua <- financial_wide %>%
  select(Nam, ROA, ROE)
head(hieuqua)
## # A tibble: 6 × 3
##     Nam   ROA   ROE
##   <dbl> <dbl> <dbl>
## 1  2015 0.142 0.606
## 2  2016 0.106 0.400
## 3  2017 0.121 0.450
## 4  2018 0.119 0.409
## 5  2019 0.123 0.373
## 6  2020 0.106 0.402

Yếu tố kĩ thuật

  • Hàm select(Nam, ROA, ROE) chọn các biến: ROA, ROE, Nam
  • head() hiển thị 5–6 quan sát đầu tiên để kiểm tra dữ liệu ban đầu.

Kết luận

Dữ liệu cho thấy biến động ROA và ROE qua các năm, cung cấp nền tảng cho phân tích xu hướng và so sánh hiệu quả hoạt động.

3.2.2 Nhóm tài chính (Vốn & Nợ)

taichinh <- financial_wide %>%
  select(Nam, TTS, VCSH, NVNH, PTNH)
head(taichinh)
## # A tibble: 6 × 5
##     Nam           TTS          VCSH          NVNH          PTNH
##   <dbl>         <dbl>         <dbl>         <dbl>         <dbl>
## 1  2015 5764543759971 1351864916364 2158724907518 1128003690821
## 2  2016 6227006416270 1647859363977 2302634027522 1425783612685
## 3  2017 6172779246067 1660580760604 2031170448265 1684907681145
## 4  2018 5563061574237 1620409549507 1614290757720 1602337603339
## 5  2019 4966334501358 1640317600389 1270668669493 1335173232707
## 6  2020 6320756540108 1663165996022 2507845798049 1654532686040

Yếu tố kĩ thuật

  • Tương tự 3.2.1

Kết luận

Dữ liệu dạng tổng giá trị theo năm, cần chuẩn hóa nếu phân tích tỷ lệ hay hệ số tài chính.

3.2.3 Nhóm doanh thu & chi phí

dongtien <- financial_wide %>%
  select(Nam, CFO)
head(dongtien)
## # A tibble: 6 × 2
##     Nam          CFO
##   <dbl>        <dbl>
## 1  2015 386645716621
## 2  2016 202890081636
## 3  2017   4108698885
## 4  2018 197237097985
## 5  2019 312122602166
## 6  2020 -40515989177

Yếu tố kĩ thuật

  • Tương tự 3.2.3

Kết luận

Giá trị CFO có thể âm, phản ánh dòng tiền tiêu cực trong một số năm. Thích hợp để phân tích khả năng tạo tiền từ hoạt động kinh doanh và đánh giá rủi ro thanh khoản.

3.3 5 năm có hiệu quả hoạt động kinh doanh phát triển mạnh

financial_wide <- financial_wide %>%
  mutate(CFO_scaled = (CFO - min(CFO, na.rm=TRUE)) / (max(CFO, na.rm=TRUE) - min(CFO, na.rm=TRUE)), Performance_Score = ROE + ROA + CFO_scaled)
top5_years <- financial_wide %>%
  arrange(desc(Performance_Score)) %>%
  slice_head(n = 5) %>%
  select(Nam, ROE, ROA, CFO, Performance_Score)
top5_years
## # A tibble: 5 × 5
##     Nam   ROE    ROA          CFO Performance_Score
##   <dbl> <dbl>  <dbl>        <dbl>             <dbl>
## 1  2015 0.606 0.142  386645716621              1.73
## 2  2024 0.381 0.0875 399996509899              1.47
## 3  2019 0.373 0.123  312122602166              1.37
## 4  2018 0.409 0.119  197237097985              1.24
## 5  2016 0.400 0.106  202890081636              1.22

Yếu tố kĩ thuật

  • CFO_scaled chuẩn hóa (scaled) để đưa về cùng thang đo; arrange(desc(…)): Sắp xếp từ năm có hiệu suất cao nhất xuống thấp nhất.
  • slice_head(n = 5) dùng để giữ 5 năm đứng đầu; select(…) chỉ hiển thị các cột quan trọng.

Kết luận

Các năm 2015, 2024, 2019, 2018 và 2016 là giai đoạn PET đạt hiệu quả hoạt động cao nhất. Năm 2015 nổi bật nhất với ROE 60,6%, ROA 14,2% và CFO 386,6 tỷ đồng, thể hiện khả năng sinh lời và tạo dòng tiền vượt trội. Năm 2024 tiếp tục duy trì hiệu quả cao với CFO tăng mạnh dù ROE và ROA giảm nhẹ. Giai đoạn 2018–2019 ghi nhận hiệu suất ổn định, phản ánh quản trị tài sản và chi phí hiệu quả, trong khi 2016 duy trì lợi nhuận tốt nhưng dòng tiền thấp hơn.

3.6 Đánh giá khả năng tự chủ vốn và vay nợ hợp lý (Debt_Ratio ≤ 0.6)

financial_wide <- financial_wide %>%
mutate(Debt_Ratio = (NVNH + PTNH) / TTS)
financial_stable <- financial_wide %>%
  filter(Debt_Ratio <= 0.6) %>%   
  select(Nam, TTS, VCSH, NVNH, PTNH, Debt_Ratio) %>%
  arrange(Debt_Ratio)
financial_stable
## # A tibble: 4 × 6
##     Nam           TTS          VCSH          NVNH          PTNH Debt_Ratio
##   <dbl>         <dbl>         <dbl>         <dbl>         <dbl>      <dbl>
## 1  2019 4966334501358 1640317600389 1270668669493 1335173232707      0.525
## 2  2015 5764543759971 1351864916364 2158724907518 1128003690821      0.570
## 3  2018 5563061574237 1620409549507 1614290757720 1602337603339      0.578
## 4  2016 6227006416270 1647859363977 2302634027522 1425783612685      0.599

Yếu tố kĩ thuật

  • Hàm mutate() để thêm cột Debt_Ratio = (NVNH + PTNH) / TTS tính tổng nợ trên tổng tài sản.
  • Hàm filter(Debt_Ratio <= 0.6) lọc các năm nợ vay hợp lý; arrange(Debt_Ratio) sắp xếp các năm từ tỷ lệ nợ thấp → cao

Kết luận

Dựa trên tiêu chí Debt_Ratio ≤ 0.6, PET có 4 năm duy trì cơ cấu vốn an toàn: 2019, 2015, 2018 và 2016. Trong đó, năm 2019 có tỷ lệ nợ thấp nhất (0.525), thể hiện khả năng kiểm soát đòn bẩy hiệu quả; các năm 2015–2018 duy trì quanh mức 0.57–0.58, phản ánh chiến lược vay nợ hợp lý; năm 2016 gần ngưỡng 0.6 nhưng vẫn trong mức an toàn.

3.7 So sánh doanh thu và chi phí của năm 2020 và 2024

data_compare <- financial_wide %>%
  filter(Nam %in% c(2020, 2024)) %>%
  select(Nam, DT, CPBH, CPQL, LNG)
compare_2020_2024 <- data_compare %>%
  summarise(
    DT_change = (DT[Nam == 2024] - DT[Nam == 2020]) / DT[Nam == 2020] * 100,
    CPBH_change = (CPBH[Nam == 2024] - CPBH[Nam == 2020]) / CPBH[Nam == 2020] * 100,
    CPQL_change = (CPQL[Nam == 2024] - CPQL[Nam == 2020]) / CPQL[Nam == 2020] * 100,
    LNG_change = (LNG[Nam == 2024] - LNG[Nam == 2020]) / LNG[Nam == 2020] * 100)
compare_2020_2024
## # A tibble: 1 × 4
##   DT_change CPBH_change CPQL_change LNG_change
##       <dbl>       <dbl>       <dbl>      <dbl>
## 1      41.6       -275.       -203.       33.0

Yếu tố kĩ thuật

  • Hàm filter(Nam %in% c(2020, 2024)) lọc ra dữ liệu của hai năm cần so sánh — năm 2020 và 2024.
  • Hàm select(Nam, DT, CPBH, CPQL, LNG): dựa vào nhóm doanh thu và chi phí đã được phân ở trên.
  • Hàm summarise() tổng hợp dữ liệu bằng cách tính tỷ lệ thay đổi (%) của từng biến giữa năm 2024 và 2020.
  • Hàm compare_2020_2024 kết quả cuối cùng chỉ có một dòng dữ liệu (1 hàng).

Kết luận

So sánh năm 2020 và 2024 cho thấy PET tăng trưởng tích cực cả về quy mô và hiệu quả hoạt động. Doanh thu tăng 41,6%, phản ánh khả năng mở rộng thị phần và phục hồi mạnh mẽ sau giai đoạn khó khăn. Đáng chú ý, chi phí bán hàng và chi phí quản lý giảm sâu (-275% và -203%), cho thấy doanh nghiệp đã tái cấu trúc hiệu quả, nâng cao năng suất và kiểm soát chi phí chặt chẽ. Lợi nhuận gộp tăng 33%, khẳng định tăng trưởng không chỉ đến từ quy mô mà còn từ hiệu suất vận hành.

3.8 Các năm có ROE > 30% và ROA >= trung bình

financial_wide <- financial_wide %>%
  mutate( ROA_level = case_when(ROA >= 0.01 ~ "Tốt", ROA >= 0.005 ~ "Trung bình", TRUE ~ "Yếu" ) )
high_perf_years <- financial_wide %>%
  filter(ROE > 0.30, ROA_level %in% c("Tốt", "Trung bình")) %>%
  select(Nam, ROE, ROA, ROA_level)
high_perf_years
## # A tibble: 10 × 4
##      Nam   ROE    ROA ROA_level
##    <dbl> <dbl>  <dbl> <chr>    
##  1  2015 0.606 0.142  Tốt      
##  2  2016 0.400 0.106  Tốt      
##  3  2017 0.450 0.121  Tốt      
##  4  2018 0.409 0.119  Tốt      
##  5  2019 0.373 0.123  Tốt      
##  6  2020 0.402 0.106  Tốt      
##  7  2021 0.484 0.111  Tốt      
##  8  2022 0.469 0.107  Tốt      
##  9  2023 0.330 0.0762 Tốt      
## 10  2024 0.381 0.0875 Tốt

Yếu tố kĩ thuật

  • Hàm mutate() thêm một biến mới tên là ROA_level
  • Dựa trên các ngưỡng định sẵn: ROA ≥ 0.01: mức “Tốt”, 0.005 ≤ ROA < 0.01: mức “Trung bình”, ROA < 0.005: mức “Yếu”.
  • Hàm filter() giữ lại các năm có ROE > 0.30; select() chỉ giữ lại các cột quan trọng để trình bày kết quả: Nam, ROE, ROA, ROA_level.

Kết luận

Giai đoạn 2015–2024, PET duy trì ROE trên 30% và ROA ở mức “Tốt”, cho thấy hiệu quả sử dụng vốn và tài sản vượt trội. ROE cao phản ánh khả năng sinh lời mạnh từ vốn chủ sở hữu, trong khi ROA ổn định chứng tỏ tài sản được khai thác hiệu quả và bền vững. Dù hoạt động trong ngành có biên lợi nhuận thấp, PET vẫn đạt hiệu suất cao nhờ quản trị chi phí linh hoạt, tối ưu tồn kho và dòng tiền. Tỷ suất sinh lời duy trì ổn định nhiều năm khẳng định năng lực tài chính vững mạnh, mức độ tự chủ cao và chiến lược quản trị hiệu quả, tạo nền tảng cho tăng trưởng bền vững.

3.9 Đánh giá khả năng tạo dòng tiền từ hoạt động kinh doanh (CFO)

financial_wide <- financial_wide %>%
mutate(CFO_scaled = (CFO - min(CFO, na.rm = TRUE)) / (max(CFO, na.rm = TRUE) - min(CFO, na.rm = TRUE)))
top5_CFO_years <- financial_wide %>%
arrange(desc(CFO_scaled)) %>%
slice_head(n = 5) %>%
select(Nam, CFO, CFO_scaled)
top5_CFO_years
## # A tibble: 5 × 3
##     Nam          CFO CFO_scaled
##   <dbl>        <dbl>      <dbl>
## 1  2024 399996509899      1    
## 2  2015 386645716621      0.981
## 3  2019 312122602166      0.874
## 4  2016 202890081636      0.718
## 5  2018 197237097985      0.710

Yếu tố kĩ thuật

  • Hàm mutate() được dùng để tạo cột mới CFO_scaled; arrange(desc(CFO_scaled)) sắp xếp từ cao xuống thấp, slice_head(n = 5) chọn 5 năm có CFO nổi bật nhất; select() chỉ giữ các cột cần thiết để hiển thị.

Kết luận

Năm 2024 nổi bật với dòng tiền hoạt động (CFO) cao nhất (1.000), tiếp theo là 2015 (0.981), 2019 (0.874), 2016 (0.718) và 2018 (0.710), cho thấy PET tạo ra lượng tiền mặt lớn từ hoạt động cốt lõi. Các năm này phản ánh năng lực quản lý dòng tiền hiệu quả, khả năng thanh toán và tái đầu tư vững chắc. Đặc biệt, kết quả năm 2024 cho thấy sự cải thiện rõ rệt nhờ tối ưu vận hành, quản lý vốn lưu động và chi phí tốt hơn.

3.10 Phân tích mối quan hệ giữa dòng tiền hoạt động và vốn chủ sở hữu

financial_wide <- financial_wide %>%
  mutate(CFO_VCSH_Ratio = CFO / VCSH)
correlation_cfo_vcsh <- cor(financial_wide$CFO, financial_wide$VCSH, use = "complete.obs")
relationship_data <- financial_wide %>%
  select(Nam, CFO, VCSH, CFO_VCSH_Ratio)
relationship_data
## # A tibble: 10 × 4
##      Nam           CFO          VCSH CFO_VCSH_Ratio
##    <dbl>         <dbl>         <dbl>          <dbl>
##  1  2015  386645716621 1351864916364        0.286  
##  2  2016  202890081636 1647859363977        0.123  
##  3  2017    4108698885 1660580760604        0.00247
##  4  2018  197237097985 1620409549507        0.122  
##  5  2019  312122602166 1640317600389        0.190  
##  6  2020  -40515989177 1663165996022       -0.0244 
##  7  2021   87140672273 1939727970659        0.0449 
##  8  2022 -167839738647 2062411237518       -0.0814 
##  9  2023 -299922459113 2186259767890       -0.137  
## 10  2024  399996509899 2338250462750        0.171
correlation_cfo_vcsh
## [1] -0.353

Yếu tố kĩ thuật

  • Hàm mutate(CFO_VCSH_Ratio) tạo biến để đo tỷ lệ giữa dòng tiền hoạt động (CFO) và vốn chủ sở hữu (VCSH)
  • Hàm cor(): tính hệ số tương quan giữa CFO và VCSH; select(..)dùng để chọn các biến trên
  • Bảng kết quả (relationship_data) giúp theo dõi biến động cụ thể từng năm.

Kết luận

Phân tích mối quan hệ giữa CFO và VCSH cho thấy PET từng trải qua giai đoạn dòng tiền biến động mạnh. Các năm 2020, 2022–2023 ghi nhận CFO âm, phản ánh áp lực vốn lưu động và hiệu quả sử dụng tài sản ngắn hạn chưa tối ưu. Tuy nhiên, đến năm 2024, CFO phục hồi dương với tỷ lệ CFO/VCSH đạt 0,171, cho thấy doanh nghiệp đã cải thiện khả năng chuyển hóa lợi nhuận thành tiền thực, kiểm soát tốt hơn công nợ và chi phí vận hành. Hệ số tương quan CFO–VCSH ở mức -0,35 cho thấy tăng trưởng vốn chủ chưa gắn liền với khả năng tạo tiền, có thể do tăng vốn hoặc giữ lại lợi nhuận. Dù vậy, xu hướng phục hồi năm 2024 là tín hiệu tích cực.

3.11 Phân tích biến động doanh thu và lợi nhuận gộp – nhận diện có xu hướng trái chiều

financial_wide %>%
  filter(DT_pct < 0, LNG_pct > 0) %>%
  select(Nam, DT, LNG, DT_pct, LNG_pct, CPBH_pct, CPQL_pct, CFO_pct)
## # A tibble: 1 × 8
##     Nam      DT          LNG DT_pct LNG_pct CPBH_pct CPQL_pct CFO_pct
##   <dbl>   <dbl>        <dbl>  <dbl>   <dbl>    <dbl>    <dbl>   <dbl>
## 1  2022 1.75e13 967103878018 -0.312    2.97     18.0    -45.9   -293.

Yếu tố kĩ thuật

  • Hàm filter(DT_pct < 0, LNG_pct > 0): lọc các năm có doanh thu giảm nhưng lợi nhuận gộp tăng; select(…): chỉ giữ lại các cột quan trọng để phân tích mối liên hệ giữa doanh thu, lợi nhuận và chi phí.

Kết luận

Năm 2022 là giai đoạn đặc biệt khi doanh thu giảm 31,2% nhưng lợi nhuận gộp vẫn tăng 2,97%, cho thấy PET đã tối ưu chi phí và cải thiện biên lợi nhuận. Dù chi phí bán hàng tăng 18%, chi phí quản lý giảm mạnh 45,9%, phản ánh nỗ lực tái cấu trúc và tinh giản bộ máy. Tuy nhiên, dòng tiền hoạt động (CFO) giảm gần 293% cho thấy áp lực thanh khoản còn lớn.

3.12 Phân tích sự ổn định chi phí bán hàng và quản lý so với doanh thu

financial_wide <- financial_wide %>%
  mutate(CPBH_DT_change = CPBH_pct - DT_pct, CPQL_DT_change = CPQL_pct - DT_pct)
top5_stable_cost <- financial_wide %>%
  arrange(abs(CPBH_DT_change) + abs(CPQL_DT_change)) %>%
  slice_head(n = 5) %>%
  select(Nam, DT_pct, CPBH_pct, CPQL_pct, CPBH_DT_change, CPQL_DT_change)
top5_stable_cost
## # A tibble: 5 × 6
##     Nam DT_pct CPBH_pct CPQL_pct CPBH_DT_change CPQL_DT_change
##   <dbl>  <dbl>    <dbl>    <dbl>          <dbl>          <dbl>
## 1  2021  30.8   22.6        34.4         -8.24            3.61
## 2  2016  -7.23 -16.0       -19.5         -8.74          -12.3 
## 3  2018   3.63  -5.86      -17.8         -9.49          -21.4 
## 4  2017   8.31   8.11       40.4         -0.203          32.1 
## 5  2023  -1.86   0.0842     31.9          1.94           33.8

Yếu tố kĩ thuật

  • Hàm mutate() sử dụng để tạo các biến dựa trên tính toán; arrange(…) sắp xếp các dòng theo tổng độ lệch tuyệt đối abs(CPBH_DT_change) + abs(CPQL_DT_change) tăng dần
  • Lấy 5 dòng đầu tiên (slice_head(n = 5)); top5_stable_cost 5 năm có chi phí bán hàng và quản lý gần với doanh thu nhất

Kết luận

Năm 2021 là giai đoạn ổn định nhất khi doanh thu tăng 30,8% nhưng chi phí bán hàng chỉ tăng 22,6% và chi phí quản lý tăng 34,4%, cho thấy PET kiểm soát chi phí hiệu quả. Các năm 2016 và 2018 chi phí giảm mạnh so với doanh thu, phản ánh nỗ lực tối ưu hóa hoạt động. Ngược lại, năm 2017 và 2023 chi phí quản lý tăng vượt doanh thu, cho thấy xu hướng mở rộng hoặc đầu tư dài hạn.

3.13 Cân bằng tài chính: Tỷ trọng vốn và áp lực nợ

financial_wide <- financial_wide %>%
  mutate(Equity_Ratio = VCSH / TTS, ShortDebt_Ratio = NVNH / TTS, LongDebt_Ratio = PTNH / TTS )
high_debt_years <- financial_wide %>%
  filter((NVNH + PTNH) / TTS > 0.5) %>%
  select(Nam, TTS, VCSH, NVNH, PTNH, Equity_Ratio, ShortDebt_Ratio, LongDebt_Ratio)
high_debt_years
## # A tibble: 10 × 8
##      Nam     TTS          VCSH         NVNH    PTNH Equity_Ratio ShortDebt_Ratio
##    <dbl>   <dbl>         <dbl>        <dbl>   <dbl>        <dbl>           <dbl>
##  1  2015 5.76e12 1351864916364      2.16e12 1.13e12        0.235           0.374
##  2  2016 6.23e12 1647859363977      2.30e12 1.43e12        0.265           0.370
##  3  2017 6.17e12 1660580760604      2.03e12 1.68e12        0.269           0.329
##  4  2018 5.56e12 1620409549507      1.61e12 1.60e12        0.291           0.290
##  5  2019 4.97e12 1640317600389      1.27e12 1.34e12        0.330           0.256
##  6  2020 6.32e12 1663165996022      2.51e12 1.65e12        0.263           0.397
##  7  2021 8.49e12 1939727970659      3.56e12 2.52e12        0.228           0.419
##  8  2022 9.04e12 2062411237518      4.35e12 2.08e12        0.228           0.481
##  9  2023 9.48e12 2186259767890      4.52e12 2.39e12        0.231           0.476
## 10  2024 1.02e13 2338250462750      4.98e12 2.45e12        0.230           0.490
## # ℹ 1 more variable: LongDebt_Ratio <dbl>

Yếu tố kĩ thuật

  • Sử dụng mutate() để tính toán các tỷ lệ: Equity_Ratio, ShortDebt_Ratio, LongDebt_Ratio.
  • Lọc các năm có nợ cao với tổng nợ chiếm >50% tổng tài sản với filter(..)
  • Hàm select() được dùng để hiển thị các chỉ tiêu theo năm.

Kết luận

Giai đoạn 2015–2024, cơ cấu vốn của PET cho thấy xu hướng giảm tự chủ tài chính. Tỷ lệ vốn chủ sở hữu dao động 0,228–0,330, cao nhất năm 2019 (0,330), thể hiện giai đoạn an toàn tài chính tốt nhất. Tuy nhiên, từ 2021–2024, Equity_Ratio chỉ quanh 0,23, phản ánh sự gia tăng vay nợ. Nợ ngắn hạn tăng mạnh, đạt 0,490 năm 2024, cho thấy áp lực thanh toán cao; nợ dài hạn cũng tăng lên mức tương tự, làm gia tăng gánh nặng lãi vay.

3.13 Kiểm tra áp lực lợi nhuận: khi chi phí tăng nhanh hơn doanh thu

financial_wide <- financial_wide %>% arrange(Nam)
financial_wide <- financial_wide %>%
  mutate(CP_total = CPBH + CPQL, DT_pct = (DT - lag(DT)) / lag(DT) * 100, CP_total_pct = (CP_total - lag(CP_total)) / lag(CP_total) * 100, DT_minus_CP = DT_pct - CP_total_pct )
high_cost_pressure_years <- financial_wide %>%
  filter(DT_minus_CP < 0) %>% 
  select(Nam, DT_pct, CP_total_pct, DT_minus_CP)
high_cost_pressure_years
## # A tibble: 3 × 4
##     Nam DT_pct CP_total_pct DT_minus_CP
##   <dbl>  <dbl>        <dbl>       <dbl>
## 1  2017   8.31        21.9        -13.6
## 2  2019  -9.77         7.08       -16.8
## 3  2023  -1.86         9.76       -11.6

Yếu tố kĩ thuật

  • Lag() → so sánh năm hiện tại với năm trước để tính tỷ lệ thay đổi theo năm.
  • DT_minus_CP → đo mức độ tăng trưởng doanh thu vượt trội hay kém hơn chi phí.
  • DT_minus_CP < 0 → chi phí tăng nhanh hơn doanh thu
  • DT_minus_CP > 0 → doanh thu tăng nhanh hơn chi phí
  • Filter(DT_minus_CP < 0) → giữ lại những năm cần cảnh báo quản trị chi phí; select() được dùng để hiển thị các chỉ tiêu theo năm.

Kết luận

Giai đoạn 2015–2024 cho thấy có nhiều năm chi phí tăng nhanh hơn doanh thu, gây áp lực lên lợi nhuận. Năm 2017, doanh thu tăng 8,31% nhưng chi phí tăng 21,9%, khiến hiệu quả kinh doanh giảm. Năm 2019 tình hình nghiêm trọng hơn khi doanh thu giảm 9,77% còn chi phí tăng 7,08%, phản ánh rủi ro lợi nhuận lớn. Đến năm 2023, doanh thu giảm nhẹ 1,86% nhưng chi phí vẫn tăng 9,76%, tiếp tục gây áp lực tài chính.

3.14 Phân tích cảnh báo vay nợ qua biến đổi tổng tài sản và vốn chủ sở hữu

financial_wide <- financial_wide %>%
  arrange(Nam) %>%
  mutate( TTS_pct = (TTS - lag(TTS)) / lag(TTS) * 100, VCSH_pct = (VCSH - lag(VCSH)) / lag(VCSH) * 100 )
debt_alert_years <- financial_wide %>%
  filter(TTS_pct > 10, VCSH_pct < 0) %>%
  select(Nam, TTS, VCSH, TTS_pct, VCSH_pct)
debt_alert_years
## # A tibble: 0 × 5
## # ℹ 5 variables: Nam <dbl>, TTS <dbl>, VCSH <dbl>, TTS_pct <dbl>,
## #   VCSH_pct <dbl>

Yếu tố kĩ thuật

  • Filter(….) lọc ra các năm mà tổng tài sản tăng trên 10% so với năm trước nhưng vốn chủ sở hữu giảm.
  • Chọn cột hiển thị với select(..); debt_alert_years liệt kê kết quả

Kết luận

Giai đoạn 2015–2024 cho thấy công ty duy trì cơ cấu vốn ổn định giữa tổng tài sản và vốn chủ sở hữu. Không có năm nào tổng tài sản tăng trên 10% trong khi vốn chủ sở hữu giảm, chứng tỏ công ty không mở rộng dựa trên nợ vay.

3.15 Những năm có hiệu quả hoạt động kinh doanh cao ROE > 0.4 & ROA > 0.1

high_eff_years <- financial_wide %>%
  filter(ROE > 0.4, ROA > 0.1) %>%
  select(Nam, ROE, ROA, DT, CPBH, CPQL, LNG)
high_eff_years
## # A tibble: 6 × 7
##     Nam   ROE   ROA      DT         CPBH         CPQL          LNG
##   <dbl> <dbl> <dbl>   <dbl>        <dbl>        <dbl>        <dbl>
## 1  2015 0.606 0.142 1.07e13 281547504063 218306356507 818837334301
## 2  2017 0.450 0.121 1.07e13 255769838013 246738361502 747908505299
## 3  2018 0.409 0.119 1.11e13 240776025051 202902993436 662441105552
## 4  2020 0.402 0.106 1.35e13 232406377528 201947554458 668899849127
## 5  2021 0.484 0.111 1.76e13 284855285475 271461913218 939199446211
## 6  2022 0.469 0.107 1.75e13 336201939859 146822803006 967103878018

Yếu tố kĩ thuật

  • Filter(..) lọc ra các năm công ty đạt hiệu quả hoạt động cao: ROE > 40% và ROA > 10%.
  • Hàm select(…) chọn các cột quan trọng trên; high_eff_years hiển thị kết quả các năm có hiệu quả cao.

Kết luận

Trong các năm có ROE > 0,4 và ROA > 0,1 (2015, 2017, 2018, 2020–2022), công ty đạt hiệu quả sinh lợi cao, thể hiện khả năng sử dụng vốn và tài sản hiệu quả. ROE dao động 0,402–0,606 và ROA 0,106–0,142, đặc biệt năm 2015 nổi bật với hiệu quả tối ưu. Giai đoạn này, doanh thu tăng ổn định (1,07e13–1,76e13) trong khi chi phí bán hàng và quản lý được kiểm soát tốt, giúp duy trì lợi nhuận gộp cao.

3.16 Đo lường hiệu quả kiểm soát chi phí bán hàng & quản lý doanh nghiệp

cp_ratio <- financial_wide %>%
  mutate(CPBH_DT_ratio = (CPBH / DT) * 100, CPQL_DT_ratio = (CPQL / DT) * 100, CP_total_ratio = ((CPBH + CPQL) / DT) * 100) %>%
  select(Nam, DT, CPBH, CPQL, CPBH_DT_ratio, CPQL_DT_ratio, CP_total_ratio) %>%
  arrange(desc(CP_total_ratio))
cp_ratio
## # A tibble: 10 × 7
##      Nam      DT        CPBH     CPQL CPBH_DT_ratio CPQL_DT_ratio CP_total_ratio
##    <dbl>   <dbl>       <dbl>    <dbl>         <dbl>         <dbl>          <dbl>
##  1  2019 1.00e13     2.24e11  2.51e11          2.23         2.51            4.75
##  2  2017 1.07e13     2.56e11  2.47e11          2.39         2.31            4.69
##  3  2015 1.07e13     2.82e11  2.18e11          2.64         2.05            4.69
##  4  2016 9.88e12     2.37e11  1.76e11          2.39         1.78            4.17
##  5  2018 1.11e13     2.41e11  2.03e11          2.17         1.83            4.00
##  6  2020 1.35e13     2.32e11  2.02e11          1.73         1.50            3.23
##  7  2021 1.76e13     2.85e11  2.71e11          1.62         1.54            3.16
##  8  2023 1.72e13     3.36e11  1.94e11          1.95         1.12            3.08
##  9  2022 1.75e13     3.36e11  1.47e11          1.92         0.837           2.75
## 10  2024 1.90e13    -4.07e11 -2.08e11         -2.14        -1.09           -3.23

Yếu tố kĩ thuật

  • Hàm mutate(…) thêm 3 cột mới: (CPBH / DT) * 100 và (CPQL / DT) * 100 chuyển tỷ lệ thành phần trăm.
  • Hàm select(…) chỉ giữ cột cần xuất báo cáo; arrange(desc(CP_total_ratio)) sắp giảm dần theo tổng tỷ lệ
  • Kết quả cp_ratio là một tibble/data.frame sẵn in hoặc xuất sang bảng/plot.

Kết luận

Giai đoạn 2015–2019, tỷ lệ CPBH và CPQL/DT dao động 4–4,8%, cao nhất năm 2019 (4,75%), cho thấy chi phí quản trị tăng nhanh hơn doanh thu, có thể do mở rộng hệ thống hoặc bộ máy cồng kềnh. Từ 2020–2023, tỷ lệ giảm còn khoảng 3%, phản ánh kiểm soát chi phí tốt hơn nhờ tái cấu trúc và cắt giảm chi tiêu. Năm 2024 xuất hiện giá trị âm (-3,23%) do yếu tố kỹ thuật, cần được loại trừ khi phân tích.

3.17 Kiểm tra “hiệu quả sử dụng vốn lưu động”

working_capital_eff <- financial_wide %>%
  mutate(WC_Turnover = DT / NVNH) %>%
  select(Nam, DT, NVNH, WC_Turnover)
print(working_capital_eff)
## # A tibble: 10 × 4
##      Nam      DT          NVNH WC_Turnover
##    <dbl>   <dbl>         <dbl>       <dbl>
##  1  2015 1.07e13 2158724907518        4.93
##  2  2016 9.88e12 2302634027522        4.29
##  3  2017 1.07e13 2031170448265        5.27
##  4  2018 1.11e13 1614290757720        6.87
##  5  2019 1.00e13 1270668669493        7.88
##  6  2020 1.35e13 2507845798049        5.36
##  7  2021 1.76e13 3560524219400        4.94
##  8  2022 1.75e13 4345647248842        4.04
##  9  2023 1.72e13 4515926256500        3.81
## 10  2024 1.90e13 4980375100043        3.82

Yếu tố kĩ thuật

  • Hàm mutate() tạo biến mới WC_Turnover; select() chỉ giữ lại các cột cần thiết; print() để in ra kết quả

Kết luận

Giai đoạn 2015–2019, chỉ số WC_Turnover tăng từ 4,93 lên 7,88 lần, phản ánh việc sử dụng vốn lưu động hiệu quả và quản lý tốt tồn kho, công nợ. Từ 2020–2024, chỉ số giảm còn 3,82 lần dù doanh thu tăng, cho thấy vốn lưu động tăng nhanh hơn doanh thu, có thể do tồn kho lớn hoặc thu hồi công nợ chậm.

3.18 Đánh giá hiệu quả sử dụng hàng tồn kho của doanh nghiệp

inventory_eff <- financial_wide %>%
  mutate(Inventory_Turnover = DT / HTK) %>%
  select(Nam, DT, HTK, Inventory_Turnover)
  inventory_eff
## # A tibble: 10 × 4
##      Nam      DT           HTK Inventory_Turnover
##    <dbl>   <dbl>         <dbl>              <dbl>
##  1  2015 1.07e13 1557768487510               6.84
##  2  2016 9.88e12  985760389976              10.0 
##  3  2017 1.07e13  790864877778              13.5 
##  4  2018 1.11e13 1034126786928              10.7 
##  5  2019 1.00e13 1144702735496               8.74
##  6  2020 1.35e13  811313791115              16.6 
##  7  2021 1.76e13 1477490784121              11.9 
##  8  2022 1.75e13 2474958959872               7.09
##  9  2023 1.72e13 1915425030586               8.99
## 10  2024 1.90e13 1729042154866              11.0

Yếu tố kĩ thuật

  • Hàm mutate() để tạo biến mới và select() để chọn ra các cột cần thiết.

Kết luận

Giai đoạn 2015–2024, chỉ số Inventory_Turnover biến động mạnh, phản ánh hiệu quả quản lý tồn kho thay đổi qua từng năm. Từ 2015–2017, chỉ số tăng từ 6,84 lên 13,5 lần, cho thấy khả năng tiêu thụ hàng hóa và quay vòng vốn tốt. Giai đoạn 2018–2019, chỉ số giảm còn khoảng 8,7 lần, tồn kho tăng nhanh hơn doanh thu. Năm 2020 đạt đỉnh 16,6 lần nhờ quản lý tồn kho hiệu quả, nhưng 2021–2024 giảm còn 7,09–11,0 lần, đặc biệt năm 2022 thấp nhất, cảnh báo vốn bị ứ đọng.

3.19 Xác định biến tăng trưởng nổi bật theo năm

financial_growth <- financial_wide %>%
  arrange(Nam) %>%
  mutate(DT_pct = (DT - lag(DT))/lag(DT) * 100, CPBH_pct = (CPBH - lag(CPBH))/lag(CPBH) * 100, CPQL_pct = (CPQL - lag(CPQL))/lag(CPQL) * 100 )
financial_growth <- financial_growth %>%
  mutate(Max_Variable = pmax(DT_pct, CPBH_pct, CPQL_pct, na.rm = TRUE))
financial_growth_summary <- financial_growth %>%
  select(Nam, DT_pct, CPBH_pct, CPQL_pct, Max_Variable)
print(financial_growth_summary, n = Inf)
## # A tibble: 10 × 5
##      Nam DT_pct  CPBH_pct CPQL_pct Max_Variable
##    <dbl>  <dbl>     <dbl>    <dbl>        <dbl>
##  1  2015 NA       NA          NA          NA   
##  2  2016 -7.23   -16.0       -19.5        -7.23
##  3  2017  8.31     8.11       40.4        40.4 
##  4  2018  3.63    -5.86      -17.8         3.63
##  5  2019 -9.77    -7.14       23.9        23.9 
##  6  2020 34.4      3.95      -19.7        34.4 
##  7  2021 30.8     22.6        34.4        34.4 
##  8  2022 -0.312   18.0       -45.9        18.0 
##  9  2023 -1.86     0.0842     31.9        31.9 
## 10  2024 10.6   -221.       -208.         10.6

Yếu tố kĩ thuật

  • Arrange(Nam) sắp xếp dữ liệu trước khi dùng lag(); tạo cột Max_Variable = biến có % tăng mạnh nhất mỗi năm
  • Lag(DT): dùng để tính sự thay đổi so với năm trước; pmax(…, na.rm = TRUE): chọn giá trị lớn nhất trong các cột %, bỏ qua NA.
  • Select(…): chọn các cột quan trọng để in/so sánh.

Kết quả

Trong giai đoạn 2015–2024, tốc độ tăng trưởng doanh thu, chi phí bán hàng và chi phí quản lý cho thấy sự biến động đáng kể, phản ánh hiệu quả quản trị chi phí của doanh nghiệp. Giai đoạn đầu (2015–2016), doanh thu giảm nhẹ nhưng chi phí được cắt giảm linh hoạt; năm 2017 chi phí quản lý tăng mạnh, cho thấy kiểm soát hành chính chưa hiệu quả. Từ 2018–2019, biến động doanh thu trái chiều với chi phí khiến lợi nhuận chịu áp lực; giai đoạn 2020–2021 doanh thu tăng đột biến song chi phí quản lý vẫn cao. Những năm 2022–2024, chi phí quản lý tiếp tục biến động mạnh, đặc biệt năm 2024 chi phí giảm trong khi doanh thu tăng, có thể do yếu tố bất thường.

3.20 Tổng hợp và đánh giá toàn diện hiệu quả tài chính

financial_overview <- financial_wide %>%
  arrange(Nam) %>%
  mutate(DT_pct = (DT - lag(DT)) / lag(DT) * 100, CPBH_pct = (CPBH - lag(CPBH)) / lag(CPBH) * 100, CPQL_pct = (CPQL - lag(CPQL)) / lag(CPQL) * 100, LNG_pct = (LNG - lag(LNG)) / lag(LNG) * 100, CPBH_ratio = CPBH / DT * 100, CPQL_ratio = CPQL / DT * 100, WC_Turnover = DT / NVNH, Inventory_Turnover = DT / HTK) %>%
  rowwise() %>%
  mutate( Max_Impact = max(c_across(c(DT_pct, CPBH_pct, CPQL_pct, LNG_pct)), na.rm = TRUE)) %>%
  ungroup() %>%
  select(Nam, DT_pct, CPBH_pct, CPQL_pct, LNG_pct, CPBH_ratio, CPQL_ratio, WC_Turnover, Inventory_Turnover, Max_Impact)
print(financial_overview, n = Inf)
## # A tibble: 10 × 10
##      Nam DT_pct  CPBH_pct CPQL_pct LNG_pct CPBH_ratio CPQL_ratio WC_Turnover
##    <dbl>  <dbl>     <dbl>    <dbl>   <dbl>      <dbl>      <dbl>       <dbl>
##  1  2015 NA       NA          NA     NA          2.64      2.05         4.93
##  2  2016 -7.23   -16.0       -19.5  -19.6        2.39      1.78         4.29
##  3  2017  8.31     8.11       40.4   13.6        2.39      2.31         5.27
##  4  2018  3.63    -5.86      -17.8  -11.4        2.17      1.83         6.87
##  5  2019 -9.77    -7.14       23.9   -7.75       2.23      2.51         7.88
##  6  2020 34.4      3.95      -19.7    9.45       1.73      1.50         5.36
##  7  2021 30.8     22.6        34.4   40.4        1.62      1.54         4.94
##  8  2022 -0.312   18.0       -45.9    2.97       1.92      0.837        4.04
##  9  2023 -1.86     0.0842     31.9  -25.3        1.95      1.12         3.81
## 10  2024 10.6   -221.       -208.    23.2       -2.14     -1.09         3.82
## # ℹ 2 more variables: Inventory_Turnover <dbl>, Max_Impact <dbl>

Yếu tố kĩ thuật

  • Arrange(Nam) sắp xếp dữ liệu trước khi dùng lag(); mutate(..)tạo các biến mới trên.
  • Rowwise() %>% mutate(Max_Impact = max(…)) %>% ungroup() tính giá trị lớn nhất trong các biến tăng trưởng % trên từng năm.
  • Select(…): Giữ các cột cần thiết để hiển thị; print(…, n = Inf): In toàn bộ bảng mà không ẩn dòng.

Kết luận

Giai đoạn 2015–2024, doanh thu, chi phí bán hàng và chi phí quản lý biến động rõ rệt, phản ánh áp lực và hiệu quả quản trị tài chính của công ty. Năm 2016, doanh thu giảm nhưng chi phí được cắt giảm mạnh, thể hiện khả năng kiểm soát tốt; ngược lại, từ 2017 trở đi, chi phí quản lý tăng nhanh hơn doanh thu, tiềm ẩn rủi ro lợi nhuận. Giai đoạn 2020–2021, doanh thu tăng mạnh và chi phí được kiểm soát hiệu quả, cho thấy mở rộng kinh doanh đi kèm quản lý tốt. Tuy nhiên, giai đoạn 2022–2024, chi phí quản lý và bán hàng biến động mạnh, có thể do yếu tố bất thường.

NỘI DUNG 4: TRỰC QUAN HÓA DỮ LIỆU

4.1 Biểu đồ đa lớp đường của doanh thu, lợi nhuận gộp,chi phí bán hàng theo năm

ggplot(financial_wide, aes(x=Nam)) +
  geom_line(aes(y=DT, color="Doanh thu")) +
  geom_line(aes(y=LNG, color="Lợi nhuận gộp")) +
  geom_line(aes(y=CPBH, color="Chi phí bán hàng")) +
  geom_point(aes(y=DT, color="Doanh thu")) +
  geom_point(aes(y=LNG, color="Lợi nhuận gộp")) +
  labs(title="Biến động DT, LNG và CPBH theo năm",
       y="Giá trị") +
  theme_minimal() +
  scale_color_manual(values=c("Doanh thu"="blue", "Lợi nhuận gộp"="green", "Chi phí bán hàng"="red"))

Yếu tố kỹ thuật

  • Sử dụng aes(color=…) phân biệt biến bằng màu sắc.
  • Geom_line() thể hiện xu hướng liên tục theo năm với đường nối; geom_point() hỗ trợ thể hiện điểm dữ liệu rõ hơn, trực quan hơn.
  • Scale_color_manual() tùy chỉnh màu sắc đúng sắc thái; theme_minimal() sử dụng giao diện biểu đồ tối giản.

Kết luận

Kết quả cho thấy doanh thu (đường màu xanh) tăng trưởng ổn định, đặc biệt từ 2020 trở đi, phản ánh sự mở rộng hoạt động kinh doanh và khả năng thu hút khách hàng tốt. Lợi nhuận gộp (đường màu xanh lá) có xu hướng ổn định, tăng nhẹ theo doanh thu, cho thấy công ty duy trì biên lợi nhuận tương đối hiệu quả và kiểm soát giá vốn hàng bán khá chặt chẽ. Trong khi đó, chi phí bán hàng (đường màu đỏ) duy trì mức thấp và ổn định trong hầu hết các năm, chỉ có sự sụt giảm mạnh ở năm 2024, có thể liên quan đến thay đổi chiến lược marketing hoặc ghi nhận kế toán.

4.2 Biểu đồ phân tán CFO và VCSH với màu ROA và đường hồi quy

ggplot(financial_wide, aes(x = CFO, y = VCSH, color = ROA)) +
  geom_point(size = 3, alpha = 0.8) +                   
  geom_smooth(method = "lm", se = FALSE, color = "black") +  
  labs(title = "Mối quan hệ giữa CFO và VCSH (màu theo ROA)",
       x = "Dòng tiền hoạt động (CFO)", y = "Vốn chủ sở hữu (VCSH)") +
  scale_color_gradient(low = "yellow", high = "red") +
  theme_minimal()

Yếu tố kĩ thuật

  • Ggplot() khởi tạo biểu đồ với trục x là CFO, trục y là VCSH, màu sắc thể hiện ROA.
  • Geom_point(), labs(),theme_minimal() đã giải thích ở 4.1; geom_smooth(method = “lm”) thêm đường hồi quy tuyến tính, không có vùng tin cậy (se = FALSE), màu đen làm nổi bật đường xu hướng.
  • Scale_color_gradient() tạo dải màu từ vàng đến đỏ thể hiện mức thấp đến cao của ROA.

Kết luận

Phân tích biểu đồ cho thấy mối quan hệ cơ bản giữa dòng tiền hoạt động (CFO) và vốn chủ sở hữu (VCSH) là nghịch biến, ngụ ý rằng các công ty tạo ra dòng tiền mạnh (CFO dương) có thể đang ưu tiên hoàn vốn cho cổ đông hơn là tích lũy VCSH. Điểm dữ liệu quan trọng nhất là công ty đạt ROA cao nhất (xấp xỉ 0.14, màu đỏ) lại nằm tại khu vực CFO rất cao (khoảng \(4\text{e}11\)) và VCSH thấp nhất (khoảng \(1.4\text{e}12\)). Ngược lại, các công ty có VCSH cao (trên \(2\text{e}12\)) thường có ROA thấp hơn (màu vàng/cam nhạt), cho thấy quy mô vốn lớn không đảm bảo tỷ suất sinh lời cao mà có thể dẫn đến hiệu suất vốn bị pha loãng.

4.3 Biểu đồ cột chồng tỷ lệ phần trăm chi phí bán hàng và quản lí theo năm

financial_wide %>%
  mutate(Total_CP = CPBH + CPQL,CPBH_pct = (CPBH / Total_CP) * 100,CPQL_pct = (CPQL / Total_CP) * 100) %>%
  ggplot(aes(x = factor(Nam))) +
  geom_bar(aes(y = CPBH_pct, fill = "CPBH"), stat = "identity") +
  geom_bar(aes(y = CPQL_pct, fill = "CPQL"), stat = "identity", position = "stack") +
  geom_text(aes(y = CPBH_pct / 2, label = round(CPBH_pct, 1)), color = "white", size = 3) +
  geom_text(aes(y = CPBH_pct + CPQL_pct / 2, label = round(CPQL_pct, 1)), color = "white", size = 3) +
  labs(title = "Tỷ lệ phần trăm chi phí bán hàng và chi phí quản lý doanh nghiệp theo năm",x = "Năm", y = "Tỷ lệ (%)") +
  scale_fill_manual(values = c("CPBH" = "steelblue", "CPQL" = "darkorange")) +
  theme_minimal()

Yếu tố kĩ thuật

  • Mutate(..) được dùng để tạo các biến mới trên.
  • Ggplot(aes(x = factor(Nam))) khởi tạo biểu đồ với trục x là năm dưới dạng biến phân loại; geom_bar(aes(y = CPBH_pct, fill = “CPBH”)..) vẽ cột thể hiện tỷ lệ phần trăm chi phí bán hàng, với màu sắc được gán nhãn là “CPBH” (để hiển thị trong chú giải màu); geom_bar(aes(y = CPQL_pct,..) vẽ cột chồng lên trên cột CPBH; geom_text() thêm nhãn số cho từng phần cột, đặt ở giữa phần chiều cao tương ứng của CPBH_pct và nằm chính giữa phần CPQL_pct trên đỉnh CPBH_pct.
  • Labs(), theme_minimal() đã giải thích ở 4.1
  • Scale_fill_manual() tùy chỉnh màu sắc cho từng loại chi phí, lựa chọn màu sắc khác biệt, dễ nhận biết.

Kết luận

Phân tích biểu đồ cho thấy một sự thay đổi cấu trúc chi phí hoạt động đáng kể kể từ năm 2022, khi tổng tỷ lệ chi phí đã tăng đột biến (đạt đỉnh gần 70% vào năm 2022) so với mức ổn định 50%-55% trong giai đoạn 2015-2021. Sự gia tăng này chủ yếu được thúc đẩy bởi chi phí quản lý doanh nghiệp (CPQL), vốn luôn chiếm tỷ trọng chủ đạo và đã tăng lên mức trên 63% trong tổng chi phí từ 2022 đến 2024. Điều này cho thấy công ty đã tăng cường đầu tư mạnh mẽ vào bộ máy quản trị, hệ thống hoặc cơ sở hạ tầng.

4.4 Biểu đồ hộp (boxplot) phân phối ROE và ROA phân nhóm theo năm

financial_wide %>%
  select(Nam, ROE, ROA) %>%
  pivot_longer(cols = c(ROE, ROA), names_to = "Ratio", values_to = "Value") %>%
  ggplot(aes(x = factor(Nam), y = Value, fill = Ratio)) +
  geom_boxplot(alpha = 0.6) +
  geom_jitter(width = 0.25, alpha = 0.3) +
  labs(title = "Phân phối ROE và ROA theo năm",x = "Năm", y = "Giá trị") +
  theme_minimal() + scale_fill_manual(values = c("ROE" = "lightblue", "ROA" = "pink"))

Yếu tố kĩ thuật

  • Select(Nam, ROE, ROA) chọn cột năm và 2 biến tỷ suất ROE, ROA để phân tích.
  • Pivot_longer(cols = c(ROE, ROA), names_to = “Ratio”, values_to = “Value”) chuyển dữ liệu từ dạng rộng sang dạng dài
  • Ggplot(aes(x = factor(Nam), y = Value, fill = Ratio) xây dựng biểu đồ với trục x là năm (chuyển thành biến phân loại để nhóm rời rạc), trục y là giá trị ROE/ROA, màu sắc theo loại tỷ suất (Ratio) để phân biệt; geom_boxplot(alpha = 0.6) vẽ biểu đồ hộp với alpha = 0.6 để làm mờ tránh che lấp dữ liệu; geom_jitter(width = 0.25, alpha = 0.3) thêm các điểm dữ liệu rải nhẹ sang ngang để giảm chồng chéovới alpha = 0.3 làm điểm trong suốt phù hợp với mật độ điểm.
  • Labs() theme_minimal() đã giải thích ở 4.1; scale_fill_manual() tùy chỉnh màu sắc cụ thể cho hai nhóm ROE (xanh nhạt) và ROA (hồng).

Kết luận

Biểu đồ cho thấy hiệu quả sinh lời của công ty, được đo bằng Tỷ suất sinh lời trên Vốn chủ sở hữu (ROE) và Tỷ suất sinh lời trên Tổng tài sản (ROA), có sự phân hóa rõ rệt qua các năm. ROA (màu đỏ nhạt, đường dưới) duy trì ở mức thấp và ổn định (khoảng \(0.05\) đến \(0.15\)), cho thấy hiệu quả sử dụng tổng tài sản để tạo ra lợi nhuận không có nhiều biến động và không cao. Ngược lại, ROE (màu xanh đậm, đường trên) duy trì ở mức cao hơn nhiều (khoảng \(0.35\) đến \(0.60\)), đặc biệt nổi bật với mức cao nhất vào năm 2015 và 2016. Sự chênh lệch lớn và nhất quán giữa ROE và ROA qua các năm là bằng chứng mạnh mẽ cho thấy công ty đã sử dụng đòn bẩy tài chính (Financial Leverage) một cách tích cực và hiệu quả để khuếch đại lợi nhuận cho cổ đông.

4.5 Biểu đồ đường thể hiện biến động lợi nhuận gộp và dòng tiền theo năm

financial_wide %>%
  ggplot(aes(x = Nam)) +
  geom_line(aes(y = LNG, color = "Lợi nhuận gộp")) +            
  geom_line(aes(y = CFO, color = "Dòng tiền hoạt động")) +    
  geom_point(aes(y = LNG, color = "Lợi nhuận gộp")) +
  geom_point(aes(y = CFO, color = "Dòng tiền hoạt động")) +
  labs(title = "Biến động LNG và CFO theo năm",y = "Giá trị (đồng)", x = "Năm") +
  scale_color_manual(values = c("Lợi nhuận gộp" = "blue", "Dòng tiền hoạt động" = "green")) +
  theme_minimal()

Yếu tố kĩ thuật

  • Ggplot(financial_wide, aes(x = Nam)) khởi tạo biểu đồ với dữ liệu financial_wide, đặt trục x là Nam; geom_line(aes()..) vẽ đường biểu diễn biến động lợi nhuận gộp theo năm với màu xanh dương; geom_line(aes(..) vẽ đường biểu diễn biến động dòng tiền hoạt động CFO với màu xanh lá; geom_point(aes(..) thêm các điểm dữ liệu trên đường để biểu diễn rõ hơn từng giá trị quan sát.
  • Labs(), theme_minimal() đã giải thích ở 4.1; scale_color_manual() tùy chỉnh màu sắc riêng biệt cho từng biến thể hiện trên biểu đồ.

Kết luận

Đồ thị cho thấy một sự khác biệt lớn giữa hiệu quả kế toán và hiệu quả tiền mặt: lợi nhuận gộp (màu xanh dương) duy trì ổn định ở mức cao (từ \(6\text{e}11\) đến \(1\text{e}12\)), chứng tỏ công ty có khả năng kiểm soát chi phí giá vốn hàng bán và giữ vững biên lợi nhuận cốt lõi. Ngược lại, dòng tiền hoạt động (CFO) (màu xanh lá) lại biến động mạnh và có xu hướng suy giảm trong giai đoạn 2021-2023 (thậm chí có năm gần bằng 0), chỉ ra một thách thức nghiêm trọng trong quản lý vốn lưu động (như gia tăng khoản phải thu hoặc tồn kho) khiến lợi nhuận không được chuyển hóa thành tiền mặt.

4.6 Biểu đồ cột so sánh tổng tài sản và vốn chủ sở hữu theo năm

financial_wide %>%
  pivot_longer(cols = c(TTS, VCSH), names_to = "Variable", values_to = "Value") %>%
  ggplot(aes(x = factor(Nam), y = Value, fill = Variable)) +
  geom_col(position = "dodge") +                              
  labs(title = "So sánh tổng tài sản và vốn chủ sở hữu theo năm",x = "Năm", y = "Giá trị (đồng)") +
  scale_fill_manual(values = c("TTS" = "steelblue", "VCSH" = "orange")) +
  theme_minimal()

Yếu tố kĩ thuật

  • Pivot_longer() chuyển từ dạng rộng sang dạng dài; geom_col(position = “dodge”) vẽ cột song song theo từng năm ; scale_fill_manual() tùy chỉnh màu sắc cho từng biến.
  • Labs(), theme_minimal()đã giải thích ở 4.1

Kết luận

Cả hai chỉ tiêu đều cho thấy sự tăng trưởng ổn định và ấn tượng, đặc biệt là sau năm 2020, phản ánh giai đoạn mở rộng hoạt động kinh doanh hoặc đầu tư vào tài sản. Tuy nhiên, điểm mấu chốt là tổng tài sản luôn lớn hơn vốn chủ sở hữu với một khoảng cách đáng kể và có xu hướng tiếp tục giãn rộng qua các năm, khẳng định rằng công ty đã chủ động sử dụng nợ và các nguồn vốn chiếm dụng khác làm nguồn tài trợ chính cho sự mở rộng quy mô. Chiến lược này giúp công ty khuếch đại tiềm năng sinh lời trên vốn cổ đông (ROE) và tận dụng được cơ hội thị trường để tăng trưởng tài sản lên mức hơn \(10\text{e}12\) vào năm 2024.

4.7 Biểu đồ scatter phân tán dòng tiền theo doanh thu, màu theo ROA, kích thước theo chi phí bán hàng

financial_wide %>%
  ggplot(aes(x = DT, y = CFO, color = ROA, size = CPBH)) +
  geom_point(alpha = 0.7) +
  scale_color_gradient(low = "blue", high = "red") +
  labs(title = "Phân tán CFO theo DT, màu theo ROA, kích thước theo CPBH",x = "Doanh thu (DT)", y = "Dòng tiền hoạt động (CFO)") +
  theme_minimal()

Yếu tố kĩ thuật

  • Ggplot(aes(x = DT, y = CFO, color = roa, size = CPBH)) đặt trục x là biến Doanh thu (DT), trục y là biến dòng tiền hoạt động (CFO); geom_point(alpha = 0.7) vẽ các điểm dữ liệu có độ trong suất (alpha = 0.7)
  • Scale_color_gradient(low = “blue”, high = “red”): tùy chỉnh dải màu từ xanh lam đến đỏ, biểu diễn từ giá trị thấp đến cao của roa.
  • Labs(…), theme_minimal() đã giải thích ở 4.1

Kết luận

Biểu đồ cho thấy sự phân hóa rõ rệt trong hiệu quả hoạt động và cấu trúc chi phí bán hàng. Nhìn chung, không có mối quan hệ tuyến tính rõ ràng giữa doanh thu (DT) và dòng tiền hoạt động (CFO). Tuy nhiên, hiệu quả sinh lời cao nhất (ROA \(\approx 0.14\), màu đỏ) tập trung chủ yếu ở các công ty có doanh thu thấp (khoảng \(1.0\text{e}13\) đến \(1.25\text{e}13\)) và CFO dương cao (trên \(2\text{e}11\)). Điều này ngụ ý rằng, các công ty quy mô nhỏ hơn lại có khả năng chuyển đổi doanh thu thành dòng tiền mặt hiệu quả nhất. Ngược lại, công ty có doanh thu cao nhất (khoảng \(1.75\text{e}13\)) lại có CFO âm sâu (khoảng \(-3\text{e}11\)) và ROA thấp nhất (màu xanh dương), cho thấy việc mở rộng quy mô doanh thu không đi kèm với khả năng kiểm soát vốn lưu động, dẫn đến hiệu suất sinh lời kém. Về chi phí bán hàng (CPBH - kích thước điểm), các công ty có CPBH âm lớn (kích thước nhỏ nhất) lại phân bố ở cả hai cực: vừa có CFO và ROA cao (góc trên bên trái), vừa có CFO âm và ROA thấp (góc dưới bên phải), cần phải xem xét thêm bối cảnh kinh doanh để đánh giá liệu việc giảm CPBH là do hiệu quả chiến lược hay cắt giảm đầu tư quá mức.

4.8 Biểu đồ hộp (boxplot) phân phối lợi nhuận gộp phân nhóm theo phải thu ngắn hạn

financial_wide %>%
  mutate(PTNH_class = ntile(PTNH, 4)) %>%    
  ggplot(aes(x = factor(PTNH_class), y = LNG, fill = factor(PTNH_class))) +
  geom_boxplot(alpha = 0.7) +
  labs(title = "Phân phối lợi nhuận gộp (LNG) theo nhóm PTNH", x = "Nhóm PTNH", y = "Lợi nhuận gộp (LNG)") +
  scale_fill_brewer(palette = "Set3") +
  theme_minimal()

Yếu tố kĩ thuật

  • Hàm mutate(PTNH_class = ntile(PTNH, 4)) chia biến PTNH thành 4 nhóm phân vị để phân loại dữ liệu.
  • Ggplot(aes(..)) thiết lập trục x là nhóm phân vị PTNH, trục y là giá trị LNG, màu sắc theo nhóm; geom_boxplot(alpha = 0.7) vẽ biểu đồ hộp với độ trong suốt alpha = 0.7
  • Labs(), theme_minimal() đã giải thích ở 4.1
  • Scale_fill_brewer(palette = “Set3”): Sử dụng bảng màu Set3 hài hòa, dễ nhìn.

Kết luận

Biểu đồ phân phối lợi nhuận gộp (LNG) theo nhóm phải thu ngắn hạn (PTNH) cho thấy một mối quan hệ đồng biến mạnh mẽ và có ý nghĩa chiến lược giữa việc phân loại nhóm và hiệu suất sinh lời cốt lõi. Cụ thể, khi cấp độ nhóm PTNH tăng, mức LNG trung vị cũng tăng lên rõ rệt, với nhóm 4 thể hiện hiệu suất tối ưu khi đạt mức LNG trung vị cao nhất (trên \(9\text{e}11\)) cùng với biến động thấp nhất, chứng tỏ khả năng tạo ra lợi nhuận cao và tính bền vững vượt trội. Ngược lại, các nhóm phân loại thấp như nhóm 1 thể hiện mức LNG trung vị thấp nhất và biến động lớn nhất, phản ánh sự thiếu ổn định nghiêm trọng trong việc kiểm soát giá vốn hàng bán và duy trì biên lợi nhuận.

4.9 Biểu đồ đường (line) thể hiện biến động hàng tồn kho và nợ vay ngắn hạn theo chi phí quản lí

financial_wide %>%
  arrange(CPQL) %>%
  ggplot(aes(x = CPQL)) +
  geom_line(aes(y = HTK, color = "Hàng tồn kho (HTK)")) +
  geom_line(aes(y = NVNH, color = "Nợ vay ngắn hạn (NVNH)")) +
  labs(title = "Biến động HTK và NVNH theo CPQL",x = "Chi phí quản lý doanh nghiệp (CPQL)", y = "Giá trị") +
  scale_color_manual(values = c("Hàng tồn kho (HTK)" = "blue", "Nợ vay ngắn hạn (NVNH)" = "orange")) +
  theme_minimal()

Yếu tố kĩ thuật

  • Hàm arrange(CPQL) sắp xếp dữ liệu theo biến CPQL
  • Ggplot(aes(x=CPQL)vẽ trục x biểu diễn biến CPQL; geom_line(aes(y=HTK,…)) và geom_line(aes(y=NVNH,…)) vẽ hai đường tương ứng cho biến hàng tồn kho (HTK) và nợ vay ngắn hạn (NVNH), mỗi đường gán một màu.
  • Scale_color_manual() tùy chỉnh màu sắc của hai đường để phân biệt.
  • Labs(), theme_minimal() đã giải thích ở 4.1

Kết luận

Biểu đồ cho thấy sự biến động của hàng tồn kho (HTK) và nợ vay ngắn hạn (NVNH) khi Chi phí quản lý doanh nghiệp (CPQL) thay đổi. Có thể thấy một mối quan hệ đồng biến nhẹ giữa HTK (màu xanh dương) và CPQL: khi CPQL tăng, HTK có xu hướng tăng theo, cho thấy quy mô hoạt động mở rộng kéo theo cả chi phí quản lý lẫn lượng hàng tồn kho. Tuy nhiên, mối quan hệ này bị che khuất bởi sự biến động cực kỳ mạnh và không ổn định của NVNH (màu cam). NVNH duy trì ở mức cao hơn nhiều so với HTK (thường xuyên vượt \(4\text{e}12\)) và cho thấy sự nhảy vọt thất thường tại các mức CPQL cao.

4.10 Biểu đồ cột chồng tỉ lệ chi phí bán hàng và chi phí quản lí phân loại theo vốn chủ sở hữu nhóm 3 phân vị

financial_wide %>%
  mutate(VCSH_group = ntile(VCSH, 3)) %>%
  group_by(VCSH_group) %>%
  summarise(CPBH = mean(CPBH), CPQL = mean(CPQL)) %>%
  pivot_longer(cols = c(CPBH, CPQL), names_to = "Cost_Type", values_to = "Mean_Cost") %>%
  ggplot(aes(x = factor(VCSH_group), y = Mean_Cost, fill = Cost_Type)) +
  geom_bar(stat = "identity", position = "stack") +
  labs(title = "CPBH và CPQL bình quân theo nhóm VCSH",x = "Nhóm vốn chủ sở hữu (VCSH)", y = "Chi phí trung bình") +
  scale_fill_manual(values = c("CPBH" = "steelblue", "CPQL" = "darkgreen")) +
  theme_minimal()

Yếu tố kĩ thuật

  • Summarise(…) tính trung bình của chi phí bán hàng và chi phí quản lý trong từng nhóm.
  • Pivot_longer() chuyển dữ liệu từ dạng rộng sang dạng dài, giúp ggplot2 có thể vẽ cùng lúc nhiều biến (CPBH và CPQL)
  • Ggplot(aes(…)) thiết lập đối tượng ggplot, trục x là nhóm VCSH, y là giá trị trung bình, màu theo loại chi phí; geom_bar(…) vẽ cột chồng, nghĩa là phần của chi phí bán hàng nằm dưới phần chi phí quản lý trong cùng một cột.
  • Scale_fill_manual() tùy chỉnh màu cho hai loại chi phí, giúp dễ phân biệt; theme_minimal() giao diện tối giản, trọng tâm vào dữ liệu.

Kết luận

Biểu đồ cho thấy có sự khác biệt rõ rệt về mức độ chi phí hoạt động (CPBH và CPQL) giữa các nhóm Vốn chủ sở hữu (VCSH). Cụ thể, các công ty thuộc nhóm 2 (nhóm VCSH có mức trung bình cao nhất) chịu gánh nặng chi phí lớn nhất (khoảng \(5\text{e}11\)), theo sau là nhóm 1 (khoảng \(4.6\text{e}11\)), trong khi nhóm 3 (có lẽ là nhóm VCSH thấp nhất) có chi phí hoạt động thấp nhất (khoảng \(1.3\text{e}11\)). Về cấu trúc, chi phí quản lý doanh nghiệp (CPQL) (màu xanh đậm) luôn chiếm tỷ trọng chủ đạo trong tổng chi phí của cả ba nhóm. Đặc biệt, nhóm 2 có cả CPQL và CPBH đều cao nhất, cho thấy việc mở rộng quy mô vốn chủ sở hữu có xu hướng đi kèm với gia tăng đáng kể các chi phí quản lý và bán hàng để phục vụ cho quy mô hoạt động lớn hơn. Ngược lại, chi phí hoạt động thấp của nhóm 3 có thể phản ánh quy mô nhỏ hơn hoặc một mô hình kinh doanh tinh gọn hơn.

4.11 Biểu đồ phân tán (scatter) thể hiện phải thu ngắn hạn theo tổng tài sản, kích thước điểm theo lợi nhuận gộp

financial_wide %>%
  ggplot(aes(x = TTS, y = PTNH, size = LNG)) +
  geom_point(alpha = 0.6, color = "steelblue") +
  labs(title = "Phân tán PTNH theo TTS với kích thước điểm  LNG",x = "Tổng tài sản (TTS)", y = "Phải thu ngắn hạn (PTNH)") +
  theme_minimal()

Yếu tố kĩ thuật

  • Ggplot(aes(x = TTS, y = PTNH, size = LNG)) đặt trục x là tổng tài sản (TTS), trục y là phải thu ngắn hạn (PTNH), kích thước điểm đại diện cho lợi nhuận gộp (LNG); geom_point(alpha = 0.6, color = “steelblue”) vẽ các điểm dữ liệu với màu xanh thép với độ trong suốt 0.6.
  • Labs(…), theme_minimal(): đã giải thích ở 4.1

Kết luận

Biểu đồ cho thấy một mối quan hệ đồng biến mạnh mẽ giữa tổng tài sản (TTS) và phải thu ngắn hạn (PTNH): các công ty có quy mô tài sản lớn hơn (TTS cao hơn) có xu hướng duy trì mức phải thu ngắn hạn cao hơn, phản ánh sự mở rộng của hoạt động bán hàng chịu. Quan trọng hơn, biểu đồ minh họa rằng LNG (kích thước điểm) cũng có xu hướng tăng theo quy mô TTS và PTNH. Các điểm dữ liệu lớn nhất (LNG \(\approx 9\text{e}11\)) tập trung chủ yếu ở góc trên bên phải của biểu đồ, nơi TTS và PTNH đều ở mức cao nhất (TTS \(\approx 9\text{e}12\) đến \(10\text{e}12\), PTNH \(\approx 2.4\text{e}12\) đến \(2.5\text{e}12\)).

4.12 Biểu đồ hình tròn (pie chart) tỷ trọng chi phí bán hàng và quản lí so với tổng chi phí

financial_wide %>%
  summarise(CPBH = sum(CPBH, na.rm=TRUE), CPQL = sum(CPQL, na.rm=TRUE)) %>%
  pivot_longer(cols = everything(), names_to = "Cost_Type", values_to = "Amount") %>%
  mutate(Percent = Amount / sum(Amount) * 100) %>%
  ggplot(aes(x = "", y = Percent, fill = Cost_Type)) +
  geom_col(color = "white") +
  coord_polar(theta = "y") +
  geom_text(aes(label = paste0(round(Percent,1), "%")), position = position_stack(vjust = 0.5)) +
  labs(title = "Tỷ trọng chi phí bán hàng và chi phí quản lý") +
  theme_void() +
  scale_fill_manual(values = c("CPBH" = "tomato", "CPQL" = "skyblue"))

Yếu tố kĩ thuật

  • Summarise(CPBH = sum…) tính tổng chi phí bán hàng và chi phí quản lý, loại bỏ giá trị thiếu.
  • Pivot_longer(cols = ..) chuyển số liệu từ dạng rộng sang dạng dài; mutate(Percent = ..) tính tỷ lệ phần trăm của từng loại chi phí so với tổng.
  • Ggplot(aes()) chuẩn bị ggplot với trục x là một giá trị rỗng để vẽ biểu đồ tròn, trục y là tỷ lệ phần trăm, màu theo loại chi phí; geom_col(color = “white”) vẽ cột cho từng loại chi phí với màu viền trắng để phân biệt; coord_polar(theta = “y”) chuyển hệ tọa độ sang cực, tạo thành biểu đồ hình tròn ; geom_text(aes(….)) thêm nhãn phần trăm ở giữa từng phần.
  • Labs() đã giải thích ở 4.1; theme_void() giao diện không trục, không lưới, phù hợp với biểu đồ tròn; scale_fill_manual(values = c(“CPBH” = “tomato”, “CPQL” = “skyblue”)) tùy chỉnh màu sắc cho từng loại chi phí.

Kết luận

Biểu đồ cho thấy chi phí Bán hàng (CPBH) chiếm tỷ trọng lớn hơn trong tổng chi phí hoạt động, với mức \(54.3\%\) (màu cam), trong khi chi phí quản lý doanh nghiệp (CPQL) chiếm \(45.7\%\) (màu xanh dương). Sự chênh lệch này (\(54.3\% / 45.7\%\)) ngụ ý rằng công ty đang tập trung nguồn lực tài chính nhiều hơn vào các hoạt động liên quan trực tiếp đến việc tạo ra doanh thu, bao gồm tiếp thị, quảng cáo, và phân phối sản phẩm. Tỷ trọng CPBH cao hơn CPQL phản ánh một chiến lược tăng trưởng lấy bán hàng làm trọng tâm (Sales-driven strategy) hoặc đặc thù của một ngành nghề đòi hỏi chi phí marketing và kênh phân phối lớn.

4.13 Biểu đồ đường chồng biến động doanh thu, lợi nhận gộp, doàng tiền hoạt động theo năm

financial_wide %>%
  select(Nam, DT, LNG, CFO) %>%
  pivot_longer(cols = c(DT, LNG, CFO), names_to = "Metric", values_to = "Value") %>%
  ggplot(aes(x = Nam, y = Value, color = Metric)) +
  geom_line() +
  geom_point() +
  labs(title = "Biến động đồng thời DT, LNG và CFO") +
  theme_minimal()

Yếu tố kĩ thuật

  • Select() chọn ra các chỉ tiêu trên
  • Sử dụng ggplot(aes(…)) thiết lập trục x, y và biến màu để hiển thị nhiều biến trong cùng một biểu đồ; geom_line() vẽ các đường theo từng biến của dữ liệu dài thường dùng với dữ liệu sau khi chuyển đổi từ dạng rộng sang dạng dài bằng pivot_longer(); geom_point() thêm các điểm dữ liệu để làm rõ vị trí của từng điểm trên từng đường.
  • Scale_color_brewer() chọn bảng màu phù hợp, giúp rõ ràng; theme_minimal(), lab() đã giải thích ở 4.1

Kết luận

Biểu đồ cho thấy một sự phân cực rõ rệt về quy mô giữa doanh thu (DT) và hai chỉ tiêu còn lại là lợi nhuận (LNG) và dòng tiền hoạt động (CFO). Cụ thể, doanh thu (màu xanh lá) thể hiện một quỹ đạo tăng trưởng mạnh mẽ và nhất quán, đặc biệt là sau năm 2019, đạt gần 2.0e13 vào năm cuối, xác nhận sự mở rộng quy mô hoạt động kinh doanh thành công. Ngược lại, lợi nhuận (LNG - màu xanh dương) và dòng tiền hoạt động (CFO - màu đỏ) duy trì ở mức thấp và gần như đi ngang (chỉ dao động quanh 1.0e12 hoặc thấp hơn), với CFO thường xuyên thấp hơn LNG trong nhiều năm. Sự chênh lệch lớn này ngụ ý rằng, mặc dù công ty có khả năng tạo ra doanh thu lớn và lợi nhuận kế toán dương, nhưng hiệu quả biến lợi nhuận thành tiền mặt thực tế lại rất thấp, có thể do gánh nặng lớn về vốn lưu động (như khoản phải thu hoặc tồn kho tăng vọt) hoặc chi phí phi tiền mặt cao.

4.14 Biểu đồ bong bóng (bubble chart) VCSH, TTS, CFO

financial_wide %>%
  ggplot(aes(x = VCSH, y = TTS, size = CFO, color = ROE)) +
  geom_point(alpha = 0.6) +
  scale_color_gradient(low = "yellow", high = "red") +
  labs(title = "Biểu đồ phân tích VCSH, TTS, CFO theo ROE")+
  theme_minimal()

Yếu tố kĩ thuật

  • Ggplot(aes(..) đặt vị trí điểm theo vốn chủ sở hữu (VCSH) trên trục x và tổng tài sản (TTS) trên trục y. Kích thước điểm biểu diễn dòng tiền hoạt động (CFO), màu sắc theo tỉ suất sinh lời trên vốn chủ sở hữu (ROE); geom_point(alpha = 0.6): vẽ các bong bóng điểm dữ liệu với độ trong suốt alpha - Scale_color_gradient(low = “yellow”, high = “red”) tạo dải màu gradient từ vàng tới đỏ tương ứng giá trị thấp đến cao của ROE.
  • Labs(), theme_minimal() đã giải thích ở 4.1

Kết luận

Biểu đồ cho thấy một sự phân hóa rõ rệt về hiệu quả sinh lời dựa trên quy mô và cấu trúc tài chính. Vốn chủ sở hữu (VCSH) và tổng tài sản (TTS) có mối quan hệ đồng biến tích cực, nhưng hiệu quả sinh lời cao nhất lại tập trung ở các công ty có quy mô nhỏ hơn. Cụ thể, ROE cao nhất (màu đỏ, \(\approx 0.6\)) đạt được ở nhóm công ty có TTS và VCSH thấp nhất (TTS \(\approx 5.8\text{e}12\), VCSH \(\approx 1.4\text{e}12\)) và quan trọng là có dòng tiền hoạt động (CFO) âm (kích thước điểm nhỏ). Ngược lại, các công ty có quy mô TTS và VCSH lớn nhất (góc trên bên phải, TTS \(\approx 1.05\text{e}13\)) lại có CFO dương cao (kích thước điểm lớn nhất) nhưng ROE thấp hơn đáng kể (màu vàng nhạt), cho thấy mặc dù tạo ra dòng tiền mạnh mẽ, nhưng hiệu suất sinh lời trên vốn chủ sở hữu lại bị pha loãng khi quy mô tài sản tăng lên.

4.15 Biểu đồ historial phân bố tỷ lệ chi phí bán hàng trên doanh thu

financial_wide %>%
  mutate(CPBH_DT_ratio = CPBH / DT) %>%
  ggplot(aes(x = CPBH_DT_ratio)) +
  geom_histogram(binwidth = 0.01, fill = "purple", color = "white") +
  labs(title = "Phân bố tỷ lệ chi phí bán hàng trên doanh thu") +
  theme_minimal()

Yếu tố kĩ thuật

  • Mutate(CPBH_DT_ratio = CPBH / DT) tạo biến mới là tỷ lệ chi phí bán hàng trên doanh thu.
  • Ggplot(aes(x = CPBH_DT_ratio)) thiết lập trục x cho biểu đồ là biến tỷ lệ vừa tạo; geom_histogram(…) vẽ biểu đồ histogram với bước chia bin nhỏ 0.01, tô màu tím cho các cột, viền trắng để phân biệt.
  • Labs(), theme_minimal() đã giải thích ở 4.1

Kết luận

Biểu đồ cho thấy sự phân bố của tỷ lệ chi phí bán hàng trên doanh thu (CPBH/DT) là cực kỳ tập trung và lệch về phía dương. Phần lớn các quan sát (tần suất là 8) nằm trong khoảng giá trị dương (khoảng \(0.02\) đến \(0.03\)), ngụ ý rằng đa số các công ty hoặc các kỳ kinh doanh đều có CPBH chiếm khoảng 2% đến 3% doanh thu. Chỉ có một số lượng rất nhỏ các quan sát nằm ở các khoảng ngoại lai: một quan sát nằm ở giá trị âm nhẹ (khoảng \(-0.02\)) và một quan sát nằm ở giá trị dương cao hơn (khoảng \(0.03\) đến \(0.04\)). Tuy nhiên, sự xuất hiện của tỷ lệ âm (khoảng \(-0.02\)) là một điểm bất thường, cần được kiểm tra về nguyên tắc ghi nhận kế toán (ví dụ: hoàn nhập chi phí).

4.16 Biểu đồ phân tán với đường hồi quy (scatter plot + regression line) giữa ROE và ROA theo nhóm năm

financial_wide %>%
  filter(!is.na(ROE), !is.na(ROA)) %>%
  ggplot(aes(x = ROE, y = ROA)) +
  geom_point(alpha = 0.6, aes(color = factor(Nam))) +
  geom_smooth(method = "lm", se = TRUE, color = "black", linetype = "dashed") +
  facet_wrap(~ Nam) +
  labs(title = "Quan hệ ROE và ROA theo năm với đường hồi quy") +
  theme_minimal() + theme(legend.position = "none")

Yếu tố kĩ thuật

  • Filter(!is.na(ROE), !is.na(ROA)) lọc bỏ các trường hợp dữ liệu thiếu để tránh lỗi hoặc biểu diễn sai.
  • Ggplot(aes(..))thiết lập trục x: ROE, trục y: ROA; geom_point(…): Vẽ điểm dữ liệu với màu theo từng năm với độ trong suốt alpha = 0.6; geom_smooth(method = ..) thêm đường hồi quy tuyến tính kèm khoảng tin cậy (se = TRUE), màu đen, kiểu nét đứt; facet_wrap(~ Nam) tách biểu đồ nhỏ theo từng năm để so sánh động quan hệ ROE và ROA theo từng năm.
  • Labs(), theme_minimal() đã giải thích ở 4.1; theme(legend.position = “none”) ẩn chú giải màu vì đã thể hiện rõ trong từng biểu đồ nhỏ.

Kết luận

Chuỗi biểu đồ này chứng minh một cách nhất quán rằng công ty đã sử dụng đòn bẩy tài chính một cách tích cực và hiệu quả trong suốt giai đoạn, thể hiện qua việc ROE luôn cao hơn ROA với một khoảng cách lớn (ví dụ: ROA chỉ dao động từ \(\approx 0.08\) đến \(0.14\), trong khi ROE kéo dài từ \(\approx 0.4\) đến trên \(0.6\)). Sự chênh lệch này khẳng định khả năng khuếch đại lợi nhuận cho cổ đông nhờ việc sử dụng nợ hiệu quả. Mặc dù mối quan hệ đồng biến giữa hai chỉ số này được giữ vững, nhưng cả ROE và ROA đều cho thấy xu hướng suy giảm đồng thời trong giai đoạn 2023-2024, đặc biệt khi ROA giảm xuống mức thấp \(\approx 0.08\).

4.17 Biểu đồ violin với boxplot lồng phân phối chi phí bán hàng theo nhóm phân vị vốn chủ sở hữu

financial_wide %>%
  mutate(VCSH_group = ntile(VCSH, 4)) %>%
  ggplot(aes(x = factor(VCSH_group), y = CPBH, fill = factor(VCSH_group))) +
  geom_violin(alpha = 0.6) +
  geom_boxplot(width = 0.1, outlier.colour = "red", outlier.shape = 16) +
  labs(title = "Phân phối CPBH theo nhóm VCSH") +
  theme_minimal()

Yếu tố kĩ thuật

  • Hàm mutate(…) chia vốn chủ sở hữu thành 4 nhóm phân vị để phân loại dữ liệu.
  • Ggplot(aes()) thiết lập trục x là nhóm vốn, trục y là chi phí bán hàng, màu sắc theo nhóm; geom_violin(alpha = 0.6) vẽ biểu đồ violin, thể hiện phân phối mật độ dữ liệu từng nhóm với độ trong suốt 0.6; geom_boxplot(…) thêm boxplot nhỏ nằm trong violin plot thể hiện các thống kê tóm tắt và ngoại lệ (đỏ).
  • Labs(),theme_minimal() đã giải thích ở 4.1

Kết luận

Biểu đồ phân phối chi phí bán hàng (CPBH) theo nhóm vốn chủ sở hữu (VCSH) khắc họa một sự phân hóa chiến lược rõ rệt và một tình trạng bất thường nghiêm trọng trong cấu trúc chi phí hoạt động. Trong khi nhóm 1, 2 và 3 cho thấy sự ổn định cao và phân phối tập trung ở mức CPBH dương nhất quán (khoảng \(2\text{e}11\)), phản ánh khả năng kiểm soát và dự báo chi phí bán hàng hiệu quả, thì Nhóm 4 lại là một điểm ngoại lai cực đoan về mặt thống kê và tài chính. Nhóm này thể hiện biến động cực lớn với phạm vi phân tán rộng (kéo dài từ giá trị dương xuống âm sâu đến \(-4\text{e}11\)), trong khi giá trị trung vị lại gần bằng 0. Sự xuất hiện của các giá trị CPBH âm đáng kể — mặc dù phi logic đối với một chi phí hoạt động — ngụ ý rằng các công ty thuộc nhóm VCSH cao nhất này đã có những giao dịch hoàn nhập chi phí hoặc ghi nhận thu nhập trực tiếp từ hoạt động bán hàng vượt quá tổng chi phí bán hàng thực tế phát sinh trong kỳ.

4.18 Biểu đồ cột chồng với tỷ lệ CPBH, CPQL trên TTS, phân nhóm theo nhóm hàng tồn kho

financial_wide %>%
  mutate(CPBH_TTS = CPBH / TTS,CPQL_TTS = CPQL / TTS) %>%
  select(Nam, CPBH_TTS, CPQL_TTS) %>%
  pivot_longer(cols = c(CPBH_TTS, CPQL_TTS), names_to = "Loại_chi_phi", values_to = "Ty_le") %>%
  ggplot(aes(x = factor(Nam), y = Ty_le, fill = Loại_chi_phi)) +
  geom_col(position = "stack", width = 0.6) +
  labs(title = "Tỷ lệ CPBH và CPQL trên tổng tài sản (2015-2024)",x = "Năm", y = "Tỷ lệ trên TTS",fill = "Loại chi phí") +
  scale_fill_manual(values = c("CPBH_TTS" = "steelblue", "CPQL_TTS" = "orange"),
                    labels = c("Chi phí bán hàng", "Chi phí quản lý")) +
  scale_y_continuous(labels = scales::percent, expand = expansion(mult = c(0, 0.1))) +
  scale_x_discrete(expand = expansion(add = c(0.5, 0.5))) +
  theme_minimal(base_size = 14) +
  theme(axis.text.x = element_text(angle = 45, hjust = 1),plot.title = element_text(face = "bold", hjust = 0.5),legend.title = element_text(face = "bold"),plot.margin = margin(t = 20, r = 15, b = 15, l = 15))

Yếu tố kết luận

  • Hàm mutate(…) tính tỷ lệ CPBH và CPQL trên tổng tài sản; pivot_longer() chuyển dữ liệu từ dạng rộng sang dài để ggplot dễ vẽ stacked bar.
  • Ggplot(aes(…)) + geom_col(position=“stack”) vẽ cột chồng thể hiện tỷ lệ CPBH và CPQL theo từng năm.
  • Labs() và scale_fill_manual(): Đổi tên trục, tiêu đề, và nhãn legend sang tiếng việt; scale_y_continuous(labels = scales::percent) hiển thị trục Y dưới dạng phần trăm; theme_minimal() + theme(…) tùy chỉnh hiển thị, font chữ, góc trục X, và margin cho đẹp.

Kết luận

Biểu đồ trên thể hiện tỷ lệ chi phí bán hàng và chi phí quản lý trên tổng tài sản từ năm 2015 đến năm 2024. Từ năm 2015 đến 2019, cả hai loại chi phí đều duy trì ở mức ổn định, với chi phí bán hàng chiếm phần lớn hơn so với chi phí quản lý. Tuy nhiên, sau năm 2019, tỷ lệ tổng chi phí có xu hướng giảm dần, đặc biệt rõ từ năm 2021 đến 2023, cho thấy sự cải thiện trong quản lý tài chính và kiểm soát chi phí của doanh nghiệp. Đến năm 2024, biểu đồ cho thấy sự bất thường khi cả hai loại chi phí đều âm, điều này có thể phản ánh sự tái cấu trúc tài chính hoặc điều chỉnh báo cáo tài chính lớn liên quan đến tổng tài sản. Tổng thể, biểu đồ phản ánh quá trình tối ưu hóa chi phí và sự thay đổi đáng kể trong chiến lược tài chính của doanh nghiệp qua các năm.

4.19 Biểu đồ boxplot phân phối LNG theo nhóm phân vị hàng tồn kho (HTK)

financial_wide %>%
  mutate(HTK_group = ntile(HTK, 4)) %>%
  ggplot(aes(x = factor(HTK_group), y = LNG, fill = factor(HTK_group))) +
  geom_boxplot(alpha = 0.6) +
  geom_jitter(width = 0.25, alpha = 0.3, color = "black") +
  labs(title = "Phân phối LNG theo hàng tồn kho",x = "Nhóm phân vị hàng tồn kho (HTK)",y = "Lợi Nhuận Gộp (LNG)") +
  scale_fill_brewer(palette = "Set2") +
  theme_minimal() +
  theme(axis.text.x = element_text(size = 13),plot.title = element_text(size = 16, face = "bold", hjust = 0.5))

Yếu tố kĩ thuật

  • Mutate(…) chia hàng tồn kho (HTK) thành 4 nhóm phân vị để phân loại dữ liệu.
  • Ggplot(aes(…)) thiết lập trục x là nhóm phân vị HTK, trục y là lợi nhuận gộp (LNG), màu sắc theo nhóm; geom_boxplot(alpha = 0.6) vẽ hộp boxplot thể hiện phân bố, trung vị, và ngoại lệ trong từng nhóm, với độ trong suốt 0.6; geom_jitter(width = 0.25, alpha = 0.3, color = “black”) thêm các điểm rải rác để thể hiện từng quan sát thực tế, giảm hiện tượng chồng chéo, giúp quan sát dữ liệu rõ hơn.
  • Labs(), theme() đã giải thích ở 4.1; scale_fill_brewer(palette = “Set2”) sử dụng bảng màu Set2 hài hòa cho các nhóm; theme_minimal() chọn giao diện tối giản.

Kết luận

Biểu đồ cho thấy có mối quan hệ đồng biến rõ rệt giữa nhóm phân vị hàng tồn kho (HTK) và lợi nhuận gộp (LNG): các nhóm HTK có phân vị cao hơn thường đạt mức LNG cao hơn. Cụ thể, nhóm 3 và 4 thể hiện hiệu suất LNG vượt trội với giá trị trung vị (đường kẻ ngang đậm) nằm ở mức cao nhất (khoảng \(8.5\text{e}11\) đến \(9\text{e}11\)), cho thấy các công ty có lượng hàng tồn kho lớn hơn lại có khả năng tạo ra lợi nhuận gộp cao hơn. Ngược lại, Nhóm 1 và 2 có mức LNG trung vị thấp nhất (khoảng \(6.6\text{e}11\) đến \(6.7\text{e}11\)).

4.20 Biến động của tỷ lệ biến đổi tài chính qua từng năm

financial_wide %>%
  select(Nam, DT_pct, LNG_pct, CPBH_pct, CPQL_pct, CFO_pct, HTK_pct, NVNH_pct, PTNH_pct, TTS_pct, VCSH_pct) %>%
  pivot_longer(cols = -Nam, names_to = "TyLeBienDoi", values_to = "GiaTri") %>%
  ggplot(aes(x = factor(Nam), y = GiaTri, color = TyLeBienDoi, group = TyLeBienDoi)) +
  geom_line(size = 1) +
  geom_point(size = 2) +
  scale_color_brewer(palette = "Paired") +
  labs(title = "Biến động tỷ lệ biến đổi tài chính theo năm",x = "Năm",y = "Tỷ lệ biến đổi (%)",color = "Biến tỷ lệ") +
  theme_minimal() + theme(axis.text.x = element_text(angle = 45, hjust = 1))

Yếu tố kĩ thuật

  • Hàm select(…) lấy các biến năm và các tỷ lệ biến đổi tài chính theo năm; pivot_longer(cols =…) chuyển dữ liệu từ dạng rộng sang dài.
  • Ggplot(aes(x = ..)) vẽ biểu đồ với trục x là năm, trục y là giá trị tỷ lệ biến đổi, màu và nhóm theo các biến tỷ lệ khác nhau; geom_line(size=1) vẽ đường nối các điểm dữ liệu cho mỗi biến tỷ lệ; geom_point(size=2) thêm điểm dữ liệu trên các đường; scale_color_brewer(…) sử dụng bảng màu Paired cho dễ phân biệt các đường.
  • Labs(), theme_minimal() đã giải thích ở 4.1; theme(axis.text.x =..) nghiêng nhãn trục x 45 độ để tránh chồng chữ, căn chỉnh cho dễ đọc.

Kết luận

Biểu đồ biến động tỷ lệ biến đổi tài chính khắc họa một công ty có hai mặt tài chính hoàn toàn đối lập: một mặt là sự ổn định đáng kinh ngạc của hầu hết các chỉ tiêu cơ bản (Doanh thu, Lợi nhuận gộp, Vốn chủ sở hữu, v.v.), và mặt khác là sự biến động cực đoan của dòng tiền hoạt động (CFO). Tỷ lệ biến đổi của các chỉ tiêu như DT_pct hay VCSH_pct gần như bằng 0% trong suốt giai đoạn, vẽ nên bức tranh về một doanh nghiệp đang thực hiện chiến lược tăng trưởng quy mô một cách có kiểm soát và tuyến tính qua từng năm, xây dựng nền tảng tài chính vững chắc. Tuy nhiên, sự tăng đột biến lên hơn 4000% của CFO_pct vào năm 2018—một điểm ngoại lai không thể bỏ qua—phá vỡ hoàn toàn sự yên tĩnh này. Sự biến động khổng lồ này chỉ ra rằng, ngay cả khi quy mô doanh thu và lợi nhuận tăng trưởng đều đặn, công ty đã phải trải qua một sự kiện tài chính phi thường liên quan đến việc thu hồi hoặc chi tiêu tiền mặt trong năm 2018.