knitr::opts_chunk$set(dev.args = list(png = list(type = "cairo")), fig.crop = FALSE)
Sys.setlocale("LC_ALL", "Vietnamese_Vietnam.1258")
## Warning in Sys.setlocale("LC_ALL", "Vietnamese_Vietnam.1258"): using locale
## code page other than 65001 ("UTF-8") may cause problems
## [1] "LC_COLLATE=Vietnamese_Vietnam.1258;LC_CTYPE=Vietnamese_Vietnam.1258;LC_MONETARY=Vietnamese_Vietnam.1258;LC_NUMERIC=C;LC_TIME=Vietnamese_Vietnam.1258"
options(encoding = "UTF-8")
Sys.setlocale("LC_CTYPE", "en_US.UTF-8")
## [1] "en_US.UTF-8"

1 CHƯƠNG 1: BỘ DỮ LIỆU MEDICAL

1.1 Giới thiệu bộ dữ liệu

Bộ dữ liệu Sinh viên Y khoa là một bộ dữ liệu mô phỏng gồm 100.000 hàng và 11 cột. Bộ dữ liệu được thiết kế để mô phỏng dữ liệu thực tế

thường gặp trong giáo dục và nghiên cứu y khoa, thu thập thông tin liên quan đến các đặc điểm sinh học và sức khỏe cơ bản của bệnh nhân.

Mục tiêu của bộ dữ liệu là mô tả mối quan hệ giữa các yếu tố nhân khẩu học (tuổi, giới tính, chiều cao, cân nặng, nhóm máu) và các chỉ

số sức khỏe (BMI, nhiệt độ cơ thể, nhịp tim, huyết áp, cholesterol).

1.1.1 Đọc dữ liệu

library(readxl)
data <- read_excel("D:/ahihi.xlsx")
head(data)
## # A tibble: 6 × 11
##   Student.ID   Age Gender Height Weight Blood.Type   BMI Temperature Heart.Rate
##        <dbl> <dbl> <chr>   <dbl>  <dbl> <chr>      <dbl>       <dbl>      <dbl>
## 1          1    18 Female   162.   72.4 O           27.6        99.1         95
## 2          2    30 Male     152.   47.6 B           21.6        98.7         93
## 3          3    32 Female   183.   55.7 A           16.7        98.3         76
## 4          4    30 Male     182.   63.3 B           19.1        98.8         99
## 5          5    23 Female   164.   46.2 O           18.8        98.5         95
## 6          6    32 Male     151.   68.6 B           29.9        99.7         70
## # ℹ 2 more variables: Blood.Pressure <dbl>, Cholesterol <dbl>

Giải thích: Hàm head() giúp xem nhanh 6 dòng đầu tiên của bảng dữ liệu data.

1.1.2 Xem 6 dòng cuối

tail(data)
## # A tibble: 6 × 11
##   Student.ID   Age Gender Height Weight Blood.Type   BMI Temperature Heart.Rate
##        <dbl> <dbl> <chr>   <dbl>  <dbl> <chr>      <dbl>       <dbl>      <dbl>
## 1      99995    22 Male     161.   70.3 O           27.6        99.0         86
## 2      99996    24 Male     177.   95.8 B           30.7        98.5         65
## 3      99997    29 Female   164.   45.2 O           16.8        97.9         62
## 4      99998    34 Male     173.   99.6 B           33.2        98.8         60
## 5      99999    30 Female   156.   50.1 A           20.5        99.0         86
## 6     100000    20 Female   154.   99.9 O           42.2        98.6         95
## # ℹ 2 more variables: Blood.Pressure <dbl>, Cholesterol <dbl>

Giải thích: Hàm tail() giúp xem nhanh 6 dòng cuối cùng của bảng dữ liệu data.

1.1.3 Số biến, số quan sát:

dim(data)
## [1] 100000     11

Giải thích: Hàm dim() (dimension) trả về kích thước của đối tượng dữ liệu Kết quả: Bộ dữ liệu gồm 100000 dòng và 11 biến.

1.1.4 Tên các biến:

names(data)
##  [1] "Student.ID"     "Age"            "Gender"         "Height"        
##  [5] "Weight"         "Blood.Type"     "BMI"            "Temperature"   
##  [9] "Heart.Rate"     "Blood.Pressure" "Cholesterol"

Giải thích: Hàm names() dùng để: Liệt kê hoặc xem tên các biến (tên cột) trong một data frame

1.1.5 Kiểm tra số quan sát trùng lặp:

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

Giải thích: duplicated(data): Kiểm tra dòng nào bị trùng trong toàn bộ bảng dữ liệu → trả về một vector logic (TRUE hoặc FALSE) cho từng dòng.

sum(…): Cộng tất cả giá trị TRUE (vì TRUE = 1, FALSE = 0) → cho ra số lượng dòng trùng.

Kết quả 0 cho thấy bộ dữ liệu không có bất kỳ quan sát nào bị trùng lặp.

Nhận xét: Không có quan sát nào bị trùng lặp trong bộ dữ liệu

1.1.6 Kiểm tra giá trị thiếu

sum(is.na(data))
## [1] 0

Giải thích: Hàm is.na() dùng để kiểm tra giá trị bị thiếu (NA) trong dữ liệu.

sum() sẽ đếm tổng số giá trị bị thiếu (NA) trong toàn bộ bảng dữ liệu data.

Kết quả 0 cho thấy bộ dữ liệu không có bất kỳ giá trị nào bị thiếu.

Nhận xét: Không có giá trị nào bị thiếu trong bộ dữ liệu

1.1.7 Giải thích ý nghĩa các biến trong bộ dữ liệu:

variable_meaning <- data.frame(
  Variable = c("Student.ID", "Age", "Gender", "Height", "Weight", "Blood.Type", "BMI", "Temperature", "Heart.Rate", "Blood.Pressure", "Cholesterol"),
  Meaning = c(
    "Mã số bẹnh nhân",                   
    "Tuổi (năm)",                                  
    "Giới tính (Nam/Nu)",                         
    "Chiều cao (cm)",                             
    "Cân nặng (kg)",                              
    "Nhóm máu (A, B, AB, O)",                    
    "Chỉ số BMI (khối lượng cơ thể)",            
    "Nhiệt độ cơ thể",                      
    "Nhịp tim (bpm)",                             
    "Huyết áp (mmHg)",                            
    "Cholesterol (mg/dL)"
  ),
  stringsAsFactors = FALSE
)
library(knitr)
kable(variable_meaning, booktabs = TRUE)
Variable Meaning
Student.ID Mã số bẹnh nhân
Age Tuổi (năm)
Gender Giới tính (Nam/Nu)
Height Chiều cao (cm)
Weight Cân nặng (kg)
Blood.Type Nhóm máu (A, B, AB, O)
BMI Chỉ số BMI (khối lượng cơ thể)
Temperature Nhiệt độ cơ thể
Heart.Rate Nhịp tim (bpm)
Blood.Pressure Huyết áp (mmHg)
Cholesterol Cholesterol (mg/dL)

Giải thích: Hàm Variable: chứa tên các biến trong bộ dữ liệu.

Meaning: mô tả ý nghĩa của từng biến.

stringsAsFactors = FALSE Thông số này giúp giữ nguyên kiểu dữ liệu chữ (character). Nếu không viết stringsAsFactors = FALSE, R (trong phiên bản cũ) sẽ tự động chuyển cột ký tự sang factor (kiểu phân loại), điều này không cần thiết trong bảng mô tả.

knitr là một thư viện dùng trong R Markdown để hiển thị kết quả đẹp, đặc biệt là bảng (table).

Hàm kable() thuộc gói knitr, dùng để hiển thị bảng dữ liệu (data frame) dưới dạng bảng trình bày đẹp trong R Markdown.

booktabs = TRUE giúp bảng xuất ra đẹp hơn, có đường kẻ mảnh và gọn, phù hợp với tiêu chuẩn xuất bản (LaTeX style). Nhận xét: Đoạn code này tạo và trình bày một bảng mô tả biến trong dữ liệu, giúp người đọc nắm rõ ý nghĩa của từng cột.

1.1.8 Phân loại biến định lượng:

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

Giải thích: - is.numeric: Đây là hàm kiểm tra kiểu dữ liệu trong R xem có phải dạng số hay không.

  • Hàm sapply() áp dụng hàm is.numeric lên từng cột của data. Kết quả trả về là một vector logic (TRUE/FALSE) cho biết cột nào là số.

  • sum(…): Trong R, khi cộng các giá trị TRUE/FALSE, R tự hiểu TRUE = 1 và FALSE = 0. Sẽ đếm số lượng cột TRUE, tức là số biến có kiểu numeric.

names(data)[sapply(data, is.numeric)]
## [1] "Student.ID"     "Age"            "Height"         "Weight"        
## [5] "BMI"            "Temperature"    "Heart.Rate"     "Blood.Pressure"
## [9] "Cholesterol"

Giải thích: names(data): Lấy tên các cột trong data. Nhận xét: Bộ dữ liệu gồm 9 biến định lượng: “Student.ID”, “Age”, “Height”, “Weight”, “BMI”, “Temperature”, “Heart.Rate”, “Blood.Pressure”, “Cholesterol”

1.1.9 Phân loại biến định tính:

sum(sapply(data, function(x) is.factor(x) | is.character(x)))
## [1] 2

Giải thích:

  • sapply() sẽ lặp qua từng cột của data

  • Hàm function(x) is.factor(x) | is.character(x) sẽ: Kiểm tra xem cột đó có phải là factor hay character. Dấu | nghĩa là “hoặc”. Nếu cột là factor hoặc character → trả về TRUE (tức 1). Nếu không → trả về FALSE( tức 0).

names(data)[sapply(data, function(x) is.factor(x) | is.character(x))]
## [1] "Gender"     "Blood.Type"

Giải thích: names(data): Lấy tên các cột trong data. Nhận xét: Bộ dữ liệu gồm 2 biến định tính: “Gender” “Blood.Type”

1.1.10 Xem cấu trúc dữ liệu

nrow(data)
## [1] 100000

Giải thích: Hàm nrow(data) sẽ trả về một số nguyên (integer) chính là số dòng. Kết quả cho thấy bộ dữ liệu có 100000 dòng.

ncol(data)
## [1] 11

Giải thích: Hàm ncol(data) sẽ trả về một số nguyên (integer) chính là số lượng cột. Kết quả cho thấy bộ dữ liệu có 11 cột.

1.2 Xử lý dữ liệu thô, mã hóa dữ liệu

1.2.1 Xem kiểu dữ liệu của từng biến

sapply(data, class)
##     Student.ID            Age         Gender         Height         Weight 
##      "numeric"      "numeric"    "character"      "numeric"      "numeric" 
##     Blood.Type            BMI    Temperature     Heart.Rate Blood.Pressure 
##    "character"      "numeric"      "numeric"      "numeric"      "numeric" 
##    Cholesterol 
##      "numeric"

Giải thích:

sapply() = “simplified apply”: áp dụng một hàm cho từng cột trong data

class() = hàm kiểm tra kiểu dữ liệu (class) của đối tượng.

Nhận xét:

Kết quả cho thấy có 9 biến kiểu numeric (Age, Height, Weight, BMI, Temperature, Heart.Rate, Blood.Pressure, Cholesterol, Student.ID). Có 2 biến kiểu character (Gender, Blood.Type).

1.2.2 Kiểm tra giá trị thiếu trong từng cột

colSums(is.na(data))
##     Student.ID            Age         Gender         Height         Weight 
##              0              0              0              0              0 
##     Blood.Type            BMI    Temperature     Heart.Rate Blood.Pressure 
##              0              0              0              0              0 
##    Cholesterol 
##              0

Giải thích: Hàm is.na() kiểm tra giá trị bị thiếu (NA) trong từng ô của bảng dữ liệu. Kết quả là một bảng logic (TRUE/FALSE) có cùng kích thước với data, trong đó: TRUE → ô đó bị thiếu dữ liệu (NA), FALSE → ô đó có dữ liệu hợp lệ colSums(…) tính tổng số giá trị TRUE, tức là NA trong từng cột Nhận xét: Kết quả cho thấy các cột đều không có giá trị bị thiếu.

1.2.3 Giả sử cột Weight có giá trị NA.

data$Height[is.na(data$Height)] <- mean(data$Height, na.rm = TRUE)

Giải thích:

  • is.na(data$Weight): Tạo một vector logic cho biết vị trí nào trong cột Weight bị thiếu (NA). Mỗi phần tử: TRUE → là giá trị bị thiếu, FALSE → có giá trị hợp lệ.

  • mean(data$Weight, na.rm = TRUE): Tính giá trị trung bình của cột Weight, bỏ qua các NA (na.rm = TRUE có nghĩa là remove NAs)

  • data\(Weight[is.na(data\)Weight)] <- … : Tại các vị trí mà Weight bị thiếu (TRUE trong is.na()), gán lại bằng giá trị trung bình. Sau khi chạy, cột Weight không còn giá trị NA

1.2.4 Chuẩn hóa chữ trong biến Gender

data$Gender <- tools::toTitleCase(data$Gender)

Giải thích:

  • data$Gender: là cột Gender trong bộ dữ liệu data.

  • tools::toTitleCase(): là hàm trong package tools, dùng để viết hoa chữ cái đầu tiên của mỗi từ (title case).

=> Hàm này sẽ chuẩn hóa lại giá trị trong cột Gender

1.2.5 Mã hóa biến Gender thành dạng nhị phân:

data$GenderF <- ifelse(data$Gender == "Female", 1, 0)

Giải thích: - ifelse() là hàm điều kiện trong R. Cú pháp: ifelse(điều_kiện, giá_trị_nếu_đúng, giá_trị_nếu_sai)

  • data$Gender == “Female”: kiểm tra từng dòng trong cột Gender xem có bằng “Female” không.

→ Nếu đúng → trả về 1. Nếu sai → trả về 0

Kết quả được gán vào cột mới GenderF.

1.2.6 Mã hóa Gender thành dạng ‘factor’

data$Gender <- as.factor(data$Gender)

Giải thích: as.factor(): Hàm chuyển kiểu dữ liệu sang factor (biến phân loại). Giúp R hiểu đây không phải là dữ liệu dạng số hay text thông thường, mà là biến phân loại, dễ dàng trong việc thực hiện vẽ biểu đồ, tóm tắt dữ liệu.

1.2.7 Loại bỏ các dòng có giá trị ngoại lệ BMI

library(dplyr)
## 
## Attaching package: 'dplyr'
## The following objects are masked from 'package:stats':
## 
##     filter, lag
## The following objects are masked from 'package:base':
## 
##     intersect, setdiff, setequal, union
data <- data %>% filter(BMI >= 10 & BMI <= 50)

Giải thích

filter() là hàm của thư viện dplyr, dùng để lọc dữ liệu theo điều kiện.

Giữ lại các dòng có chỉ số BMI trong khoảng từ 10 đến 50.

Những giá trị ngoài khoảng này có thể là lỗi nhập liệu (ví dụ BMI = 3 hoặc 120 là vô lý).

1.2.8 Chuẩn hóa (z-score) biến BMI:

data$BMIz <- scale(data$BMI)

Giải thích:

scale() chuẩn hóa dữ liệu theo công thức: z=x−mean(x)sd(x) (Giá trị trừ trung bình, chia cho độ lệch chuẩn).

Kết quả: giá trị chuẩn hóa có trung bình = 0, độ lệch chuẩn = 1.

Giúp so sánh giữa các cá nhân khác nhau trên cùng thang đo.

1.2.9 Chuẩn hóa chiều cao và nhịp tim về z-score

data$HeightZ <- scale(data$Height)
data$HeartRateZ <- scale(data$Heart.Rate)

Giải thích: scale() chuẩn hóa dữ liệu theo công thức: z=x−mean(x)sd(x) (Giá trị trừ trung bình, chia cho độ lệch chuẩn). Kết quả: giá trị chuẩn hóa có trung bình = 0, độ lệch chuẩn = 1. Tạo ra các cột mới: HeightZ và HeartRateZ trong bảng dữ liệu. Dễ dàng so sánh sự khác biệt giữa các biến khác nhau trên cùng thang chuẩn hóa (z-score) — hữu ích khi trực quan hóa nhiều biến cùng lúc.

1.2.10 Biến nhị phân nguy cơ sức khỏe cao:

data$HighRisk <- ifelse(data$BMI > 23 & data$Heart.Rate > 100, 1, 0)
table(data$HighRisk)
## 
##      0 
## 100000

Giải thích:

ifelse(điều kiện, giá_trị_nếu_đúng, giá_trị_nếu_sai)

HighRisk = 1: cá nhân có chỉ số BMI cao (trên 23) và nhịp tim cao (trên 100 nhịp/phút) → nguy cơ sức khỏe cao.

HighRisk = 0: cá nhân không thuộc nhóm nguy cơ cao Kết quả: 100000 quan sát của bộ dữ liệu đều không thuộc nhóm nguy cơ cao

1.2.11 Bảng kết quả

library(knitr)

kable(
  head(data[, c("Gender", "Age", "BMIz", "HeightZ","HeartRateZ", "HighRisk")]),
  caption = "Bảng minh họa sau khi chuẩn hóa dữ liệu"
)
Bảng minh họa sau khi chuẩn hóa dữ liệu
Gender Age BMIz HeightZ HeartRateZ HighRisk
Female 18 0.6148529 -0.9072217 1.3409531 0
Male 30 -0.2399155 -1.5781988 1.1677792 0
Female 32 -0.9373668 0.5274927 -0.3041990 0
Male 30 -0.6008088 0.4981349 1.6873009 0
Female 23 -0.6477781 -0.7583893 1.3409531 0
Male 32 0.9371273 -1.6181351 -0.8237207 0

Giải thích:

  • library(knitr): Gọi thư viện knitr, dùng để hiển thị bảng và kết quả đẹp hơn trong RMarkdown.

  • data[, c(…columns…)] : Chọn các cột cụ thể từ data.

  • head(…) Lấy 6 dòng đầu tiên để minh họa (tránh bảng quá dài).

  • kable() Hàm trong knitr, dùng để tạo bảng đẹp trong file HTML, PDF, hoặc Word xuất ra từ RMarkdown.

  • caption = “…” Gắn tên bảng (chú thích) hiển thị bên dưới bảng.

Nhận xét về bảng dữ liệu sau khi chuẩn hóa: Chuẩn hóa giúp so sánh trên cùng thang đo. Không còn giá trị ngoại lệ lớn (BMI, Chiều cao…), giúp nâng cao độ tin cậy của danh sách phân tích. HighRisk là biến mới xác định nguy cơ sức khỏe cao (BMI > 23 & Heart.Rate > 100). Khoảng cách này giúp phát hiện nhanh đối tượng cần quan tâm.

1.3 THỐNG KÊ MÔ TẢ

library(psych)
library(moments)
library(dplyr)
  • library(psych): Dùng trong phân tích thống kê mô tả, tóm tắt dữ liệu nhanh.

Hàm Mô tả: describe(data) Tóm tắt thống kê (mean, sd, median, min, max, skewness, kurtosis, v.v.)

  • library(moments): Dùng để tính các đặc trưng mô tả nâng cao của phân phối dữ liệu như:

skewness(x) Độ lệch (mức độ phân bố nghiêng trái hay phải)

kurtosis(x) Độ nhọn (mức độ phân bố tập trung hay trải rộng)

  • library(dplyr): Gói mạnh nhất để xử lý, biến đổi và tóm tắt dữ liệu trong R.

select() Chọn cột

filter() Lọc dòng

mutate() Tạo biến mới

arrange() Sắp xếp

summarise() Tóm tắt dữ liệu

group_by() Nhóm dữ liệu để tính trung bình, đếm,…

1.3.1 Kiểm tra cấu trúc tổng quát

str(data)
## tibble [100,000 × 16] (S3: tbl_df/tbl/data.frame)
##  $ Student.ID    : num [1:100000] 1 2 3 4 5 6 7 8 9 10 ...
##  $ Age           : num [1:100000] 18 30 32 30 23 32 21 28 21 32 ...
##  $ Gender        : Factor w/ 2 levels "Female","Male": 1 2 1 2 1 2 2 2 2 2 ...
##  $ Height        : num [1:100000] 162 152 183 182 164 ...
##  $ Weight        : num [1:100000] 72.4 47.6 55.7 63.3 46.2 ...
##  $ Blood.Type    : chr [1:100000] "O" "B" "A" "B" ...
##  $ BMI           : num [1:100000] 27.6 21.6 16.7 19.1 18.8 ...
##  $ Temperature   : num [1:100000] 99.1 98.7 98.3 98.8 98.5 ...
##  $ Heart.Rate    : num [1:100000] 95 93 76 99 95 70 66 85 75 61 ...
##  $ Blood.Pressure: num [1:100000] 109 104 130 112 105 128 134 123 111 94 ...
##  $ Cholesterol   : num [1:100000] 203 163 216 141 231 183 247 128 243 166 ...
##  $ GenderF       : num [1:100000] 1 0 1 0 1 0 0 0 0 0 ...
##  $ BMIz          : num [1:100000, 1] 0.615 -0.24 -0.937 -0.601 -0.648 ...
##   ..- attr(*, "scaled:center")= num 23.3
##   ..- attr(*, "scaled:scale")= num 7.03
##  $ HeightZ       : num [1:100000, 1] -0.907 -1.578 0.527 0.498 -0.758 ...
##   ..- attr(*, "scaled:center")= num 175
##   ..- attr(*, "scaled:scale")= num 14.5
##  $ HeartRateZ    : num [1:100000, 1] 1.341 1.168 -0.304 1.687 1.341 ...
##   ..- attr(*, "scaled:center")= num 79.5
##   ..- attr(*, "scaled:scale")= num 11.5
##  $ HighRisk      : num [1:100000] 0 0 0 0 0 0 0 0 0 0 ...

Giải thích: Hàm str(data) hiển thị cấu trúc tổng quát của đối tượng data. Cho biết:

Loại đối tượng (data.frame, tibble, v.v.)

Số hàng, số cột

Tên và kiểu dữ liệu của từng cột

Một vài giá trị mẫu trong mỗi cột

Nhận xét: Kết quả kiểm tra dữ liệu cấu trúc ( str(data)) cho thấy đang làm việc với một bảng bao gồm 100.000 dòng và 18 cột, lưu trữ thông tin sức khỏe cá nhân. Các biến thể như chiều cao, cân nặng, tuổi, huyết áp, nhiệt độ, nhịp tim, cholesterol đều có loại số, có lợi cho việc phân tích thống kê. Biến giới tính đã chuyển thành phần (dạng phân loại), còn nhóm máu dạng ký tự. Nhìn chung, cấu trúc dữ liệu rõ ràng, đầy đủ các loại sức khỏe cần thiết và đã được mã hóa đúng kiểu, phù hợp cho các bước xử lý, khám phá và phân tích dữ liệu tiếp theo.

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

library(psych)
describe(data$BMI)
##    vars     n  mean   sd median trimmed  mad   min   max range skew kurtosis
## X1    1 1e+05 23.32 7.03  22.62   22.93 7.42 10.07 44.31 34.24 0.45    -0.41
##      se
## X1 0.02

Giải thích:

Hàm describe() thường nằm trong gói psych, được dùng để tính toán các chỉ số thống kê mô tả cho từng biến (cột) trong bộ dữ liệu, xuất ra

các chỉ số như:

mean (trung bình)

sd (độ lệch chuẩn)

median (trung vị)

trimmed mean (trung bình cắt tỉa)

mad (độ lệch tuyệt đối trung vị)

min, max, range

skew (độ lệch): Skew > 0: lệch phải (đuôi dài bên phải). Skew < 0: lệch trái. Skew ≈ 0: đối xứng.

kurtosis (độ nhọn): > 0: nhọn (dữ liệu tập trung nhiều ở trung tâm); < 0: bẹt (phân tán rộng hơn).

se (sai số chuẩn)

Kết quả:

Mean (Trung bình) ≈ 23.32: Trung bình BMI trong mẫu nằm ở mức bình thường (18.5–24.9). Median (Trung vị) 22.62: Gần với giá trị trung bình → phân bố khá đối xứng, không bị lệch mạnh. SD (Độ lệch chuẩn) 7.03: Mức độ phân tán tương đối lớn, cho thấy BMI giữa các cá nhân khá đa dạng. Min – Max 10.07 – 44.31: Biên độ trải rộng (range = 34.24), thể hiện có cả người rất gầy lẫn rất béo phì. Trimmed mean (Trung bình cắt tỉa) 22.93: Sau khi bỏ đi 10% giá trị ngoại lai, trung bình vẫn gần 22.9 → dữ liệu ổn định, ít nhiễu. MAD (Độ lệch tuyệt đối trung vị) 7.42: Tương đối cao → dữ liệu BMI có phân tán đáng kể quanh trung vị. Skewness (Độ lệch) 0.45: Dương nhẹ → phân bố lệch phải, có một số người có BMI cao hơn mức trung bình. Kurtosis (Độ nhọn) -0.41: Phân bố phẳng hơn phân phối chuẩn, nghĩa là dữ liệu khá đều, không quá tập trung quanh trung tâm. SE (Sai số chuẩn) 0.02: Rất nhỏ → ước lượng trung bình BMI đáng tin cậy.

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

describe(data$Height)
##    vars     n   mean    sd median trimmed   mad min max range skew kurtosis
## X1    1 1e+05 174.91 14.47 174.79  174.89 18.59 150 200    50 0.01     -1.2
##      se
## X1 0.05

Kết quả Mean (Trung bình) 174.86 Chiều cao trung bình của mẫu là ≈175 cm, tương đối cao so với mức trung bình người Việt (~165 cm). SD (Độ lệch chuẩn) 14.46 Cho thấy độ phân tán vừa phải, có sự chênh lệch nhất định giữa các cá nhân, nhưng không quá lớn. Median (Trung vị) 174.72 Rất gần với giá trị trung bình → phân bố dữ liệu khá đối xứng, không bị lệch rõ rệt. Trimmed Mean (Trung bình cắt tỉa) 174.84 Gần với Mean → ít giá trị ngoại lai, dữ liệu ổn định. MAD (Độ lệch tuyệt đối trung vị) 18.57 Mức độ phân tán xung quanh trung vị vẫn đáng kể, phản ánh sự đa dạng chiều cao. Min – Max 150.00 – 199.99 Khoảng biến thiên là gần 50 cm, cho thấy phạm vi chiều cao khá rộng, bao gồm cả người thấp và người rất cao. Range (Biên độ) 49.99 Xác nhận độ dao động của dữ liệu trong khoảng 50 cm. Skewness (Độ lệch) 0.013 Rất gần 0 → phân bố gần đối xứng, không lệch trái hay lệch phải đáng kể. Kurtosis (Độ nhọn) 1.80 Dương và lớn hơn 0 → phân bố hơi nhọn hơn phân bố chuẩn, nghĩa là nhiều giá trị tập trung quanh trung bình. SE (Sai số chuẩn) 0.0528 Rất nhỏ → ước lượng trung bình chiều cao đáng tin cậy, chứng tỏ kích thước mẫu lớn.

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

describe(data$Age)
##    vars     n  mean   sd median trimmed  mad min max range skew kurtosis   se
## X1    1 1e+05 26.02 4.89     26   26.02 5.93  18  34    16    0     -1.2 0.02

Nhận xét: Mean (Trung bình) 26.02: Tuổi trung bình của mẫu là khoảng 26 tuổi SD (Độ lệch chuẩn) 4.89: Mức độ phân tán tuổi quanh trung bình là 4.89 năm — tức là đa số rơi vào khoảng 21–31 tuổi Median (Trung vị) 26: Một nửa mẫu có tuổi nhỏ hơn hoặc bằng 26 Trimmed Mean 26.02: Trung bình sau khi loại bỏ các giá trị cực trị gần như bằng Mean, chứng tỏ không có ngoại lệ mạnh MAD 5.93: Mức độ dao động trung bình quanh giá trị trung tâm là khoảng 6 tuổi Min 18: Tuổi nhỏ nhất trong mẫu là 18 Max 34: Tuổi lớn nhất trong mẫu là 34 Range 16: Khoảng biến thiên (chênh lệch lớn nhất – nhỏ nhất) là 16 tuổi Skew (Độ lệch) 0: Phân phối gần như đối xứng quanh trung bình Kurtosis (Độ nhọn) -1.2: Phân phối phẳng hơn phân phối chuẩn → ít tập trung, tuổi phân tán đều SE (Sai số chuẩn) 0.02: Trung bình mẫu rất ổn định, ít dao động

1.3.5 Thống kê mô tả biến Heart.Rate

describe(data$Heart.Rate)
##    vars     n  mean    sd median trimmed   mad min max range skew kurtosis   se
## X1    1 1e+05 79.51 11.55     79   79.52 14.83  60  99    39    0     -1.2 0.04

Nhận xét:

Nhịp tim của các cá nhân trong tập dữ liệu dao động từ 60 nhịp/phút đến 99 nhịp/phút, thể hiện sự chênh lệch nhất định giữa những người có nhịp tim chậm và những người có nhịp tim cao hơn mức trung bình.

Trung vị (Median) đạt 79 nhịp/phút, gần tương đương với giá trị trung bình (Mean = 79.51 nhịp/phút), cho thấy phân phối dữ liệu khá đối xứng và không có hiện tượng lệch rõ rệt.

Độ lệch chuẩn (SD = 11.55) và phương sai (Var = 133.38) cho thấy mức độ phân tán trung bình, tức là nhịp tim giữa các cá nhân có sự dao động nhưng không quá lớn, phần lớn vẫn nằm trong giới hạn bình thường.

Nhịp tim tối đa 99 nhịp/phút tuy cao hơn mức trung bình, nhưng vẫn nằm trong ngưỡng sinh lý an toàn đối với người trưởng thành, trong khi mức tối thiểu 60 nhịp/phút phản ánh nhóm có nhịp tim ổn định, thể lực tốt hoặc ít căng thẳng.

Kết luận: Phân bố nhịp tim của các đối tượng khảo sát khá ổn định, tập trung quanh mức 79 nhịp/phút, đặc trưng cho tình trạng sức khỏe tim mạch bình thường. Điều này cho thấy mẫu dữ liệu phản ánh tốt trạng thái thể chất ổn định và không có sự xuất hiện của các giá trị bất thường

1.3.6 Tạo nhóm tuổi bằng cut() .

data$AgeGroup <- cut(data$Age, breaks = c(15, 20, 25, 30, 100), right = TRUE, labels = c("16-20", "21-25", "26-30", "31+"))
table(data$AgeGroup)
## 
## 16-20 21-25 26-30   31+ 
## 17399 29431 29666 23504

Giải thích:

cut() chia dữ liệu liên tục thành các khoảng (bins).

breaks xác định ranh giới nhóm tuổi.

labels đặt tên nhóm.

right = TRUE: khoảng được bao gồm giá trị bên phải.

Nhận xét

Nhóm 26–30 tuổi chiếm tỷ trọng cao nhất (≈30%) → đây là độ tuổi phổ biến nhất trong mẫu.

Kế đến là nhóm 21–25 tuổi với tỷ lệ tương đương, cho thấy phần lớn người trong mẫu ở độ tuổi 21–30.

Nhóm 16–20 tuổi chỉ chiếm khoảng 17.5%, thể hiện số người trẻ (dưới 21) ít hơn đáng kể.

Nhóm trên 31 tuổi (31+) chiếm 23.1%, thấp hơn nhóm trung niên (21–30), cho thấy mẫu có xu hướng trẻ hóa.

1.3.7 Tạo nhóm phân loại BMI theo chuẩn WHO

→ Giúp nhóm giá trị liên tục thành các cụm dễ so sánh, phục vụ trực quan hóa/phân tích nhóm nguy cơ.

data$BMIcat <- as.character(cut(data$BMI,
                                breaks = c(0, 18.5, 23, 25, 100),
                                labels = c("Gầy", "Bình thuờng", "Thừa cân", "Béo phì")))
table(data$BMIcat)
## 
##     Béo phì Bình thuờng         Gầy    Thừa cân 
##       37867       23433       28495       10205

Giải thích:

cut() chia giá trị BMI thành 4 nhóm:

< 18.5 → Gầy

18.5–23 → Bình thường

23–25 → Thừa cân

25 → Béo phì

Sau đó as.character() chuyển từ factor sang ký tự để dễ thao tác. Nhận xét: Kết quả cho thấy đa số người trong mẫu có thể trạng ngoài mức bình thường, trong đó nhóm béo phì chiếm 36.3%, gầy chiếm 27.3%, trong khi chỉ 22.5% có thể trạng bình thường. Điều này phản ánh sự mất cân đối về dinh dưỡng và thói quen sinh hoạt trong mẫu khảo sát.

1.3.8 Tạo nhóm chiều cao dựa vào tứ phân vị

→ Chia thành các nhóm: thấp, trung bình, cao, rất cao.

data$HeightGroup <- as.character(cut(data$Height,
                    breaks = quantile(data$Height, probs = seq(0, 1, 0.25)),
                    include.lowest = TRUE,
                    labels = c("Thấp", "Trung bình", "Cao", "Rất cao")))
table(data$HeightGroup)
## 
##        Cao    Rất cao       Thấp Trung bình 
##      24999      25000      25000      25001

Giải thích:

quantile(…, probs = seq(0,1,0.25)) chia dữ liệu thành 4 khoảng bằng nhau (mỗi khoảng 25% dữ liệu).

cut() tạo nhóm tương ứng với từng khoảng.

include.lowest = TRUE: bao gồm giá trị thấp nhất trong nhóm đầu tiên. Kết quả: Phân bố chiều cao của mẫu khá đồng đều giữa bốn nhóm (thấp, trung bình, cao, rất cao), mỗi nhóm chiếm khoảng 25% tổng số quan sát. Điều này cho thấy dữ liệu có tính cân bằng tốt, không bị thiên lệch về một nhóm chiều cao cụ thể.

1.3.9 Tạo nhóm nhịp tim:

data$HRgroup <- as.character(cut(data$Heart.Rate, breaks = c(0,60,100,200), labels = c("Thấp","Bình thường", "Cao"), right = FALSE))
table(data$HRgroup)
## 
## Bình thường 
##      100000

Giải thích:

Chia nhịp tim theo ngưỡng y học:

<60: Thấp

60–100: Bình thường

100: Cao

right = FALSE: khoảng không bao gồm giá trị phải (vd: [0,60)).

Kết quả: Tất cả các đối tượng khảo sát đều có nhịp tim bình thường, chiếm 100%. Không ghi nhận trường hợp rối loạn hay bất thường nào, cho thấy mẫu dữ liệu này đại diện cho nhóm người có tình trạng tim mạch ổn định.

1.3.10 Tạo biến tổ hợp “Gender_AgeGroup”

data$Gender_AgeGroup <- paste(data$Gender, data$AgeGroup, sep = "_")

Giải thích:

paste() nối 2 chuỗi lại với nhau, ở đây nối Gender và AgeGroup bằng dấu _.

Tạo thành chuỗi như “Male_21-25” hoặc “Female_26-30”.

1.3.11 Biến Heart.Rate theo từng nhóm chiều cao.

library(dplyr)
result_hr_height <- data %>%
  group_by(HeightGroup) %>%
  summarise(mean_HR = mean(Heart.Rate, na.rm=TRUE),
            se_HR = sd(Heart.Rate, na.rm=TRUE)/sqrt(n()))

group_by(HeightGroup): Chia dữ liệu thành các nhóm dựa trên giá trị của biến HeightGroup (Ví dụ: Thấp, Trung bình, Cao, Rất cao).

summarise(…): Tính toán các thống kê cho từng nhóm:

mean_HR: Nhịp điệu trung bình của nhóm chiều cao đó (thiếu giá trị).

se_HR: Sai số chuẩn (sai số chuẩn) của nhịp thời gian trong mỗi nhóm

1.3.12 Bảng kết quả

library(knitr)
kable(
  head(data[, c("Gender", "Age", "AgeGroup", "BMIcat", "HeightGroup")]),
  caption = "Bảng minh họa sau khi chuẩn hóa và phân loại dữ liệu"
)
Bảng minh họa sau khi chuẩn hóa và phân loại dữ liệu
Gender Age AgeGroup BMIcat HeightGroup
Female 18 16-20 Béo phì Thấp
Male 30 26-30 Bình thuờng Thấp
Female 32 31+ Gầy Cao
Male 30 26-30 Bình thuờng Cao
Female 23 21-25 Bình thuờng Trung bình
Male 32 31+ Béo phì Thấp

Giải thích:

  • library(knitr): Gọi thư viện knitr, dùng để hiển thị bảng và kết quả đẹp hơn trong RMarkdown.

  • data[, c(…columns…)] : Chọn các cột cụ thể từ data.

  • head(…) Lấy 6 dòng đầu tiên để minh họa (tránh bảng quá dài).

  • kable() Hàm trong knitr, dùng để tạo bảng đẹp trong file HTML, PDF, hoặc Word xuất ra từ RMarkdown.

  • caption = “…” Gắn tên bảng (chú thích) hiển thị bên dưới bảng.

Nhận xét về bảng dữ liệu sau khi chuẩn hóa: AgeGroup chia thành các khoảng tuổi ý nghĩa ( 18-20, 26–30, 31+).

BMIcat phân loại trạng thái chất (Béo phì, Bình thường, Gầy…).

HeightGroup, HRgroup cho từng loại chiều cao và nhịp tim, giúp nhận thấy đặc điểm của từng nhóm rõ ràng hơn phân phối gốc

Giới tính_AgeGroup kết hợp hai chiều nhân khẩu học, hỗ trợ phân tích chéo và nhóm báo cáo chi tiết.

1.3.13 Kiểm tra so sánh trung bình BMI giữa nam và nữ (t-test)

t.test(BMI ~ Gender, data = data)
## 
##  Welch Two Sample t-test
## 
## data:  BMI by Gender
## t = 0.11775, df = 96129, p-value = 0.9063
## alternative hypothesis: true difference in means between group Female and group Male is not equal to 0
## 95 percent confidence interval:
##  -0.08235926  0.09288713
## sample estimates:
## mean in group Female   mean in group Male 
##             23.32445             23.31918

Giải thích: t.test(BMI ~ Gender, data = data)sử dụng hàm t.test trong ngôn ngữ R để kiểm tra sự khác biệt giữa hai nhóm độc lập Nhận xét kết quả: Giá trị p-value = 0,9063 : Lớn hơn 0,05, nghĩa là không có sự khác biệt có ý nghĩa thống kê giữa chỉ số BMI của nam và nữ.

Trung bình BMI của nữ là 23.3245, trung bình BMI của nam là 23.3192 : Hai giá trị này gần như bằng nhau.

Độ tin cậy 95% cho hiệu số trung bình (-0,08 đến 0,09) bao phủ giá trị 0 , cho thấy sự khác biệt trung bình BMI giữa hai nhóm là rất nhỏ hoặc không tồn tại.

1.3.14 Xem tỷ lệ giới tính trong từng nhóm BMIcat

table(data$Gender, data$BMIcat)
##         
##          Béo phì Bình thuờng   Gầy Thừa cân
##   Female   17026       10650 12777     4575
##   Male     20841       12783 15718     5630
prop.table(table(data$Gender, data$BMIcat), 1) * 100
##         
##           Béo phì Bình thuờng      Gầy Thừa cân
##   Female 37.81203    23.65195 28.37568 10.16034
##   Male   37.91203    23.25366 28.59274 10.24158

Giải thích: - table(data\(Gender, data\)BMI_Level): Tạo bảng tần số chéo, hiển thị số lượng sinh viên thuộc từng nhóm kết hợp giữa:

Giới tính (Gender): Male, Female

Mức BMI (BMIcat): Underweight, Normal, Overweight, Obese

  • prop.table() → chuyển bảng tần số thành bảng tỷ lệ phần trăm.

Tham số “, 1”: nghĩa là tính theo hàng (tức theo từng giới tính).

” * 100”: để chuyển sang phần trăm (%).

Nhận xét chung: Tỷ lệ béo phì ở cả hai giới đều cao, đặc biệt nam giới có tỷ lệ béo phì nổi trội hơn nữ. Số lượng bệnh nhân nằm trong nhóm “gầy” hoặc “thừa cân” rất ít, được tìm thấy nhiều thành phần bệnh nhân tập trung vào hai nhóm lớn (bình thường và béo phì), đặc biệt là nhóm béo phì. Kết quả này phản ánh xu hướng tăng chỉ số BMI, cần chú ý hơn đến chế độ ăn uống và vận động, nhất là với nam giới.

1.3.15 Trung bình và độ lệch chuẩn BMI theo nhóm giới tính

data %>%
  group_by(Gender) %>%
  summarise(Mean_BMI = mean(BMI), SD_BMI = sd(BMI))
## # A tibble: 2 × 3
##   Gender Mean_BMI SD_BMI
##   <fct>     <dbl>  <dbl>
## 1 Female     23.3   7.04
## 2 Male       23.3   7.03

Giải thích: group_by(Gender): chia bộ dữ liệu thành các nhóm theo giới tính (nam và nữ).

summarise(…): tính toán các giá trị thống kê cho từng nhóm.

Mean_BMI = mean(BMI) → tính giá trị trung bình BMI của từng giới.

SD_BMI = sd(BMI) → tính độ lệch chuẩn BMI của từng giới.

Kết quả: Kết quả cho thấy trung bình BMI của nam và nữ gần như bằng nhau (nữ: 23.32, nam: 23.32) với độ lệch chuẩn tương đương. Điều này chứng minh không có sự khác biệt đáng kể về chỉ số BMI giữa hai giới trong dữ liệu này.

1.3.16 Số tuổi duy nhất

length(unique(data$Age))
## [1] 17

Giải thích: Đoạn mã length(unique(data$Age)) dùng để đếm số tuổi khác nhau (số giá trị tuổi duy nhất) trong cột Age của dữ liệu bảng data. Kết quả trả về là 17,nghĩa là hiện tại dữ liệu trong bảng có 17 độ tuổi khác nhau xuất hiện ở cột Age.

1.3.17 Giới tính (Gender)

1.3.17.1 Bảng tần số của Gender

tanso1 <- table(data$Gender)
bang_tanso1 <- data.frame(Gender = names(tanso1), TanSo = as.numeric(tanso1), TanSoTichLuy = cumsum(as.numeric(tanso1)))
print(bang_tanso1)
##   Gender TanSo TanSoTichLuy
## 1 Female 45028        45028
## 2   Male 54972       100000

Giải thích: Tạo bảng tần số (frequency table) của biến Gender.

names(tanso1): Trả về tên các nhóm trong bảng tần số

as.numeric(tanso1): Chuyển giá trị tần số từ dạng bảng (table) sang dạng số (numeric vector) để có thể tính toán dễ dàng. Đây chính là số lượng quan sát (TanSo) của từng giới.

cumsum(as.numeric(tanso1)): Hàm cumsum() nghĩa là cumulative sum → tính tổng cộng dồn. Từng phần tử sau bằng tổng của tất cả các phần tử trước đó. Kết quả thể hiện tần số tích lũy (TanSoTichLuy).

data.frame(…): Dùng để kết hợp các cột trên thành một bảng dữ liệu hoàn chỉnh (data frame).

Kết quả: Dữ liệu có phân chia giới tính khá sắc nét, nam nhiều hơn nữ, với 45.028 là nữ và 54.972 là nam

1.3.17.2 Bảng tần suất của Gender

tansuat1 <- prop.table(tanso1) * 100
bang_tansuat1 <- data.frame(Gender = names(tansuat1), TanSuat = round(as.numeric(tansuat1), 2), TanSuatTichLuy = round(cumsum(as.numeric(tansuat1)), 2))
print(bang_tansuat1)
##   Gender TanSuat TanSuatTichLuy
## 1 Female   45.03          45.03
## 2   Male   54.97         100.00

Giải thích: tanso1 là bảng tần số (số lượng từng giới tính) đã tạo ở bước trước bằng table(data$Gender).

prop.table(tanso1) tính tỷ lệ phần trăm của mỗi nhóm (tức là tần suất tương đối).

Nhân với 100 để chuyển sang đơn vị phần trăm (%).

names(tansuat1) → lấy tên các nhóm (ở đây là “Female”, “Male”).

as.numeric(tansuat1) → chuyển tần suất từ dạng bảng sang dạng số.

round(…, 2) → làm tròn 2 chữ số thập phân.

cumsum() → tính tần suất tích lũy (cộng dồn tỉ lệ phần trăm từ trên xuống).

data.frame() → gom lại thành bảng dữ liệu có 3 cột: Gender, TanSuat, TanSuatTichLuy. Kết quả:

Nữ (Nữ) sử dụng 45,03% , nam (Nam) sử dụng 54,97% tổng số khảo sát trong dữ liệu.

Tần suất tích lũy thể hiện: nhóm “Nữ” là 45,03%, cộng thêm nhóm “Nam” thành 100%

1.3.18 Bảng tần số biến Heart Rate theo Gender:

tanso <- table(data$Gender, data$Heart.Rate)
bang_tanso5 <- as.data.frame(tanso)
colnames(bang_tanso5) <- c("Gender", "Heart.Rate", "TanSo")
library(dplyr)
bang_tanso5 <- bang_tanso5 %>%
  group_by(Gender) %>%
  arrange(Heart.Rate) %>%
  mutate(TanSoTichLuy = cumsum(TanSo))
print(bang_tanso5)
## # A tibble: 80 × 4
## # Groups:   Gender [2]
##    Gender Heart.Rate TanSo TanSoTichLuy
##    <fct>  <fct>      <int>        <int>
##  1 Female 60          1096         1096
##  2 Male   60          1390         1390
##  3 Female 61          1140         2236
##  4 Male   61          1432         2822
##  5 Female 62          1081         3317
##  6 Male   62          1401         4223
##  7 Female 63          1092         4409
##  8 Male   63          1439         5662
##  9 Female 64          1141         5550
## 10 Male   64          1382         7044
## # ℹ 70 more rows

Giải thích: table(data\(Gender, data\)Heart.Rate): Tạo bảng đếm số lượng theo giá trị nhịp tim phù hợp với từng nhóm giới tính.

Chuyển sang khung dữ liệu dạng bảng, đặt cột tên (Gender, Heart.Rate, TanSo).

Sử dụng dplyr để group_by(Gender), sắp xếp theo nhịp tăng dần ( arrange(Heart.Rate)), sau đó tính tần số tích lũy ( TanSoTichLuy) cho từng nhóm Giới tính bằng hàm mutate(TanSoTichLuy = cumsum(TanSo)). Kết quả là một bảng bao gồm các cột: Giới tính, Heart.Rate, TanSo và TanSoTichLuy

1.3.19 Bảng tần suất nhóm tuổi

freq_age <- table(data$AgeGroup)
prop_age <- prop.table(freq_age)
result_age <- data.frame(AgeGroup = names(freq_age),
                         tanso = as.numeric(freq_age),
                         tansuat = round(100 * as.numeric(prop_age),2))
print(result_age)
##   AgeGroup tanso tansuat
## 1    16-20 17399   17.40
## 2    21-25 29431   29.43
## 3    26-30 29666   29.67
## 4      31+ 23504   23.50

Giải thích: - Tính tần số (frequency) cho từng nhóm tuổi.

table() đếm số lần xuất hiện của mỗi giá trị trong cột AgeGroup.

  • Tính tần suất (relative frequency).

prop.table() chia tần số của mỗi nhóm cho tổng số quan sát.

Kết quả là tỷ lệ phần trăm dạng thập phân

  • Tạo bảng dữ liệu (data frame) tổng hợp kết quả: AgeGroup: tên nhóm tuổi.

tanso: số lượng người (tần số).

tansuat: phần trăm (%) — làm tròn 2 chữ số thập phân

  • print(result_age): Hiển thị bảng kết quả ra màn hình.

Nhận xét:

  • Nhóm 26–30 tuổi chiếm 29.67%, cao nhất trong toàn bộ mẫu. → Điều này cho thấy phần lớn người trong dữ liệu thuộc nhóm trung niên trẻ, có thể đang trong giai đoạn có sức khỏe tốt.

  • Nhóm 21–25 tuổi chiếm 29.49%, chỉ thấp hơn rất ít so với nhóm 26–30. → Hai nhóm này cộng lại chiếm gần 60% toàn bộ mẫu, phản ánh dữ liệu tập trung nhiều ở người trẻ trưởng thành.

  • Nhóm tuổi lớn hơn (31+): Chiếm 23.42%, thấp hơn so với hai nhóm trên nhưng vẫn đáng kể.

  • Nhóm tuổi nhỏ nhất (16–20): Chỉ chiếm 17.41%, thấp nhất trong bốn nhóm → Cho thấy số lượng người trẻ tuổi ít hơn trong dữ liệu.

1.3.20 Bảng tần số, tần suất của BMI

freq_bmi <- table(data$BMIcat)
prop_bmi <- prop.table(freq_bmi)
result_bmi <- data.frame(BMIcat = names(freq_bmi),
                         tanso = as.numeric(freq_bmi),
                         tansuat = round(100 * as.numeric(prop_bmi),2))
print(result_bmi)
##        BMIcat tanso tansuat
## 1     Béo phì 37867   37.87
## 2 Bình thuờng 23433   23.43
## 3         Gầy 28495   28.49
## 4    Thừa cân 10205   10.20

Giải thích: - Hàm table() → đếm số lần xuất hiện (tần số) của từng giá trị trong cột BMIcat.

Kết quả: tạo bảng tần số (frequency table) → Lưu vào biến freq_bmi.

  • Hàm prop.table() → chuyển tần số sang tần suất (tỷ lệ) → Lưu vào biến prop_bmi.
  • BMIcat: Tên nhóm phân loại BMI (ví dụ: “Gầy”, “Bình thường”, “Thừa cân”, “Béo phì”)
  • tanso: Số lượng người trong mỗi nhóm (tần số)
  • tansuat: Tỷ lệ phần trăm mỗi nhóm (tần suất, làm tròn 2 chữ số)

round(…, 2) → làm tròn đến 2 số thập phân để bảng gọn gàng.

100 * → chuyển tỷ lệ từ dạng số thập phân sang phần trăm .

Nhận xét

  • Nhóm chiếm tỷ lệ cao nhất: Béo phì (37.80%) là nhóm có tần suất cao nhất trong dữ liệu. Điều này cho thấy tình trạng thừa cân, béo phì khá phổ biến trong mẫu khảo sát.

  • Nhóm chiếm tỷ lệ thứ hai: Gầy (28.52%) đứng thứ hai, phản ánh một bộ phận lớn có thể thiếu cân hoặc dinh dưỡng chưa đủ. → Sự chênh lệch giữa “Béo phì” và “Gầy” cho thấy mức độ phân hóa thể trạng khá rõ.

  • Nhóm “Bình thường”: Chỉ chiếm 23.43%, nghĩa là chưa đến 1/4 dân số mẫu có chỉ số BMI trong phạm vi bình thường. → Đây là dấu hiệu mức độ sức khỏe cộng đồng chưa cân bằng.

  • Nhóm “Thừa cân”: Chiếm 10.24%, thấp nhất trong các nhóm, có thể là giai đoạn chuyển tiếp giữa bình thường và béo phì.

Kết luận: Phân bố BMI trong dữ liệu cho thấy xu hướng bất cân đối về thể trạng: Gần 38% người bị béo phì. Trong khi chỉ 23% có cân nặng bình thường. Điều này cho thấy tỷ lệ thừa cân và béo phì đang chiếm ưu thế, là yếu tố quan trọng cần quan tâm trong các nghiên cứu sức khỏe hoặc dinh dưỡng.

1.3.21 Bảng tần số, tần suất của nhóm chiều cao

freq_height <- table(data$HeightGroup)
prop_height <- prop.table(freq_height)
result_height <- data.frame(HeightGroup = names(freq_height),
                            tanso = as.numeric(freq_height),
                            tansuat = round(100 * as.numeric(prop_height),2))
print(result_height)
##   HeightGroup tanso tansuat
## 1         Cao 24999      25
## 2     Rất cao 25000      25
## 3        Thấp 25000      25
## 4  Trung bình 25001      25

Kết quả bảng cho thấy 4 nhóm chiều cao (Cao, Rất cao, Thấp, Trung bình) đều có số lượng gần như bằng nhau: mỗi nhóm dao động xung quanh 25.000 người, sử dụng 25% tổng mẫu. Không có hiện tượng chênh lệch giữa các nhóm chiều cao trong mẫu dữ liệu này.

1.3.22 Bảng tần số, tần suất nhóm nhịp tim

freq_hr <- table(data$HRgroup)
prop_hr <- prop.table(freq_hr)
result_hr <- data.frame(HRgroup = names(freq_hr),
                        tanso = as.numeric(freq_hr),
                        tansuat = round(100 * as.numeric(prop_hr),2))
print(result_hr)
##       HRgroup tanso tansuat
## 1 Bình thường 1e+05     100

Kết luận: Biến HRgroup (nhóm nhịp tim) không có sự phân hóa giữa các mức độ — tất cả các giá trị đều rơi vào cùng một nhóm “Bình thường”. Điều này cho thấy không tồn tại sự biến thiên trong đặc trưng nhịp tim của mẫu

1.4 TRỰC QUAN HÓA DỮ LIỆU

1.4.1 Đồ thị tần suất nhóm tuổi

library(ggplot2)
## 
## Attaching package: 'ggplot2'
## The following objects are masked from 'package:psych':
## 
##     %+%, alpha
ggplot(result_age, aes(x = AgeGroup, y = tansuat)) +
  geom_segment(aes(x = AgeGroup, xend = AgeGroup, y = 0, yend = tansuat), color = "skyblue", size = 2) +    # Layer 1: Thanh dọc
  geom_point(color = "#FF8C42", size = 4, alpha = 0.7) +                                                    # Layer 2: Đầu chấm lollipop
  geom_text(aes(label = tanso), vjust = -1.2, color = "black", size = 2) +                                  # Layer 3: Hiện số lượng
  geom_hline(yintercept = mean(result_age$tansuat), color = "purple", linetype = "dashed", size = 1) +      # Layer 4: Đường trung bình
  labs(title = "Tần suất nhóm tuổi (Lollipop plot)", subtitle = "So sánh các nhóm tuổi toàn mẫu",
       x = "Age group", y = "Percent (%)") +                                                               # Layer 5: Tiêu đề, trục
  theme_minimal()
## Warning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.
## ℹ Please use `linewidth` instead.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.

Giải thích: - Khởi tạo biểu đồ ggplot:

Dữ liệu đầu vào: result_age

Trục hoành (x): biến AgeGroup

Trục tung (y): biến tansuat (tần suất phần trăm)

  • Layer 1: Vẽ đường dọc (thân kẹo)

geom_segment() vẽ đường từ (x, y) → (xend, yend)

Ở đây: từ 0 đến giá trị tần suất (tansuat) theo từng nhóm tuổi

color = “skyblue” tạo màu xanh dịu

size = 2 tăng độ dày cho thân kẹo

  • Layer 2: Vẽ đầu kẹo (chấm tròn)

Màu cam đậm #FF8C42 nổi bật

size = 8: chấm to để dễ nhìn

alpha = 0.7: làm chấm hơi trong suốt, tạo hiệu ứng thẩm mỹ

  • Layer 3: Hiển thị giá trị tần số (số lượng người) ngay trên mỗi điểm

label = tanso → hiện số lượng thực tế (không chỉ phần trăm)

vjust = -1.2 → đẩy chữ lên trên đầu chấm

size = 4 → cỡ chữ vừa phải

  • Layer 4: Vẽ đường ngang thể hiện trung bình tần suất các nhóm tuổi

mean(result_age$tansuat) → giá trị trung bình tần suất

color = “purple” và linetype = “dashed” → đường gạch tím

Giúp dễ dàng so sánh nhóm nào cao/thấp hơn trung bình

  • Layer 5: Thêm tiêu đề và nhãn trục

title → tiêu đề lớn của biểu đồ

subtitle → chú thích phụ, nêu ý nghĩa

x & y → ghi nhãn cho hai trục

  • Giao diện tối giản (minimal)

Nhận xét:

  • Nhóm 26–30 sử dụng mẫu cao nhất với 22.222, tương ứng với khoảng 29,7% tổng số trong toàn bộ khảo sát. Bộ dữ liệu tập trung phần lớn vào nhóm trung niên trẻ, là những người trưởng thành.

  • Nhóm 21–25 sử dụng 22.085 mẫu , tỷ lệ tiến độ 29,5%, chỉ thấp hơn nhóm đông nhất một chút.

Hai nhóm tuổi này cộng lại sử dụng gần 60% toàn bộ mẫu, nhận thấy dữ liệu khảo sát tập trung về những người trưởng thành thành.

Đây là giai đoạn nhiều biến động về đường sống, sức khỏe.

  • Nhóm tuổi 31+ sử dụng 17.541 mẫu , tương ứng 23,4%. Tỷ lệ này thấp hơn nhóm trẻ nhưng vẫn đáng kể

  • Nhóm ít nhất 16–20: Chỉ 13.042 người thuộc nhóm 16–20 , tỷ lệ thấp nhất (khoảng 17,4%). Con số này được tìm thấy ở số lượng người trẻ tuổi.

1.4.2 Đồ thị tần suất các nhóm BMI

ggplot(result_bmi, aes(x = BMIcat, y = tansuat, fill = BMIcat)) +
  geom_bar(stat = "identity", alpha = 0.8) +                                 # Layer 1: Cột bar chart cho từng nhóm
  geom_text(aes(label = paste0(round(tansuat,1), "%")), vjust = -0.5, color = "black", size = 2) +  # Layer 2: Nhãn số liệu (%) trên đầu cột
  geom_hline(yintercept = mean(result_bmi$tansuat), color = "blue", linetype = "dashed", size = 1) + # Layer 3: Đường trung bình
  scale_fill_brewer(palette = "Pastel2") +                                   # Layer 4: Màu pastel nhẹ, chuyên nghiệp
  labs(title = "Tần suất các nhóm BMI", x = "BMI group", y = "Tần suất (%)") + # Layer 5: Tiêu đề, trục rõ ràng
  theme_minimal()

Giải thích chi tiết từng dòng ggplot(result_bmi, aes(x = BMIcat, y = tansuat, fill = BMIcat))

Khởi tạo biểu đồ với bảng tổng hợp BMI của nhóm dữ liệu.

Pad x là biến phân nhóm BMIcat (Ví dụ: “Gầy”, “Bình thường”, “Thừa cân”, “Béo phì”).

Pad y là tỉ lệ phần trăm của từng nhóm ( tansuat).

Mỗi BMI nhóm sẽ có một thuộc tính nhận dạng đặc trưng bằng màu fill = BMIcat.

geom_bar(stat = “identity”, alpha = 0.8)

Vẽ từng thanh cột, chiều cao là phần trăm giá trị của từng nhóm BMI.

stat = “identity”nghĩa là lấy giá trị y đúng (không đếm số mẫu nữa).

alpha = 0.8giúp thanh màu dịu lại, không quá đậm/chói.

geom_text(aes(label = paste0(round(tansuat,1), “%”)), vjust = -0.5, color = “black”, size = 4)

Thêm dữ liệu (% Tần suất) trên đầu mỗi cột (nhãn).

vjust = -0.5để dán nhãn lên phía trên cột, dễ đọc.

Đặt chữ màu đen, kích thước chữ 4 rõ ràng.

geom_hline(yintercept = mean(result_bmi$tansuat), color = “blue”, linetype = “dashed”)

Vẽ một đường ngang (đường ngang) ở vị trí trung tâm phần trăm của tất cả các nhóm.

Giúp người đọc so sánh từng nhóm với tổng thể “đức trung bình”.

scale_fill_brewer(palette = “Pastel2”)

Chọn bảng màu pastel dịu dàng, mỗi nhóm BMI có màu hài hòa riêng, phù hợp báo cáo/sách.

labs(title = “Tần suất các nhóm BMI”, x = “BMI group”, y = “Tần suất (%)”)

Thêm tiêu đề cho biểu đồ, tên trục x/y rõ ràng giúp người xem dễ hiểu nội dung.

theme_minimal()

Sử dụng chủ đề tối giản, loại bỏ chi tiết thừa (nền, lưới quá dày), làm biểu đồ mượt mà và chuyên nghiệp hơn.

Nhận xét - Nhóm béo sử dụng tỷ lệ cao nhất: Đạt 37,8% toàn mẫu , nhóm “Béo phì” vượt xa các nhóm còn lại và nằm trên mức trung bình của các nhóm BMI. Điều này cho thấy xu hướng cân nặng, béo phì CÓ tỷ trọng lớn, là vấn đề sức khỏe cần quan tâm đặc biệt khi phân tích.

  • Nhóm gầy cũng có nhiều biến thể khác: Kiếm 28.5% , nhóm “Gầy” xếp sau nhóm béo phì, đồng thời cũng cao hơn mức trung bình toàn mẫu.

= Nhóm bình thường ở mức trung bình: Bình thường chỉ đạt 23,4%. Điều này cho thấy tỷ lệ người có BMI “đạt chuẩn” trong khảo sát còn hạn chế

  • Nhóm thừa cân: Chỉ 10,2% số mẫu thuộc nhóm “Thừa cân”, thấp nhất trong các loại BMI.

Đường ngang màu xanh dương biểu thị giá trị trung bình của các nhóm BMI, chỉ có “Béo phì” và “Gầy” vượt trên mức trung bình, các nhóm còn lại đều thấp hơn.

1.4.3 Đồ thị tỉ lệ các nhóm chiều cao (HeightGroup)

result_height$fraction <- result_height$tansuat / sum(result_height$tansuat)
ggplot(result_height, aes(x = "", y = fraction, fill = HeightGroup)) +
  geom_col(width = 1, color = "white", alpha=0.8) +                    # Layer 1: Pie nền
  coord_polar(theta = "y") +                                           # Layer 2: Dạng tròn
  geom_text(aes(label = paste0(HeightGroup, ": ", round(tansuat,2), "%")),
            position = position_stack(vjust = 0.5), color = "black", size=3) +  # Layer 3: Nhãn group
  labs(title = "Tỉ lệ các nhóm chiều cao", fill="Height group") +      # Layer 4: Tiêu đề, legend 
  theme_void() +                                                       # Layer 5: Xóa trục nền dễ nhìn
  scale_fill_brewer(palette = "Pastel1")

result_height\(fraction <- result_height\)tansuat / sum(result_height\(tansuat) Tạo cột mới fraction trong bảng result_height, dùng để tính tỉ lệ phần trăm của từng nhóm chiều cao. Công thức: lấy giá trị tansuat của mỗi nhóm chia cho tổng tất cả tần suất (sum(result_height\)tansuat)).

ggplot(result_height, aes(x = ““, y = fraction, fill = HeightGroup)) Khởi tạo khung đồ thị ggplot với dữ liệu là result_height.

x = ““: không sử dụng trục hoành (biểu đồ tròn không cần trục X).

y = fraction: trục tung thể hiện tỉ lệ phần trăm của từng nhóm.

fill = HeightGroup: tô màu khác nhau cho từng nhóm chiều cao.

geom_col(width = 1, color = “white”, alpha = 0.8) Tạo cột (bar) làm nền cho biểu đồ tròn.

width = 1: đặt chiều rộng cột tối đa.

color = “white”: đường viền trắng giữa các lát giúp dễ phân biệt.

alpha = 0.8: giảm độ đậm màu, giúp hiển thị nhẹ nhàng hơn.

coord_polar(theta = “y”) Chuyển biểu đồ cột thành biểu đồ tròn (pie chart).

theta = “y”: phần diện tích các lát được xác định theo giá trị trục Y (fraction).

geom_text(aes(label = paste0(HeightGroup, “:”, round(tansuat, 2), “%”)), position = position_stack(vjust = 0.5), color = “black”, size = 4) Thêm nhãn (label) hiển thị tên nhóm và phần trăm ngay giữa lát bánh.

paste0(…): ghép chuỗi gồm tên nhóm (HeightGroup) và phần trăm (tansuat).

round(tansuat, 2): làm tròn giá trị tần suất đến 2 chữ số thập phân.

position_stack(vjust = 0.5): căn giữa nhãn theo chiều dọc trong từng lát.

color = “black”: màu chữ đen giúp dễ đọc.

size = 4: kích thước chữ vừa phải.

labs(title = “Tỉ lệ các nhóm chiều cao”, fill = “Height group”) Thêm tiêu đề và tên cho chú thích (legend).

title: đặt tiêu đề cho biểu đồ.

fill: tên hiển thị cho phần màu (nhóm chiều cao).

theme_void() Xóa toàn bộ trục, đường kẻ và nền để biểu đồ trông sạch sẽ, tập trung vào dữ liệu.

scale_fill_brewer(palette = “Pastel1”) Sử dụng bảng màu Pastel1 từ thư viện RColorBrewer để tạo gam màu nhẹ nhàng, dễ nhìn, phù hợp cho biểu đồ tỉ lệ.

Nhận xét: Biểu đồ tròn cho thấy bốn nhóm chiều cao (“Cao”, “Rất cao”, “Thấp”, “Trung bình”) phân bố đều nhau, mỗi nhóm sử dụng 25%. Điều này cho thấy dữ liệu mẫu đã được chia thành các nhóm có quy định tương thích, thuận lợi cho việc so sánh và phân tích giữa chiều cao của nhóm.

1.4.4 Đồ thị trực quan hóa tần số nhịp tim

library(ggplot2)

ggplot(data, aes(x = Heart.Rate)) +
  geom_histogram(
    bins = 10,
    fill = "lightpink",
    color = "white"
  ) +
  geom_text(
    stat = "bin",
    bins = 10,
    aes(label = ..count..),
    vjust = -0.5,     
    size = 3,
    color = "black"
  ) +
  scale_x_continuous(
    breaks = seq(
      floor(min(data$Heart.Rate, na.rm = TRUE)),
      ceiling(max(data$Heart.Rate, na.rm = TRUE)),
      by = 10
    )
  ) +
  labs(
    title = "Trực quan hóa nhịp tim",
    x = "Nhịp tim (lần/phút)",
    y = "Tần số"
  ) +
  theme_minimal()
## Warning: The dot-dot notation (`..count..`) was deprecated in ggplot2 3.4.0.
## ℹ Please use `after_stat(count)` instead.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.

Giải thích: - Gọi thư viện ggplot2, dùng để vẽ các biểu đồ trong R theo cú pháp ngữ pháp đồ thị (grammar of graphics). - Khởi tạo đối tượng đồ thị ggplot:

data: tập dữ liệu của tôi.

aes(x = Heart.Rate): khai báo biến trục hoành (x) là nhịp tim. → Mỗi giá trị nhịp tim sẽ được chia thành các khoảng (bins) để vẽ histogram.

  • Layer 1: Vẽ biểu đồ histogram (phân bố tần số).

bins = 10: chia dữ liệu thành 10 khoảng giá trị.

fill = “lightpink”: tô màu hồng nhạt cho các cột.

color = “white”: viền cột màu trắng, giúp dễ nhìn hơn.

→ Cho biết có bao nhiêu người có nhịp tim rơi vào mỗi khoảng.

  • Layer 2: Thêm nhãn số lượng (tần số) lên trên mỗi cột.

stat = “bin”: tính toán theo cùng các “bin” của histogram.

aes(label = ..count..) : hiện số lượng quan sát trong mỗi bin.

vjust = -0.5: điều chỉnh vị trí chữ cao hơn một chút so với cột.

size = 3: kích thước chữ.

color = “black”: màu chữ đen.

→ Giúp ta biết số lượng mẫu cụ thể trong từng khoảng nhịp tim.

  • Tùy chỉnh thang chia trục X (nhịp tim).

floor(min(…)): lấy giá trị nhỏ nhất của nhịp tim, làm tròn xuống.

ceiling(max(…)): lấy giá trị lớn nhất, làm tròn lên.

by = 10: chia trục X thành các mốc cách nhau 10 đơn vị (60, 70, 80, …).

→ Giúp trục X nhìn gọn gàng, đều đặn, dễ đọc.

  • Thêm nhãn và tiêu đề biểu đồ:

title: tiêu đề chính.

x: nhãn trục X.

y: nhãn trục Y.

️ → Làm biểu đồ rõ ràng, có thể đưa vào báo cáo.

  • Dùng giao diện tối giản (Minimal theme): loại bỏ các đường viền và nền phức tạp.

Nhận xét:

Biểu đồ phân bố tần số nhịp tim được tìm thấy ở phần lớn các nhịp giá trị ở khoảng 70 đến 90 lần/phút, với các tần số cao nhất ở khoảng 70-80 và 80-90 lần/phút. Số lượng cá nhân có nhịp tim trong hai khoảng thời gian này lần lượt là 12.528 và 12.464 người, trong khi số lượng ở các biên độ thấp (60-70 và 90-100 lần/phút) có xu hướng giảm. Điều này cho thấy phần lớn mẫu có nhịp độ ở mức bình thường, chỉ có một số ít ở hai đầu của dải nhịp.

1.4.5 Đồ thi phân bố Heart Rate theo Gender

library(ggplot2)
library(dplyr)
library(viridis)
## Loading required package: viridisLite
data <- data %>%
  mutate(Heart.Rate.Group = cut(
    Heart.Rate,
    breaks = seq(floor(min(Heart.Rate, na.rm = TRUE)),
                 ceiling(max(Heart.Rate, na.rm = TRUE)),
                 by = 10),
    right = FALSE
  )) %>%
  filter(!is.na(Heart.Rate.Group))

long_HR <- data %>%
  group_by(Gender, Heart.Rate.Group) %>%
  summarise(Count = n(), .groups = "drop")

ggplot(long_HR, aes(x = Gender, y = Count, fill = Heart.Rate.Group)) +
  geom_col(position = position_dodge(width = 0.8), width = 0.7) +
  geom_text(aes(label = Count),
            position = position_dodge(width = 0.8),
            vjust = -0.5, size = 3) +
  scale_fill_viridis(discrete = TRUE, option = "A", name = "Heart Rate (nhịp/phút)") +
  labs(title = "Phân bố Heart Rate theo Gender",
       x = "Gender",
       y = "Số lượng") +
  theme_minimal() +
  theme(
    legend.position = "top",
    axis.text.x = element_text(angle = 0, hjust = 0.5),
    plot.title = element_text(size = 16, face = "bold", hjust = 0.5)
  )

Giải thích: mutate(Heart.Rate.Group = …) Tạo một cột mới Heart.Rate.Group dựa trên Heart.Rate.

cut(…) Chia cột Heart.Rate thành các nhóm khoảng (bins).

breaks = seq(…) → tạo chuỗi các giá trị để chia khoảng:

floor(min(…)) → làm tròn xuống giá trị nhỏ nhất của Heart.Rate.

ceiling(max(…)) → làm tròn lên giá trị lớn nhất của Heart.Rate.

by = 10 → mỗi nhóm cách nhau 10 nhịp/phút.

right = FALSE → nhóm bao gồm giá trị bên trái, không bao gồm giá trị bên phải.

filter(!is.na(Heart.Rate.Group)) Loại bỏ các hàng mà Heart.Rate không rơi vào bất kỳ nhóm nào (NA). - group_by(Gender, Heart.Rate.Group) → nhóm dữ liệu theo Gender và nhóm Heart Rate.

summarise(Count = n()) → đếm số lượng từng nhóm (Count).

.groups = “drop” → bỏ grouping sau khi tính xong, để dataframe bình thường.

Sau bước này, long_HR sẽ có cấu trúc kiểu dài (long format) - ggplot(long_HR, aes(…))

x = Gender → trục x là giới tính.

y = Count → trục y là số lượng.

fill = Heart.Rate.Group → màu cột theo nhóm nhịp tim.

geom_col(position = position_dodge(width = 0.8), width = 0.7)

Vẽ cột (bar).

position_dodge → các cột nhóm theo Heart.Rate.Group đứng cạnh nhau, không chồng lên nhau.

width = 0.7 → độ rộng của mỗi cột.

geom_text(aes(label = Count), position = position_dodge(width = 0.8), vjust = -0.5, size = 3)

Thêm nhãn số lượng phía trên cột.

vjust = -0.5 → đẩy nhãn lên trên cột.

size = 3 → kích thước chữ.

scale_fill_viridis(discrete = TRUE, option = “A”, name = “Heart Rate (nhịp/phút)”)

Đặt bảng màu Viridis cho các nhóm nhịp tim.

discrete = TRUE → dữ liệu phân loại.

name = … → đặt tên legend.

labs(title = …, x = …, y = …)

Thêm tiêu đề và tên trục.

theme_minimal()

Giao diện tối giản.

theme(…)

legend.position = “top” → legend trên cùng.

axis.text.x = element_text(angle = 0, hjust = 0.5) → chữ trục x không xoay.

plot.title = element_text(size = 16, face = “bold”, hjust = 0.5) → tiêu đề in đậm, căn giữa.

Kết quả: Biểu đồ cho thấy phân bố nhịp tim (Heart Rate) ở ba mức (60–70, 70–80, 80–90 nhịp/phút) khá đồng bộ giữa nam và nữ. Các giá trị ở mỗi khoảng đều gần nhau cho cả hai giới, với nhóm nam có số lượng cao hơn nữ ở tất cả nhịp tim. Điều này giải thích rằng có sự khác biệt về nhịp tim trung bình giữa nam và nữ trong mẫu dữ liệu này không rõ ràng, và nhịp tim này phân bố đồng đều ở cả hai giới.

1.4.6 Đồ thi phân bố tỷ lệ Heart Rate theo Gender

library(ggplot2)
library(dplyr)
library(viridis)

data <- data %>%
  filter(!is.na(Heart.Rate)) %>%
  mutate(Heart.Rate.Group = cut_number(Heart.Rate, n = 6, labels = paste0("Nhóm ", 1:6)))

df_pie_HR <- data %>%
  group_by(Gender, Heart.Rate.Group) %>%
  summarise(Count = n(), .groups = "drop") %>%
  group_by(Gender) %>%
  mutate(Percent = round(Count / sum(Count) * 100, 1))

ggplot(df_pie_HR, aes(x = "", y = Percent, fill = Heart.Rate.Group)) +
  geom_col(color = "white", width = 1) +
  coord_polar(theta = "y") +
  facet_wrap(~ Gender) +
  geom_text(aes(label = paste0(Percent, "%")),
            position = position_stack(vjust = 0.5),
            size = 3) +
  scale_fill_viridis(discrete = TRUE, option = "C", name = "Heart Rate") +
  labs(title = "Tỷ lệ Heart Rate theo Gender") +
  theme_void() +
  theme(
    plot.title = element_text(face = "bold", hjust = 0.5, size = 16),
    strip.text = element_text(face = "bold", size = 12),
    legend.position = "bottom"
  )

Giải thích: - Nạp các thư viện cần thiết:

ggplot2: để vẽ biểu đồ.

dplyr: để xử lý dữ liệu (lọc, nhóm, tính toán).

viridis: để dùng bảng màu sinh động - Tiền xử lý dữ liệu:

filter(!is.na(Heart.Rate)): loại bỏ các dòng có giá trị thiếu (NA) trong biến Heart.Rate.

mutate(…): tạo thêm biến mới Heart.Rate.Group.

cut_number(Heart.Rate, n = 6, labels = paste0(“Nhóm”, 1:6)): chia biến Heart.Rate thành 6 nhóm có số lượng quan sát bằng nhau, gán nhãn “Nhóm 1”, “Nhóm 2”, … “Nhóm 6”. - Tạo bảng dữ liệu tóm tắt để vẽ biểu đồ:

group_by(Gender, Heart.Rate.Group): nhóm dữ liệu theo giới tính và nhóm nhịp tim.

summarise(Count = n()): đếm số người (Count) trong từng nhóm của mỗi giới tính.

group_by(Gender): nhóm lại theo giới tính để tính phần trăm.

mutate(Percent = round(Count / sum(Count) * 100, 1)): tính tỷ lệ phần trăm (%) từng nhóm nhịp tim trong từng giới tính. Làm tròn 1 chữ số thập phân.

  • ggplot(df_pie_HR, aes(x = ““, y = Percent, fill = Heart.Rate.Group)) +

ggplot(…): khởi tạo đồ thị với dữ liệu df_pie_HR.

aes(x = ““, y = Percent, fill = Heart.Rate.Group):

y = Percent: biểu diễn phần trăm từng nhóm.

fill = Heart.Rate.Group: tô màu theo nhóm nhịp tim. - geom_col(color = “white”, width = 1)

geom_col(…): tạo biểu đồ cột (bar chart), các cột màu trắng viền, chiều rộng 1. - coord_polar(theta = “y”): Biến biểu đồ cột thành biểu đồ tròn (pie chart).

theta = “y”: phần trăm nằm trên trục y sẽ trở thành góc trong hình tròn. - facet_wrap(~ Gender): Tạo 2 biểu đồ tròn riêng biệt cho từng giới tính (nam/nữ). - geom_text(aes(label = paste0(Percent, “%”)), position = position_stack(vjust = 0.5), size = 3) Thêm nhãn hiển thị tỷ lệ phần trăm trên từng lát biểu đồ:

label = paste0(Percent, “%”): hiển thị con số phần trăm.

position_stack(vjust = 0.5): canh giữa các lát.

size = 3: cỡ chữ của nhãn. - scale_fill_viridis(discrete = TRUE, option = “C”, name = “Heart Rate”) Áp dụng bảng màu viridis cho các nhóm nhịp tim, dễ nhìn và thẩm mỹ hơn.

discrete = TRUE: màu rời rạc cho từng nhóm.

name = “Heart Rate”: tiêu đề của chú giải màu (legend). - labs(title = “Tỷ lệ Heart Rate theo Gender”): Thêm tiêu đề cho biểu đồ. - theme_void(): Loại bỏ trục, nền và khung theme( - plot.title = element_text(face = “bold”, hjust = 0.5, size = 16), strip.text = element_text(face = “bold”, size = 12), legend.position = “bottom”

plot.title: tiêu đề in đậm, căn giữa, cỡ chữ 16.

strip.text: tiêu đề của từng giới (nam/nữ) in đậm, cỡ chữ 12.

legend.position = “bottom”: đưa chú giải màu xuống dưới biểu đồ.

Kết quả: Biểu đồ tỷ lệ nhịp tim phân theo giới tính cho thấy sự phân chia các nhóm nhịp tim là tương đối đồng đều giữa nam và nữ. Cả hai biểu đồ tròn nam và nữ đều có 6 nhóm nhịp tim với tỷ lệ dao động xung quanh là 13,5% đến gần 20% mỗi nhóm. Ở từng giới, các nhóm sử dụng tỷ lệ rất đồng đều, ở nữ các nhóm dao động từ 13,5% đến 19,7%, ở nam là từ 13,5% đến 19,8%. Không nhóm nào mạnh hơn hoặc quá ít so với các nhóm còn lại. Tỷ lệ giữa các nhóm nhịp tim gần giống nhau, cho thấy không có sự khác biệt nào về cấu trúc phân tích nhịp tim tổng hợp giữa bệnh nhân giữa nam và nữ.

1.4.7 Trực quan hóa tần số của Gender

library(ggplot2)
ggplot(data, aes(x = Gender, fill = Gender)) +
  geom_bar(color = "white", width = 0.6) +
  geom_text(stat = "count", aes(label = after_stat(count)), vjust = -0.3, size = 3.5, fontface = "bold") +
  labs(title = "Biểu đồ cột tần số theo giới tính", x = "Giới tính", y = "Tần số") +
  theme_minimal() +
  scale_fill_manual(values = c("Male" = "#5DADE2", "Female" = "#F5B7B1")) +
  theme(plot.title = element_text(size = 15, face = "bold", hjust = 0.5),
        axis.title = element_text(face = "bold"),
        axis.text = element_text(size = 11),
        legend.position = "none")

Giải thích: - ggplot(data, aes(x = Gender, fill = Gender))

Gọi hàm ggplot() để bắt đầu tạo biểu đồ từ bộ dữ liệu data.

aes() xác định biến thẩm mỹ (aesthetic mapping):

x = Gender: trục hoành là giới tính.

fill = Gender: tô màu các cột khác nhau theo giới tính.

  • geom_bar(color = “white”, width = 0.6)

Tạo biểu đồ cột (bar chart).

color = “white”: viền cột màu trắng.

width = 0.6: điều chỉnh độ rộng của các cột (nhỏ hơn 1 để các cột tách nhau nhẹ nhàng).

  • geom_text(stat = “count”, aes(label = after_stat(count)), …)

Hiển thị số lượng (tần số) trên đầu mỗi cột.

after_stat(count): lấy giá trị đếm tự động từ geom_bar.

vjust = -0.3: nâng chữ lên một chút trên cột.

size = 3.5, fontface = “bold”: chỉnh kích thước và kiểu chữ in đậm.

  • labs(…)

Thêm tiêu đề và nhãn trục:

title: tiêu đề biểu đồ.

x, y: nhãn trục hoành và trục tung.

  • theme_minimal()

Áp dụng giao diện tối giản, sạch đẹp, phù hợp cho báo cáo.

  • scale_fill_manual(values = c(“Male” = “#5DADE2”, “Female” = “#F5B7B1”))

Tùy chỉnh màu sắc thủ công:

Nam: xanh (#5DADE2)

Nữ: hồng nhạt (#F5B7B1) → Giúp biểu đồ trực quan và dễ phân biệt giới tính.

  • theme(…)

Tùy chỉnh giao diện biểu đồ:

plot.title: căn giữa, in đậm, tăng kích thước.

axis.title: chữ đậm cho nhãn trục.

axis.text: tăng kích thước chữ trục.

legend.position = “none”: ẩn chú giải (vì màu đã thể hiện rõ giới tính rồi).

Nhận xét:

Biến Gender gồm hai nhóm là Female và Male.

Dựa vào biểu đồ tần số theo giới tính, có thể thấy số lượng nam trong nghiên cứu mẫu là 54.972 người, nhiều hơn khá nhiều so với số lượng nữ là 45.028 người.

Tần số tích lũy đạt 100,000, khẳng định tính đầy đủ của bộ dữ liệu.

1.4.8 Trực quan hóa tần suất của Gender

library(ggplot2)
ggplot(bang_tansuat1, aes(x = "", y = TanSuat, fill = Gender)) +
  geom_bar(width = 1, stat = "identity", color = "white", linewidth = 0.5) + coord_polar("y", start = 0) + geom_text(aes(label = paste0(TanSuat, "%")), position = position_stack(vjust = 0.55),
            color = "gray10", size = 4, fontface = "bold") +  scale_fill_manual(values = c(
    "#A8DADC",
    "#F6BD60",  
    "#F28482",  
    "#84A59D",  
    "#B9FBC0"   
  )) + labs(title = "Biểu đồ tròn tần suất theo giới tính",
    fill = "Giới tính") +  theme_void() + theme(
    plot.title = element_text(size = 15, face = "bold", hjust = 0.5, color = "#333333"),
    legend.title = element_text(size = 12, face = "bold"),
    legend.text = element_text(size = 11),
    legend.position = "right")

Giải thích:

  • ggplot(bang_tansuat1, aes(x = ““, y = TanSuat, fill = Gender)) +:

Dữ liệu đầu vào là bang_tansuat1.

x = ““: dùng giá trị rỗng để gom tất cả các phần tử thành một vòng tròn duy nhất.

y = TanSuat: biểu diễn phần trăm tần suất.

fill = Gender: tô màu khác nhau cho từng giới tính.

_ geom_bar(width = 1, stat = “identity”, color = “white”, linewidth = 0.5)

Tạo các thanh bar (mỗi thanh đại diện cho 1 giới tính).

stat = “identity” → sử dụng giá trị thật (TanSuat) thay vì đếm số lượng.

width = 1 → thanh đầy đủ, tạo nên hình tròn sau khi chuyển đổi.

color = “white” → đường viền trắng giữa các phần.

linewidth = 0.5 → độ dày của viền.

  • coord_polar(“y”, start = 0)

Chuyển biểu đồ cột thành biểu đồ tròn (pie chart).

“y” → chuyển trục y thành góc quay quanh tâm.

start = 0 → điểm bắt đầu từ trục 0 độ (tùy chỉnh vị trí đầu tiên).

  • geom_text(aes(label = paste0(TanSuat, “%”)), position = position_stack(vjust = 0.55), color = “gray10”, size = 4, fontface = “bold”)

Thêm nhãn phần trăm ngay trên từng phần của hình tròn.

position_stack(vjust = 0.55) → căn giữa các nhãn theo chiều dọc.

paste0(TanSuat, “%”) → nối giá trị và ký hiệu “%”.

color, size, fontface → tùy chỉnh kiểu chữ.

  • scale_fill_manual(values = c( “#A8DADC”, # Màu pastel xanh “#F6BD60”, # Màu vàng nhạt “#F28482”, # Màu hồng “#84A59D”, # Màu xám xanh “#B9FBC0” # Màu xanh mint ))

Tùy chỉnh bảng màu thủ công.

Ở đây có 5 màu (dư thừa, ggplot chỉ dùng 2 màu đầu vì chỉ có 2 giới tính).

  • labs(title = “Biểu đồ tròn tần suất theo giới tính”, fill = “Giới tính”)

Thêm tiêu đề cho biểu đồ và nhãn chú thích (legend).

  • theme_void() + theme( plot.title = element_text(size = 15, face = “bold”, hjust = 0.5, color = “#333333”), legend.title = element_text(size = 12, face = “bold”), legend.text = element_text(size = 11), legend.position = “right”)

theme_void() → loại bỏ toàn bộ trục, nền, khung (đặc trưng của biểu đồ tròn).

Tùy chỉnh font và vị trí của tiêu đề và chú thích để biểu đồ trông gọn, chuyên nghiệp.

Nhận xét:

Nam giới chiếm 55,54%, là nhóm có tỷ lệ cao hơn trong tổng số người khảo sát.

Nữ giới chiếm 44,46%, thấp hơn nhưng vẫn chiếm gần một nửa mẫu.

Sự chênh lệch giữa hai giới không quá lớn, thể hiện phân bố giới tính tương đối cân bằng trong dữ liệu.

Kết luận: Cơ cấu giới tính cho thấy nam giới chiếm ưu thế nhẹ, nhưng mức độ cân đối giữa hai giới được duy trì tốt, phản ánh tính đa dạng và công bằng giới trong mẫu nghiên cứu.

1.4.9 Biểu đồ chiều cao theo giới tính và nhóm tuổi

ggplot(data, aes(x = Height, fill = Gender)) +  
  geom_histogram(position = "identity", alpha = 0.7, bins = 15) +  
  geom_vline(aes(xintercept = mean(Height)), color = "red", linetype = "dashed") +  
  facet_wrap(~AgeGroup) +  
  labs(title = "Phân phối chiều cao theo giới tính và nhóm tuổi") +  
  theme_light()

Giải thích: - ggplot(data, aes(x = Height, fill = Gender)) Khởi tạo khung đồ thị ggplot với dữ liệu đầu vào là data.

Trục hoành (X) biểu diễn biến Height – chiều cao của từng cá nhân.

Thuộc tính fill = Gender dùng để tô màu các cột theo giới tính (nam/nữ), giúp so sánh trực quan sự khác biệt giữa hai nhóm.

  • geom_histogram(position = “identity”, alpha = 0.7, bins = 15) Vẽ biểu đồ tần suất (histogram) thể hiện phân phối của biến chiều cao.

position = “identity”: cho phép các cột của hai nhóm giới tính chồng lên nhau, giúp so sánh trực tiếp sự phân bố giữa nam và nữ.

alpha = 0.7: điều chỉnh độ trong suốt, làm cho phần chồng lấn giữa hai nhóm vẫn nhìn thấy rõ ràng.

bins = 15: chia dữ liệu chiều cao thành 15 khoảng giá trị để thể hiện chi tiết hơn sự phân bố.

  • geom_vline(aes(xintercept = mean(Height)), color = “red”, linetype = “dashed”) Thêm đường thẳng đứng biểu diễn giá trị trung bình (mean) của chiều cao trong toàn bộ mẫu dữ liệu.

color = “red”: màu đỏ giúp đường trung bình nổi bật trên nền biểu đồ.

linetype = “dashed”: kiểu đường gạch đứt để dễ phân biệt với các đường hoặc cột khác.

  • facet_wrap(~AgeGroup): Chia nhỏ biểu đồ thành nhiều ô (facets), mỗi ô đại diện cho một nhóm tuổi (AgeGroup). Cách trình bày này cho phép so sánh phân phối chiều cao giữa các nhóm tuổi khác nhau, đồng thời vẫn phân biệt theo giới tính trong từng nhóm.

  • labs(title = “Phân phối chiều cao theo giới tính và nhóm tuổi”): Đặt tiêu đề cho biểu đồ, giúp người đọc hiểu rõ nội dung thể hiện: Biểu đồ mô tả phân phối chiều cao của các cá nhân, được phân tách theo giới tính và nhóm tuổi.

  • theme_light(): Áp dụng giao diện sáng (theme_light) với nền nhẹ và đường viền rõ Nhận xét: Ở tất cả các nhóm tuổi (16-20, 21-25, 26-30, 31+), nam giới luôn có chiều cao trung bình cao hơn nữ, có thể hiện qua hai màu phân biệt trong các biểu đồ. Phân chia chiều cao trong từng nhóm tuổi khá rộng rãi, không bị lệch về phía dưới hay quá cao ở bất kỳ nhóm tuổi nào.

1.4.10 Tần suất nhóm BMI từng giới tính

ggplot(data, aes(x=BMIcat, fill=Gender)) +
  geom_bar(position="dodge") +
  geom_text(stat="count", aes(label=..count..), position=position_dodge(0.9), vjust=-0.3) +
  labs(title="Tần suất nhóm BMI từng giới") +
  geom_hline(yintercept=mean(table(data$BMIcat)), color="purple", linetype="dashed") +
  theme_light()

Giải thích: - ggplot(data, aes(x = BMIcat, fill = Gender)) Khởi tạo khung đồ thị với dữ liệu data.

Trục hoành (x) là biến BMIcat – thể hiện các nhóm chỉ số khối cơ thể (BMI) như Gầy, Bình thường, Thừa cân,…

Thuộc tính fill = Gender giúp tô màu khác nhau theo giới tính (Nam/Nữ), hỗ trợ so sánh trực quan giữa hai nhóm.

  • geom_bar(position = “dodge”): Vẽ biểu đồ cột (bar chart) biểu diễn tần suất (số lượng người) trong từng nhóm BMI.

Tham số position = “dodge” làm các cột của hai giới tính đặt cạnh nhau, giúp dễ dàng so sánh từng nhóm BMI giữa nam và nữ.

  • geom_text(stat = “count”, aes(label = ..count..), position = position_dodge(0.9), vjust = -0.3) Thêm nhãn số lượng (count) ngay trên đầu mỗi cột.

stat = “count”: đếm số lượng quan sát trong từng nhóm.

position_dodge(0.9): đặt nhãn đúng vị trí với từng cột giới tính.

vjust = -0.3: điều chỉnh vị trí nhãn cao hơn cột một chút cho dễ nhìn.

  • labs(title = “Tần suất nhóm BMI từng giới”): Đặt tiêu đề cho biểu đồ, giúp người xem hiểu rằng biểu đồ thể hiện số lượng người trong từng nhóm BMI, được phân tách theo giới tính.

  • geom_hline(yintercept = mean(table(data$BMIcat)), color = “purple”, linetype = “dashed”): Thêm đường ngang màu tím (purple) biểu diễn giá trị trung bình tần suất của các nhóm BMI. Giúp người xem so sánh từng cột với mức trung bình tổng thể.

linetype = “dashed”: đường gạch đứt để dễ phân biệt.

  • theme_light(): Áp dụng giao diện sáng

Kết quả: Biểu đồ cho thấy tần số nhóm BMI (Béo phì, Bình thường, Gầy, Thừa cân) phân theo giới tính (Nam, Nữ) trong bộ dữ liệu bệnh nhân. Ở nhóm béo phì , nam giới có số lượng vượt trội (15.534) so với nữ (12.775), cho thấy tỷ lệ nam béo phì trong mẫu cao hơn khẳng định nữ. Nhóm bình thường khá cân bằng giữa hai giới, với nữ là 7.947 và nam là 9.601, chênh lệch không lớn. Ở nhóm gầy , nam (11.810) cũng nhỉnh hơn nữ (9.551), cho thấy tỷ lệ nam bị gầy nhiều hơn nữ trong bộ dữ liệu này. Nhóm thừa cân là nhóm ít thành viên nhất ở cả hai giới, với nữ là 3,456 và nam là 4,216. Nhìn chung, nam thường có số lượng cao hơn nữ ở tất cả các nhóm BMI, đặc biệt ở nhóm béo phì và gầy.

1.4.11 Trực quan hóa trung bình nhịp tim theo nhóm chiều cao

ggplot(result_hr_height, aes(x=HeightGroup, y=mean_HR, fill=HeightGroup)) +
  geom_bar(stat="identity", alpha=0.8) +                                # Layer 1: Bar trung bình từng nhóm HeightGroup
  geom_errorbar(aes(ymin=mean_HR-se_HR, ymax=mean_HR+se_HR), width=0.18, color="black", size=1.1) + # Layer 2: Error bar (sai số chuẩn)
  geom_text(aes(label=round(mean_HR,1)), vjust=-0.5, color="black", size=2, fontface="bold") +      # Layer 3: Nhãn số liệu trên mỗi cột
  labs(title="Trung bình nhịp tim theo nhóm chiều cao", x="Nhóm chiều cao ", y="Nhịp tim trung bình (bpm)") + # Layer 4: Tiêu đề, nhãn trục
  scale_fill_brewer(palette="Pastel1") +                                                            # Layer 5: Màu pastel nhẹ dịu
  theme_minimal()

Giải thích: - ggplot(result_hr_height, aes(x = HeightGroup, y = mean_HR, fill = HeightGroup)) Tạo khung đồ thị sử dụng dữ liệu result_hr_height,

Trục hoành (x) là các nhóm chiều cao (HeightGroup).

Trục tung (y) là nhịp tim trung bình (mean_HR).

Thuộc tính fill = HeightGroup giúp mỗi nhóm có một màu riêng biệt.

  • geom_bar(stat = “identity”, alpha = 0.8): Vẽ biểu đồ cột, trong đó chiều cao cột thể hiện trực tiếp giá trị mean_HR.

stat = “identity”: lấy giá trị thật từ dữ liệu (không tính tần suất).

alpha = 0.8: độ trong suốt nhẹ giúp biểu đồ mềm mại, dễ nhìn.

  • geom_errorbar(aes(ymin = mean_HR - se_HR, ymax = mean_HR + se_HR), width = 0.18, color = “black”, size = 1.1) Thêm thanh sai số chuẩn (error bar) cho từng cột.

ymin và ymax thể hiện khoảng dao động của nhịp tim trung bình ± sai số chuẩn (mean ± SE).

width = 0.18: độ rộng của thanh ngang.

color = “black”, size = 1.1: làm nổi bật đường sai số để dễ quan sát.

  • geom_text(aes(label = round(mean_HR, 1)), vjust = -0.5, color = “black”, size = 4, fontface = “bold”) Hiển thị nhãn số liệu trung bình nhịp tim trên đầu mỗi cột.

Làm tròn một chữ số thập phân (round(mean_HR, 1)).

vjust = -0.5: đặt nhãn hơi cao hơn đầu cột.

fontface = “bold”: chữ in đậm giúp nổi bật giá trị.

  • labs(title = “Trung bình nhịp tim theo nhóm chiều cao”, x = “Nhóm chiều cao”, y = “Nhịp tim trung bình (bpm)”): Đặt tiêu đề và nhãn trục, giúp người xem hiểu nội dung biểu đồ.

  • scale_fill_brewer(palette = “Pastel1”): Áp dụng bảng màu Pastel1 — các tông màu nhẹ, dễ phân biệt giữa các nhóm chiều cao.

  • theme_minimal(): Dùng giao diện tối giản

Kết quả: Biểu đồ có thể biểu thị giá trị nhịp tim trung bình (bpm) theo từng nhóm chiều cao của bệnh nhân. Các nhóm chiều cao bao gồm: Cao, Rất cao, Thấp và Trung bình. Nhịp tim trung bình của các nhóm chiều cao đều rất gần nhau: nhóm cao: 79,5 nhịp/phút, nhóm rất cao: 79.6 bpm, nhóm thấp: 79,4 bpm, nhóm trung bình: 79.6 bpm. Khoảng chênh lệch giữa các nhóm chỉ 0,2 bpm , cho thấy chiều cao không có ảnh hưởng đến nhịp trung bình ở bệnh nhân trong mẫu này.

1.4.12 Biểu đồ tuổi, BMIcat, Giới tính

ggplot(data, aes(x=Age, fill=BMIcat)) +
  geom_histogram(alpha=0.6, bins=12) +
  facet_wrap(~Gender) +
  geom_vline(aes(xintercept=mean(Age,na.rm=TRUE)), color="darkgreen", linetype="dotted") +
  labs(title="Phân phối tuổi, BMIcat, gender") +
  theme_light()

Giải thích: - ggplot(data, aes(x = Age, fill = BMIcat)) Tạo khung đồ thị từ dữ liệu data,

Trục hoành (x) là biến Age (tuổi).

Thuộc tính fill = BMIcat dùng để tô màu theo nhóm chỉ số BMI, giúp phân biệt các nhóm thể trạng (Gầy, Bình thường, Thừa cân,…).

  • geom_histogram(alpha = 0.6, bins = 12): Vẽ biểu đồ tần suất (histogram) thể hiện phân bố độ tuổi của mẫu dữ liệu.

bins = 12: chia dữ liệu thành 12 khoảng tuổi để hiển thị chi tiết.

alpha = 0.6: tạo độ trong suốt cho cột, giúp các phần màu chồng lên nhau vẫn dễ nhìn.

  • facet_wrap(~Gender): Chia nhỏ biểu đồ thành hai phần riêng biệt theo giới tính (nam và nữ).

  • geom_vline(aes(xintercept = mean(Age, na.rm = TRUE)), color = “darkgreen”, linetype = “dotted”) Thêm đường thẳng đứng tại giá trị tuổi trung bình của toàn bộ mẫu.

color = “darkgreen”: màu xanh đậm giúp nổi bật.

linetype = “dotted”: dạng chấm chấm để phân biệt rõ ràng với các cột dữ liệu.

  • labs(title = “Phân phối tuổi, BMIcat, gender”): Đặt tiêu đề cho biểu đồ, thể hiện rõ nội dung Phân bố độ tuổi theo nhóm BMI và giới tính.

  • theme_light(): Áp dụng chủ đề sáng (light theme)

Kết quả: Ở cả nam và nữ, nhóm “Béo phì” sử dụng tỷ lệ lớn nhất trong hầu hết các nhóm tuổi, tiếp theo là nhóm “Bình thường”. Nhóm “Gầy” và “Thừa cân” sử dụng tỷ lệ trọng lượng nhỏ hơn và phân bố đều qua các mức tuổi. Số lượng theo từng nhóm tuổi khá đồng đều, không có mức độ tuổi nào nổi bật về tỷ lệ BMI của nhóm. So sánh hai giới, nam có số trường hợp “Béo phì” và “Bình thường” nhỉnh hơn nữ ở cùng độ tuổi. Điều này cho thấy tỷ lệ béo phì sử dụng ưu thế ở cả hai giới trong khảo sát tuổi, trong khi tỷ lệ gầy và thừa cân không khác biệt nhiều giữa các nhóm tuổi cũng như giới tính.

1.4.13 Mật độ chiều cao chuẩn hóa theo BMIcat

ggplot(data, aes(x=HeightZ, fill=BMIcat)) +
  geom_density(alpha=0.5) +
  facet_wrap(~BMIcat) +
  geom_vline(aes(xintercept=mean(HeightZ,na.rm=TRUE)), color="red") +
  labs(title="Chiều cao chuẩn hoá các nhóm BMI") +
  theme_classic()

Giảr thích: - ggplot(data, aes(x = HeightZ, fill = BMIcat))

Tạo khung đồ thị từ bộ dữ liệu data.

Trục hoành (x) là HeightZ — tức chiều cao đã được chuẩn hoá (z-score).

Thuộc tính fill = BMIcat dùng để tô màu phân biệt các nhóm chỉ số BMI (ví dụ: Gầy, Bình thường, Thừa cân, Béo phì).

  • geom_density(alpha = 0.5): Vẽ biểu đồ mật độ xác suất (density plot). Đường cong biểu thị phân bố chiều cao của từng nhóm BMI.

alpha = 0.5 tạo độ trong suốt 50%, giúp các vùng màu chồng nhau vẫn thấy được cả hai lớp dữ liệu.

  • facet_wrap(~BMIcat): Tách biểu đồ thành nhiều ô nhỏ (facet), mỗi ô tương ứng với một nhóm BMIcat. Mỗi nhóm được thể hiện bằng một biểu đồ mật độ riêng biệt.

  • geom_vline(aes(xintercept = mean(HeightZ, na.rm = TRUE)), color = “red”)

Thêm đường thẳng đứng tại vị trí giá trị trung bình của HeightZ.

color = “red” để nổi bật.

na.rm = TRUE bỏ qua các giá trị bị thiếu.

Đường đỏ biểu thị giá trị trung bình chuẩn hóa (mean = 0), giúp đối chiếu xem nhóm nào cao hơn hoặc thấp hơn mức trung bình.

  • labs(title = “Chiều cao chuẩn hoá các nhóm BMI”): Đặt tiêu đề cho biểu đồ, nêu rõ mục tiêu trực quan hóa.

  • theme_classic(): Dùng giao diện đơn giản, nền trắng, khung đen giúp biểu đồ dễ nhìn

Nhận xét biểu đồ Nhóm Béo phì: Đa phần các cá nhân có chiều cao chuẩn hóa thấp hơn trung bình (phía bên trái vạch dọc đỏ), cho thấy nhóm này tập trung nhiều ở những người có chiều cao thấp hơn so với mặt bằng chung.

Nhóm Bình thường và Thừa cân: Phân phối chiều cao chuẩn hóa khá đều đặn, không biết rõ về phía dưới hay cao, mật độ rung tương đối đều hai bên vạch dọc. Điều này cho thấy nhóm này bao gồm cả những người ở tầng dưới, nhiều chiều cấu trúc khác nhau.

Nhóm Gầy: Mật độ chiều cao chuẩn hóa hóa tập trung mạnh về phía cao hơn trung bình (bên phải vạch đỏ), cho thấy nhóm người gầy thường có chiều cao cao hơn mặt bằng chung trong dữ liệu.

Đường dọc màu đỏ: Đây là giá trị chuẩn hóa 0 (trung bình), giúp làm nổi bật sự trôi về phía dưới hay cao của từng nhóm BMI.

Biểu đồ cho thấy đặc điểm chiều cao liên kết tới thể hiện BMI—người béo phì thường thấp hơn trung bình, người gầy thường cao hơn trung bình. Nhóm bình thường và cân bằng có phân bố chiều cao gần với trung bình và đa dạng hơn.

1.4.14 HighRisk theo nhóm tuổi

ggplot(data, aes(x=AgeGroup, fill=as.factor(HighRisk))) +
  geom_bar(position="stack") +
  labs(title="HighRisk theo tuổi") +
  facet_wrap(~Gender) +
  geom_text(stat="count", aes(label=..count..), position=position_stack(vjust=0.5), angle = 90) +
  theme_bw()

Giải thích: - ggplot(data, aes(x = AgeGroup, fill = as.factor(HighRisk))): Tạo khung dữ liệu cho biểu đồ.

Trục X là biến AgeGroup — nhóm tuổi của người tham gia (ví dụ: “<20”, “20–29”, “30–39”, …).

fill = as.factor(HighRisk): Tô màu cột theo giá trị của biến HighRisk (mức độ rủi ro cao/thấp).

as.factor() được dùng để đảm bảo R hiểu đây là biến phân loại (categorical).

→ Mục đích: xem tần suất người có hoặc không có nguy cơ cao (HighRisk) trong từng nhóm tuổi.

geom_bar(position = “stack”)

Vẽ biểu đồ cột chồng (stacked bar chart).

Mỗi cột đại diện cho một nhóm tuổi (AgeGroup), và trong cột đó, phần màu thể hiện số người thuộc từng mức HighRisk (ví dụ: 0 = thấp, 1 = cao).

→ Dễ dàng so sánh tỷ lệ rủi ro cao/thấp giữa các nhóm tuổi.

labs(title = “HighRisk theo tuổi”)

Đặt tiêu đề cho biểu đồ, cho biết nội dung là “Phân bố mức độ rủi ro theo nhóm tuổi”.

facet_wrap(~Gender)

Tách biểu đồ thành hai ô riêng biệt theo giới tính (Gender).

Một ô cho Nam, một ô cho Nữ (hoặc các giới khác nếu có).

→ Giúp ta so sánh mô hình rủi ro giữa các giới tính.

geom_text(stat = “count”, aes(label = ..count..), position = position_stack(vjust = 0.5))

Thêm nhãn số lượng (count) ngay bên trong từng phần màu của cột.

stat = “count” tự động đếm số lượng quan sát trong mỗi nhóm.

vjust = 0.5 căn giữa chữ theo chiều dọc trong cột.

→ Người xem có thể biết rõ số lượng từng mức HighRisk trong mỗi nhóm tuổi mà không cần nhìn trục Y.

theme_bw(): Sử dụng giao diện nền trắng, khung đen

Nhận xét biểu đồ Cấu trúc/Ý nghĩa: Biểu đồ chia làm hai phần: nữ và nam, mỗi phần có các cột phù hợp với nhóm tuổi (16-20, 21-25, 26-30, 31+). Mỗi cột có thể hiện số lượng cá nhân thuộc nhóm nguy cơ cao ở từng nhóm tuổi/giới tính, thông số được xác định rõ trong mỗi cột để dễ so sánh.

Xu hướng nổi bật ở nam:

Số lượng nguy cơ cao tăng mạnh ở các nhóm tuổi trung niên (21-25 và 26-30), đạt đỉnh ở 21-25 (12.158) và 26-30 (12.124).

Nhóm tuổi nhỏ nhất (16-20) và lớn nhất (31+) có số lượng thấp hơn đáng kể, lần lượt là 7.180 và 9.699.

Xu hướng nổi bật ở nữ:

Cũng tăng ở hai nhóm tuổi trung niên (21-25: 9.927, 26-30: 10.098) Nhưng thấp hơn nam, cho thấy nhóm nam có tỷ lệ nguy cơ cao lớn hơn ở cùng độ tuổi.

Nhóm nhỏ tuổi nhất (16-20: 5.862) và lớn tuổi nhất (31+: 7.842) thấp hơn sắc.

Ý nghĩa: Phân tích này cho thấy nguy cơ thuộc nhóm “HighRisk” có xu hướng tập trung cao ở các nhóm tuổi trung niên, và nam giới có số lượng nguy cơ cao vượt trội so với nữ cùng độ tuổi.

Kết luận: Biểu đồ giúp xác định nhanh các nhóm đối tượng cần ưu tiên phòng hoặc cẩn thận, nhất là nam giới từ 21 đến 30 tuổi, đồng thời cũng gợi ý về xu hướng sức khỏe hoặc hành vi nguy cơ thay đổi theo từng nhóm tuổi và giới

1.4.15 Phân bố nhóm chiều cao theo giới tính

ggplot(data, aes(x=HeightGroup, fill=Gender)) +
  geom_bar(position="stack", alpha=0.85) +                           # Layer 1: Cột xếp chồng theo Gender
  geom_text(stat="count", aes(label=..count..), position=position_stack(vjust=0.5), color="white", size=4) + # Layer 2: Nhãn trắng bên trong mỗi phần cột
  scale_fill_brewer(palette="Pastel2") +                             # Layer 3: Màu pastel
  labs(title="Phân bố nhóm chiều cao theo giới tính", x="Chiều cao", y="Số lượng") + # Layer 4: Tiêu đề và trục
  theme_minimal()                                                    # Layer 5: Giao diện sạch hiện đại

Giải thích: - ggplot(data, aes(x = HeightGroup, fill = Gender))

Khởi tạo biểu đồ từ bộ dữ liệu data.

Trục X là biến HeightGroup — tức nhóm tạng người hoặc nhóm chiều cao (ví dụ: “Thấp”, “Trung bình”, “Cao”, “Rất cao”).

fill = Gender: tô màu phần cột theo giới tính (Nam, Nữ, …).

  • geom_bar(position = “stack”, alpha = 0.85): Vẽ biểu đồ cột chồng (stacked bar chart). Các phần trong cột được xếp chồng lên nhau theo giới tính.

alpha = 0.85: làm cột hơi trong suốt nhẹ, giúp màu dịu và hài hòa hơn.

  • geom_text(stat = “count”, aes(label = ..count..), position = position_stack(vjust = 0.5), color = “white”, size = 4)

Thêm nhãn số lượng (count) vào giữa các phần màu trong cột.

stat = “count”: tự động đếm số quan sát cho từng nhóm.

position_stack(vjust = 0.5): căn giữa chữ theo chiều dọc trong phần cột.

color = “white”: chữ màu trắng để nổi bật trên nền màu pastel.

size = 4: cỡ chữ vừa phải, dễ đọc.

  • scale_fill_brewer(palette = “Pastel2”): Dùng bảng màu Pastel2 từ thư viện RColorBrewer.

  • labs(title = “Phân bố tạng người theo giới tính”, x = “Tạng người”, y = “Số lượng”)

title: đặt tiêu đề cho biểu đồ.

x: nhãn cho trục hoành (HeightGroup → “Tạng người”).

y: nhãn cho trục tung (số lượng cá nhân).

  • theme_minimal(): Áp dụng giao diện tối giản (minimal)

Nhận xét: Biểu đồ có thể biểu thị số lượng theo nhóm chiều cao (Cao, Rất cao, Thấp, Trung bình) chia theo giới tính nam và nữ cho thấy sự phân bố rất đồng đều giữa hai giới trong từng nhóm. Cụ thể, ở mỗi nhóm chiều cao, số lượng mẫu Nam luôn dao động xung quanh 10.100–10.400 và số lượng mẫu Nữ ổn định khoảng 8.300–8.500. Số liệu này phản ánh rằng tỷ lệ giữa Nam và Nữ ở từng nhóm chiều cao không chênh lệch đáng kể

1.4.16 Biểu đồ đường trung bình nhịp tim theo nhóm chiều cao

library(dplyr)

result_height_hr <- data %>%
  group_by(HeightGroup) %>%
  summarise(mean_HR = mean(Heart.Rate, na.rm = TRUE), .groups = 'drop')
ggplot(result_height_hr, aes(x = HeightGroup, y = mean_HR, group = 1)) +
  geom_line(color = "#F2452D", size = 2) +
  geom_point(color = "blue", size = 4) +
  geom_text(aes(label = round(mean_HR, 1)), vjust = -1, color = "black", size = 4, angle = 90) +
  labs(title = "Xu hướng nhịp tim trung bình theo nhóm chiều cao",
       x = "Nhóm chiều cao", y = "Nhịp tim trung bình") +
  theme_minimal(base_size = 14) +
  scale_y_continuous(expand = expansion(mult = c(0.05, 0.15)))

Giải thích: - aes(x = HeightGroup, y = mean_HR, group = 1)

Trục X: nhóm chiều cao.

Trục Y: nhịp tim trung bình của từng nhóm.

group = 1: giúp nối các điểm lại bằng đường (line), vì dữ liệu tóm tắt không có biến nhóm riêng.

  • geom_line(color = “#E2452D”, size = 2): Vẽ đường nối các điểm trung bình theo nhóm chiều cao.

Màu đỏ cam (#E2452D) giúp nổi bật xu hướng.

size = 2: độ dày của đường để dễ nhìn.

  • geom_point(color = “blue”, size = 4): Thêm các điểm đánh dấu từng nhóm trung bình trên đường xu hướng.

Màu xanh lam để tương phản với đường màu đỏ, dễ phân biệt.

  • geom_text(aes(label = round(mean_HR,1)), vjust = -1, color = “darkred”, size = 4)

Hiển thị nhãn giá trị trung bình phía trên mỗi điểm.

Làm tròn 1 chữ số thập phân (round(mean_HR,1)).

Màu chữ đỏ đậm để nổi bật.

vjust = -1: đẩy chữ lên trên đầu điểm.

  • labs(title = “…”, x = “…”, y = “…”)

title: đặt tiêu đề cho biểu đồ → “Xu hướng nhịp tim trung bình theo nhóm chiều cao”.

x, y: đặt nhãn trục cho dễ hiểu.

  • theme_minimal(base_size = 14): Giao diện tối giản, loại bỏ đường viền và lưới rối mắt.

base_size = 14: tăng kích thước chữ, giúp biểu đồ rõ ràng

Nhận xét: Tất cả các nhóm chiều cao đều có nhịp tim trung bình rất sát: nhóm “Cao” là 74,4, “Rất cao” là 74,5, “Thấp” là 74,4, và “Trung bình” là 74,6. Khoảng dao động chỉ 0,2 nhịp/phút giữa các nhóm cho thấy chiều cao gần như không ảnh hưởng nhiều đến nhịp tim trung bình ở bệnh nhân trong mẫu nghiên cứu.

1.4.17 Biểu đồ chấm nhịp tim theo nhóm tuổi

result_dot <- data %>% group_by(AgeGroup) %>% summarise(mean_HR=mean(Heart.Rate, na.rm=TRUE), .groups='drop')
ggplot(result_dot, aes(x=AgeGroup, y=mean_HR, color=AgeGroup)) +
  geom_point(size=7, alpha=0.7) +
  geom_text(aes(label=round(mean_HR,1)), vjust=-1, size=2.5, color="black") +
  scale_color_brewer(palette="Set2") +
  labs(title="Trung bình nhịp tim từng nhóm tuổi") +
  theme_classic()

Nhận xét: - summarise(mean_HR = mean(Heart.Rate, na.rm = TRUE)): tính giá trị trung bình của nhịp tim (Heart.Rate) cho mỗi nhóm tuổi.

  • aes(x = AgeGroup, y = mean_HR, color = AgeGroup): Thiết lập trục hoành là nhóm tuổi, trục tung là giá trị nhịp tim trung bình.

color = AgeGroup: mỗi nhóm tuổi có một màu riêng biệt để dễ phân biệt.

  • geom_point(size = 7, alpha = 0.7): Tạo biểu đồ điểm hiển thị giá trị trung bình của từng nhóm.

size = 7: tăng kích thước điểm để rõ ràng hơn.

alpha = 0.7: tạo độ trong suốt nhẹ, giúp đồ thị mềm mại hơn.

  • geom_text(aes(label = round(mean_HR,1)), vjust = -1, size = 4, color = “black”)

Thêm nhãn số liệu hiển thị giá trị trung bình trên mỗi điểm.

round(mean_HR, 1): làm tròn 1 chữ số thập phân.

vjust = -1: đặt nhãn phía trên điểm.

Màu chữ đen giúp dễ đọc trên nền màu pastel.

  • scale_color_brewer(palette = “Set2”): Áp dụng bảng màu pastel “Set2” của R Color Brewer → nhẹ nhàng, dễ nhìn, thích hợp cho biểu đồ so sánh nhóm.

  • labs(title = “Trung bình nhịp tim từng nhóm tuổi”): Đặt tiêu đề cho biểu đồ, giúp người đọc hiểu rõ ý nghĩa.

  • theme_classic(): Giao diện cổ điển, đơn giản với nền trắng và trục đen rõ ràng

Nhận xét: Trục hoành (x – AgeGroup): chia dữ liệu thành các nhóm tuổi: 16–20, 21–25, 26–30, 31+.

Trục tung (y – mean_HR): biểu diễn giá trị trung bình nhịp tim của từng nhóm tuổi.

Con số trên mỗi chấm (74.4 – 74.6): hiển thị giá trị trung bình nhịp tim đã được làm tròn một chữ số thập phân.

Các nhóm tuổi 16–20 và 31+ có nhịp tim trung bình cao nhất (khoảng 74.6 bpm), trong khi nhóm 21–25 và 26–30 có giá trị thấp hơn một chút (~74.4 bpm). Như vậy, sự khác biệt giữa các nhóm không lớn, cho thấy nhịp tim trung bình khá ổn định theo độ tuổi.

1.4.18 Bản đồ nhiệt nhóm tuổi và BMIcat

hm_counts <- as.data.frame(table(data$AgeGroup, data$BMIcat))
colnames(hm_counts) <- c("AgeGroup", "BMIcat", "Freq")
ggplot(hm_counts, aes(x = AgeGroup, y = BMIcat, fill = Freq)) +
  geom_tile(color = "white") +                                        # Layer 1: Ô vuông bản đồ nhiệt
  scale_fill_gradient(low = "white", high = "tomato") +               # Layer 2: Dải màu nhiệt
  geom_text(aes(label = Freq), color = "black", size = 5, fontface="bold") + # Layer 3: Đặt số đếm vào giữa từng ô
  labs(title = "Bản đồ nhiệt phân bố tuổi và thể trạng", 
       x = "Nhóm tuổi", y = "Nhóm BMI") +                             # Layer 4: Tiêu đề, trục
  theme_minimal(base_size = 9)

Giải thích: - hm_counts <- as.data.frame(table(data\(AgeGroup, data\)BMIcat)): Chuyển bảng tần suất giữa hai biến AgeGroup (nhóm tuổi) và BMIcat (nhóm thể trạng) thành dạng data frame để dễ xử lý bằng ggplot2.

  • colnames(hm_counts) <- c(“AgeGroup”, “BMIcat”, “Freq”): Đặt lại tên cột cho bảng dữ liệu:

AgeGroup: nhóm tuổi

BMIcat: nhóm BMI

Freq: tần suất (số lượng người trong từng cặp nhóm).

  • ggplot(hm_counts, aes(x = AgeGroup, y = BMIcat, fill = Freq)): Tạo khung biểu đồ sử dụng ggplot(), trong đó:

AgeGroup làm trục hoành (x-axis)

BMIcat làm trục tung (y-axis)

Freq quyết định mức độ tô màu của từng ô.

  • geom_tile(color = “white”): Tạo các ô vuông (tile) thể hiện từng cặp giá trị của hai biến, color = “white” giúp tạo đường viền trắng phân tách các ô, giúp biểu đồ rõ ràng hơn.

  • scale_fill_gradient(low = “white”, high = “tomato”): Tạo dải màu chuyển tiếp cho các ô:

Màu trắng biểu thị tần suất thấp.

Màu đỏ cà chua (tomato) biểu thị tần suất cao => Ô càng đỏ nghĩa là nhóm đó có số lượng người càng nhiều.

  • geom_text(aes(label = Freq), color = “black”, size = 5, fontface = “bold”): Thêm chữ số tần suất vào giữa từng ô:

label = Freq: hiển thị giá trị tần suất.

color = “black”: chữ màu đen để dễ đọc.

fontface = “bold”: chữ in đậm giúp nổi bật trên nền màu.

  • labs(title = “Bản đồ nhiệt phân bố tuổi và thể trạng”, x = “Nhóm tuổi”, y = “Nhóm BMI”): Đặt tiêu đề và nhãn cho hai trục:

Tiêu đề: “Bản đồ nhiệt phân bố tuổi và thể trạng”.

Trục X: “Nhóm tuổi”.

Trục Y: “Nhóm BMI”.

  • theme_minimal(base_size = 14): Áp dụng giao diện tối giản (minimal), loại bỏ các chi tiết rườm rà giúp biểu đồ gọn gàng, dễ nhìn. base_size = 14 giúp chữ lớn, rõ ràng hơn.

Nhận xét biểu đồ Theo chiều ngang là các nhóm tuổi (16-20, 21-25, 26-30, 31+), cho thấy mức độ phổ biến của từng trạng thái theo từng độ tuổi. Dọc theo trục đứng là nhóm BMI (Thừa cân, Gầy, Bình thường, Béo phì).

Nhóm tuổi 21-25 có số lượng cá nhân “Béo phì” cao nhất (8467 người), tiếp theo là nhóm Gầy (6280) và Bình thường cũng cao (5109). Tương quan này tăng dần ở nhóm tuổi lớn hơn (31+) đối với mọi trạng thái.

Nhóm “Thừa cân” luôn là nhóm có tần suất thấp nhất ở mọi lứa tuổi, đặc biệt nhóm 16-20 chỉ có 1345 trường hợp, thấp nhất toàn bảng. Ngược lại, thừa cân tăng nhẹ ở các nhóm tuổi trung niên.

Bản đồ nhiệt cho thấy phần lớn người được khảo sát ở nhóm tuổi trẻ (21-30) có xu hướng thể hiện “Béo phì” xuất hiện mạnh mẽ hơn, có thể liên kết tới đường sống, dinh dưỡng theo từng độ tuổi.

1.4.19 Biểu đồ hộp HeartRate theo BMIcat

ggplot(data, aes(x=BMIcat, y=Heart.Rate, fill=BMIcat)) +
  geom_boxplot(alpha=0.8) +
  stat_summary(fun=mean, geom="point", shape=23, color="black", fill="yellow", size=3) +
  labs(title="Nhịp tim theo nhóm thể trạng BMIcat") +
  scale_fill_brewer(palette="Pastel2") +
  theme_minimal()

Giải thích: - ggplot(data, aes(x = BMIcat, y = Heart.Rate, fill = BMIcat)): Tạo khung đồ thị sử dụng dữ liệu data, với: BMIcat (nhóm thể trạng) làm trục hoành (x).

Heart.Rate (nhịp tim) làm trục tung (y).

fill = BMIcat: tô màu từng hộp boxplot theo nhóm BMI khác nhau.

  • geom_boxplot(alpha = 0.8): Vẽ biểu đồ hộp (boxplot) thể hiện phân bố nhịp tim trong từng nhóm BMI.

Hộp biểu thị trung vị, tứ phân vị (Q1, Q3) và giá trị ngoại lai.

alpha = 0.8: tạo độ trong suốt nhẹ để màu sắc dịu hơn, dễ nhìn.

  • stat_summary(fun = mean, geom = “point”, shape = 23, color = “black”, fill = “yellow”, size = 3): Thêm dấu biểu diễn giá trị trung bình của nhịp tim cho mỗi nhóm:

fun = mean: tính giá trị trung bình.

geom = “point”: biểu diễn bằng điểm.

shape = 23: hình thoi rỗng.

fill = “yellow”: màu vàng để nổi bật.

color = “black”: viền đen dễ nhận diện.

size = 3: kích thước điểm vừa phải.

  • labs(title = “Nhịp tim theo nhóm thể trạng BMIcat”): Thêm tiêu đề cho biểu đồ: “Nhịp tim theo nhóm thể trạng BMIcat”.

  • scale_fill_brewer(palette = “Pastel2”): Dùng bảng màu Pastel2 (màu nhẹ nhàng, dễ phân biệt giữa các nhóm BMI).

  • theme_minimal(): Áp dụng giao diện tối giản

Nhận xét biểu đồ Mỗi nhóm BMIcat có một boxplot riêng, có thể thực hiện phân tích và tốc độ trung bình (ký hiệu vạch ngang trong hộp). Nhóm Bình thường, Béo phì, Gầy, Thừa cân đều có giá trị trung bình khá tương đồng, dao động xung quanh trình độ ~70–75 nhịp/phút, cho thấy không có sự khác biệt rõ ràng về nhịp tim trung vị giữa các nhóm có thể trạng thái.

Dải giá trị (phạm vi) rộng: Ở mọi nhóm, khoảng cách giữa giá trị nhỏ nhất và lớn nhất khá lớn (từ khoảng 60 đến gần 90 nhịp/phút), chỉ ra động nhịp nhịp trong từng nhóm là đáng kể.

Median và Mean (hình thoi): Hình thoi vàng biểu hiện giá trị trung bình, gần trung vị ở mọi nhóm BMI, cho phân phối dữ liệu không bị trôi mạnh (độ lệch nhỏ).

-> Kết quả này có thể cho thấy rằng BMI có thể không ảnh hưởng đến nhịp tim trung bình, dù vẫn tồn tại độ lệch nhỏ nhất.

1.4.20 Biểu đồ mật độ theo nhóm giới tính:

ggplot(data, aes(x = BMI, fill = Gender)) +
  geom_density(alpha = 0.6) +
  facet_wrap(~Gender, ncol = 1) +
  scale_fill_brewer(palette = "Pastel1") +
  labs(title = "Phân phối BMI theo giới", x = "BMI", y = "Density") +
  theme_minimal()

Giải thích: - ggplot(data, aes(x = BMI, fill = Gender)): Tạo khung đồ thị từ dữ liệu data, với:

BMI làm trục hoành (x), biểu diễn chỉ số khối cơ thể.

fill = Gender: tô màu đường mật độ theo giới tính, giúp phân biệt giữa nam và nữ.

  • geom_density(alpha = 0.6): Vẽ biểu đồ mật độ (density plot) thể hiện phân bố giá trị BMI trong dữ liệu.

Đường cong cho biết mức độ tập trung của dữ liệu ở các khoảng giá trị khác nhau.

alpha = 0.6: tạo độ trong suốt nhẹ để các phần chồng lên vẫn nhìn rõ.

  • facet_wrap(~Gender, ncol = 1) Chia biểu đồ thành hai khung riêng biệt theo giới tính, mỗi giới một biểu đồ xếp theo cột (1 cột, nhiều hàng). Giúp dễ so sánh trực quan giữa phân bố BMI của nam và nữ mà không bị chồng dữ liệu.

  • scale_fill_brewer(palette = “Pastel1”): Sử dụng bảng màu Pastel1, tông màu dịu nhẹ và hài hòa, giúp biểu đồ dễ nhìn.

  • labs(title = “Phân phối BMI theo giới”, x = “BMI”, y = “Density”): Thêm tiêu đề “Phân phối BMI theo giới” và nhãn cho trục hoành (BMI) cùng trục tung (mật độ).

  • theme_minimal(): Áp dụng giao diện tối giản

Nhận xét về biểu đồ Hình thức: Biểu đồ sử dụng hai vùng mật độ (đỏ - nữ, xanh - nam) phân tách, giúp so sánh trực quan phân bố BMI giữa hai nhóm giới tính.

Đặc điểm phân phối: Cả nam và nữ đều có tập trung BMI ở vùng thấp đến trung bình, có thể hiện qua vùng mật khẩu lớn nhất ở bên trái biểu đồ. Mật độ BMI giảm dần khi BMI giá trị tăng lên, cho phép nhiều đối tượng được tìm thấy trong hai nhóm giới hạn đều có BMI nằm trong phạm vi hợp lý. Đỉnh phân phối của nữ có vẻ trôi trái và rộng hơn, ngụ ý giá trị BMI của nữ tập trung mạnh ở mức dưới 30, trong khi nam dù cũng trung tập ở vùng này nhưng có xu hướng trải rộng sang giá trị BMI cao hơn, dù mật độ thấp hơn.

2 PHẦN II: PHÂN TÍCH CÁC YẾU TỐ TÀI CHÍNH CỦA CÔNG TY GMD GIAI ĐOẠN 2015-2024

2.1 Giới thiệu dữ liệu

Bộ dữ liệu sử dụng trong bài được thu thập từ báo cáo tài chính chính của Công ty Cổ phần Gemadept (GMD) qua các năm gần đây. Gemadept là một trong những doanh nghiệp hàng đầu hoạt động trong lĩnh vực logistics, vận tải biển và khai thác thác tại Việt Nam. Bộ dữ liệu bao gồm các chỉ tiêu tài chính quan trọng như doanh thu thuần, giá vốn hàng bán, thu lợi nhuận, chi phí quản lý, tổng tài sản, vốn chủ sở hữu, cùng nhiều thông tin khác phản ánh toàn diện thực trạng hoạt động sản xuất kinh doanh và tiềm năng tài chính của công ty.

2.1.1 Đọc dữ liệu

library(dplyr)      # thao tác dữ liệu (mutate, filter, group_by...)
library(tidyr)      # reshape (pivot_longer, pivot_wider)
library(stringi)    # xử lý chuỗi, bỏ dấu tiếng Việt
library(ggplot2)    # vẽ đồ họa
library(psych)      # describe, mô tả nâng cao
library(janitor)    # clean_names, tính tần suất, tidy dữ liệu)
## 
## Attaching package: 'janitor'
## The following objects are masked from 'package:stats':
## 
##     chisq.test, fisher.test

2.1.1.1 Đọc từng sheet vào các dataframe riêng biệt và gán bào từng object mới

cdkt <- read_excel("D:/dulieugmd.xlsx", sheet = "CDKT")
kqkd <- read_excel("D:/dulieugmd.xlsx", sheet = "KQKD")
lctt <- read_excel("D:/dulieugmd.xlsx", sheet = "LCTT")

2.1.1.2 Chuẩn hóa cột đầu tiên (không dấu, thống nhất) —

names(cdkt)[1] <- "Chi_tieu"
names(lctt)[1] <- "Chi_tieu"
names(kqkd)[1] <- "Chi_tieu"

Tên cột có dấu, ký tự đặc biệt hoặc khoảng trắng rất dễ gây lỗi khi thao tác với R nên đổi tên cột đầu tiên ở mỗi bảng dữ liệu sang tên không dấu và thống nhất để thuận tiện cho các bước xử lý tiếp theo như hợp nhất bảng.

2.1.1.3 Danh sách biến cần lọc

vars_to_select <- c(
  "18. Lợi nhuận sau thuế thu nhập doanh nghiệp(60=50-51-52)",
  "TỔNG CỘNG TÀI SẢN",
  "C. NỢ PHẢI TRẢ",
  "I. Vốn chủ sở hữu",
  "12. Quỹ khen thưởng phúc lợi",
  "10. Chi phí quản lý doanh nghiệp",
  "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",
  "5. Lợi nhuận gộp về bán hàng và cung cấp dịch vụ(20=10-11)",
  "Lưu chuyển tiền thuần từ hoạt động kinh doanh"
)

Giải thích vars_to_select <- c(…): Đây là lệnh gán, tạo ra một vectơ (danh sách) biến tên (ký tự chuỗi kiểu). Danh sách này dùng để xác định chính xác các biến/cột mà em sẽ chọn trong dữ liệu bảng.

2.1.1.4 Hàm chuẩn hóa tên để so khớp (bỏ dấu, viết hoa, xóa ký tự đặc biệt) —

clean_name <- function(x) {
  toupper(stringi::stri_trans_general(x, "Latin-ASCII")) %>%
    gsub("[^A-Z0-9]", "", .)
}
vars_clean <- clean_name(vars_to_select)

Giải thích clean_namelà một hàm định nghĩa được nhận vào một vectơ ký tự (danh sách tên biến).

stringi::stri_trans_general(x, “Latin-ASCII”): Chuẩn hóa chuyển đổi toàn bộ ký tự có dấu về chữ cái Latin không dấu. Điều này giúp các biến/bảng tên trở nên đồng nhất, dễ dàng so sánh giữa nhiều nguồn dữ liệu khác nhau.

toupper(…): Chuyển toàn bộ sang chữ hoa để loại bỏ phân biệt thông thường khi so khớp biến tên.

%>% gsub(“[^A-Z0-9]”, ““, .): Bỏ toàn bộ ký tự đặc biệt, khoảng trắng, chỉ giữ lại các ký tự tiếng Anh ở dạng hoa và số. Điều này giúp tránh những lỗi khi tham gia, chọn, xoay dữ liệu vì các ký tự không mong muốn trong cột tên.

vars_clean <- clean_name(vars_to_select): Áp dụng chức năng chuẩn hóa cho các biến tên danh sách cần lọc, giúp bạn có được biến danh sách được chuẩn hóa ở mức cao nhất.

Đây là thao tác quan trọng làm sạch và thống nhất biến, tạo điều kiện thuận lợi cho các bước phân tích và trực quan hóa dữ liệu tiếp theo. Rất hữu ích khi biến tên/chỉ tiêu trong các dữ liệu (nhất là tài liệu chính) thường chứa nhiều ký tự đặc biệt, số, dấu…, dễ gây lỗi khi thao tác hoặc trùng lặp các biến giữa các bảng khác nhau.

2.1.1.5 Hàm lọc biến theo tên

filter_df <- function(df, nguon) {
  df %>%
    mutate(Chi_tieu_clean = clean_name(Chi_tieu)) %>%
    filter(Chi_tieu_clean %in% vars_clean) %>%
    mutate(Loai_bao_cao = nguon) %>%
    select(-Chi_tieu_clean)
}

Giải thích df: Là đầu vào của khung dữ liệu (bảng cần lọc thông tin).

nguon: Gốc ký hiệu biến của bảng (vd “BS”, “PL”, “CF”match với các loại báo cáo).

mutate(Khoan_muc_clean = clean_name(Khoan_muc)): Tạo mới cột “Khoan_muc_clean” từ cột “Khoan_muc” nhưng đã được chuẩn hóa qua hàm clean_name(bỏ dấu, viết hoa, loại ký tự đặc biệt).

filter(Khoan_muc_clean %in% vars_clean): Lọc chỉ giữ lại các mục/phép đo có tiêu chuẩn hóa tên đúng trong danh sách vars_clean(ví dụ: khớp đúng nhóm chỉ tiêu muốn lấy).

mutate(Nguon = nguon): Gắn thêm cột “Nguon” để đánh dấu nguồn gốc của chỉ tiêu (Rất hữu ích sau đây khi tổng hợp các bảng lại hoặc phân tích bảng liên kết).

select(-Khoan_muc_clean): Loại bỏ cột phụ “Khoan_muc_clean” ra khỏi kết quả bảng để thu gọn dữ liệu.

Hàm này mang lại quy trình làm việc chuẩn, sạch, tự động và chuyên nghiệp trong xử lý tài liệu chính, giúp quá trình lọc, phân tích dữ liệu nhanh và chính xác hơn nhiều, đặc biệt với nhiều dữ liệu hoặc thiếu nhất quán về biến tên

2.1.1.6 Lọc dữ liệu từ 3 bảng —

cdkt_sel <- filter_df(cdkt, "CDKT")
lctt_sel <- filter_df(lctt, "LCTT")
kqkd_sel <- filter_df(kqkd, "KQKD")

2.1.1.7 Gộp 3 bảng —

gmd_selected <- bind_rows(cdkt_sel, lctt_sel, kqkd_sel)

2.1.1.8 Xác định cột năm —

cols_years <- names(gmd_selected)[!names(gmd_selected) %in% c("Loai_bao_cao", "Chi_tieu")]

2.1.1.9 Pivot sang dạng long

library(dplyr)
library(tidyr)
gmd_long <- gmd_selected %>%
  pivot_longer(
    cols = !c("Loai_bao_cao", "Chi_tieu"),   # <-- CHỈNH CHỖ NÀY
    names_to = "Nam",
    values_to = "Gia_tri"
  ) %>%
  mutate(
    Nam = as.numeric(gsub("[^0-9]", "", Nam)),
    Chi_tieu_VN = case_when(
      grepl("Doanh thu thuần", Chi_tieu, ignore.case = TRUE) ~ "DT",
      grepl("Giá vốn hàng bán", Chi_tieu, ignore.case = TRUE) ~ "GVHB",
      grepl("Lợi nhuận gộp", Chi_tieu, ignore.case = TRUE) ~ "LNG",
      grepl("Quỹ khen thưởng phúc lợi", Chi_tieu, ignore.case = TRUE) ~ "QKT",
      grepl("Chi phí quản lý", Chi_tieu, ignore.case = TRUE) ~ "CPQL",
      grepl("Lợi nhuận sau thuế", Chi_tieu, ignore.case = TRUE) ~ "LNST",
      grepl("TỔNG CỘNG TÀI SẢN", Chi_tieu, ignore.case = TRUE) ~ "TTS",
      grepl("NỢ PHẢI TRẢ", Chi_tieu, ignore.case = TRUE) ~ "NPT",
      grepl("VỐN CHỦ SỞ HỮU", Chi_tieu, ignore.case = TRUE) ~ "VCSH",
      grepl("Lưu chuyển tiền thuần từ hoạt động kinh doanh", Chi_tieu, ignore.case = TRUE) ~ "CFO",
      TRUE ~ Chi_tieu
    )
  )

Nạp hai thư viện hỗ trợ:

dplyr → xử lý và biến đổi dữ liệu.

tidyr → chuyển đổi cấu trúc dữ liệu (rộng ↔︎ dài).

financial_long <- financial_selected %>% Bắt đầu chuỗi lệnh (pipeline) trên bộ dữ liệu gốc financial_selected, lưu kết quả vào financial_long.

pivot_longer(cols = !c(Nguon, Khoan_muc), names_to = “Nam”, values_to = “Gia_tri”) Chuyển dữ liệu từ dạng rộng sang dạng dài:

cols = !c(Nguon, Khoan_muc) → lấy tất cả các cột trừ hai cột này để gom lại.

names_to = “Nam” → tên cột năm sẽ được gom vào cột Nam.

values_to = “Gia_tri” → giá trị tương ứng của từng năm sẽ nằm trong cột Gia_tri. Kết quả: mỗi hàng thể hiện một khoản mục – một năm – một giá trị.

mutate( Thêm hoặc chỉnh sửa cột trong bảng: Nam = as.numeric(gsub(“[^0-9]”, ““, Nam)) Lọc chỉ giữ lại số năm (bỏ chữ cái, ký tự khác) và chuyển sang dạng số.

Khoan_muc_VN = case_when(…) Tạo cột mới Khoan_muc_VN với tên viết tắt tiếng Việt cho từng khoản mục

Nếu không trùng, giữ nguyên tên gốc (TRUE ~ Khoan_muc).

Ý nghĩa tổng thể: Đoạn code này chuẩn hóa và tái cấu trúc dữ liệu tài chính — chuyển bảng từ dạng rộng sang dạng dài, làm sạch tên năm, và gắn mã viết tắt dễ phân tích cho các khoản mục tài chính.

2.1.1.10 Loại trùng lặp (nếu có) —

gmd_long_unique <- gmd_long %>%
  group_by(Nam, Chi_tieu_VN) %>%
  summarise(Gia_tri = first(Gia_tri), .groups = "drop")

2.1.1.11 Pivot sang dạng rộng (wide) —

gmd <- gmd_long_unique %>%
  pivot_wider(
    names_from = Chi_tieu_VN,
    values_from = Gia_tri
  ) %>%
  arrange(Nam)

financial_wide <- financial_long_unique %>%

Bắt đầu chuỗi xử lý từ bộ dữ liệu dài financial_long_unique, và lưu kết quả sau khi chuyển đổi vào financial_wide.

pivot_wider(names_from = Khoan_muc_VN, values_from = Gia_tri)

Chuyển dữ liệu từ dạng dài → dạng rộng:

names_from = Khoan_muc_VN → mỗi giá trị trong cột Khoan_muc_VN sẽ trở thành một cột mới (ví dụ: DT, LNST, CFO, TTS…).

values_from = Gia_tri → các giá trị tương ứng sẽ được điền vào các cột mới này.

Kết quả: mỗi dòng là một năm, và mỗi cột là một chỉ tiêu tài chính.

arrange(Nam)

Sắp xếp dữ liệu theo thứ tự tăng dần của năm (Nam).

2.1.1.12 Kiểm tra kết quả —

cat("## KẾT QUẢ: TÊN CỘT TRONG GMD\n")
## ## KẾT QUẢ: TÊN CỘT TRONG GMD
print(names(gmd))
##  [1] "Nam"  "CFO"  "CPQL" "DT"   "GVHB" "LNG"  "LNST" "NPT"  "QKT"  "TTS" 
## [11] "VCSH"

2.1.2 Xem cấu trúc dữ liệu —

str(gmd)
## tibble [10 × 11] (S3: tbl_df/tbl/data.frame)
##  $ Nam : num [1:10] 2015 2016 2017 2018 2019 ...
##  $ CFO : num [1:10] 3.54e+11 7.62e+11 6.33e+11 5.45e+11 1.06e+12 ...
##  $ CPQL: num [1:10] 2.80e+11 2.97e+11 3.44e+11 3.21e+11 3.31e+11 ...
##  $ DT  : num [1:10] 3.01e+12 3.74e+12 3.98e+12 2.71e+12 2.64e+12 ...
##  $ GVHB: num [1:10] 2.39e+12 2.72e+12 2.95e+12 1.74e+12 1.63e+12 ...
##  $ LNG : num [1:10] 6.25e+11 1.02e+12 1.03e+12 9.68e+11 1.01e+12 ...
##  $ LNST: num [1:10] 5.65e+11 4.44e+11 5.81e+11 1.90e+12 6.14e+11 ...
##  $ NPT : num [1:10] 2.96e+12 4.25e+12 4.20e+12 3.46e+12 3.55e+12 ...
##  $ QKT : num [1:10] 1.24e+10 5.68e+10 4.68e+10 4.25e+10 6.20e+10 ...
##  $ TTS : num [1:10] 8.18e+12 1.01e+13 1.13e+13 9.98e+12 1.01e+13 ...
##  $ VCSH: num [1:10] 4.88e+12 5.87e+12 7.09e+12 6.53e+12 6.57e+12 ...

Giải thích: Sử dụng lệnh str(gmd) trong R để kiểm tra cấu trúc của một tài liệu chính có tên bảng gmd. Kết quả: Dữ liệu này là một tibble bao gồm 10 dòng (tương ứng với 10 năm từ 2015–2024) và 11 cột, mỗi cột lưu một số tài chính quan trọng. Tất cả các biến có kiểu số ( num) với số lượng lớn, phù hợp để phân tích tổng hợp chính xác theo từng năm.

2.1.3 Tên các biến

names(gmd)
##  [1] "Nam"  "CFO"  "CPQL" "DT"   "GVHB" "LNG"  "LNST" "NPT"  "QKT"  "TTS" 
## [11] "VCSH"

Hàm names(gmd) cho biết tên các biến trong bộ dữ liệu gồm: “Nam” “CFO” “CPQL” “DT” “GVHB” “LNG” “LNST” “NPT” “QKT” “TTS” “VCSH”

2.1.4 Giải thích các biến

col_desc <- data.frame(
  Ten = c("Nam", "CFO", "CPQL", "DT", "GVHB", "LNG", "LNST", "NPT", "QKT", "TTS", "VCSH"),
  Mo_ta = c(
    "Năm tài chính",
    "Lưu chuyển tiền thuần từ hoạt động kinh doanh",
    "Chi phí quản lý doanh nghiệp",
    "Doanh thu thuần",
    "Giá vốn hàng bán",
    "Lợi nhuận gộp",
    "Lợi nhuận sau thuế",
    "Nợ phải trả",
    "Quỹ khen thưởng phúc lợi",
    "Tổng cộng tài sản",
    "Vốn chủ sở hữu"
  )
)
print(col_desc)
##     Ten                                         Mo_ta
## 1   Nam                                 Năm tài chính
## 2   CFO Lưu chuyển tiền thuần từ hoạt động kinh doanh
## 3  CPQL                  Chi phí quản lý doanh nghiệp
## 4    DT                               Doanh thu thuần
## 5  GVHB                              Giá vốn hàng bán
## 6   LNG                                 Lợi nhuận gộp
## 7  LNST                            Lợi nhuận sau thuế
## 8   NPT                                   Nợ phải trả
## 9   QKT                      Quỹ khen thưởng phúc lợi
## 10  TTS                             Tổng cộng tài sản
## 11 VCSH                                Vốn chủ sở hữu

2.1.5 Kiểm tra số lượng dòng và cột

dim(gmd)
## [1] 10 11

Giải thích: dim(gmd)trong R trả về kích thước của dữ liệu bảng gmd. Kết quả: [1] 10 11 có nghĩa là:

gmd có 10 dòng (matched 10 quan sát hoặc 10 năm dữ liệu)

gmd có 11 cột (mỗi cột là một tài liệu chính biến)

2.1.6 Xem 6 dòng đầu tiên

head(gmd)
## # A tibble: 6 × 11
##     Nam      CFO    CPQL      DT    GVHB     LNG    LNST     NPT     QKT     TTS
##   <dbl>    <dbl>   <dbl>   <dbl>   <dbl>   <dbl>   <dbl>   <dbl>   <dbl>   <dbl>
## 1  2015  3.54e11 2.80e11 3.01e12 2.39e12 6.25e11 5.65e11 2.96e12 1.24e10 8.18e12
## 2  2016  7.62e11 2.97e11 3.74e12 2.72e12 1.02e12 4.44e11 4.25e12 5.68e10 1.01e13
## 3  2017  6.33e11 3.44e11 3.98e12 2.95e12 1.03e12 5.81e11 4.20e12 4.68e10 1.13e13
## 4  2018  5.45e11 3.21e11 2.71e12 1.74e12 9.68e11 1.90e12 3.46e12 4.25e10 9.98e12
## 5  2019  1.06e12 3.31e11 2.64e12 1.63e12 1.01e12 6.14e11 3.55e12 6.20e10 1.01e13
## 6  2020  6.55e11 3.41e11 2.61e12 1.66e12 9.50e11 4.40e11 3.24e12 6.01e10 9.83e12
## # ℹ 1 more variable: VCSH <dbl>

Hàm head() cho xem 6 dòng đầu tiên của bộ dữ liệu

2.1.7 Xem 6 dòng cuối

tail(gmd)
## # A tibble: 6 × 11
##     Nam      CFO    CPQL      DT    GVHB     LNG    LNST     NPT     QKT     TTS
##   <dbl>    <dbl>   <dbl>   <dbl>   <dbl>   <dbl>   <dbl>   <dbl>   <dbl>   <dbl>
## 1  2019  1.06e12 3.31e11 2.64e12 1.63e12 1.01e12 6.14e11 3.55e12 6.20e10 1.01e13
## 2  2020  6.55e11 3.41e11 2.61e12 1.66e12 9.50e11 4.40e11 3.24e12 6.01e10 9.83e12
## 3  2021  9.65e11 2.95e11 3.21e12 2.06e12 1.14e12 7.21e11 3.69e12 5.71e10 1.07e13
## 4  2022  2.30e12 5.24e11 3.90e12 2.18e12 1.72e12 1.16e12 5.08e12 6.35e10 1.30e13
## 5  2023 -2.87e 9 5.52e11 3.85e12 2.07e12 1.78e12 2.53e12 3.81e12 4.94e10 1.35e13
## 6  2024  1.53e12 5.75e11 4.83e12 2.70e12 2.14e12 1.92e12 4.23e12 6.43e10 1.80e13
## # ℹ 1 more variable: VCSH <dbl>

Hàm tail() cho xem 6 dòng cuối của bộ dữ liệu

2.1.8 Tóm tắt thống kê tổng quát

summary(gmd)
##       Nam            CFO                  CPQL                 DT           
##  Min.   :2015   Min.   :-2.870e+09   Min.   :2.805e+11   Min.   :2.606e+12  
##  1st Qu.:2017   1st Qu.: 5.672e+11   1st Qu.:3.029e+11   1st Qu.:2.784e+12  
##  Median :2020   Median : 7.084e+11   Median :3.361e+11   Median :3.474e+12  
##  Mean   :2020   Mean   : 8.794e+11   Mean   :3.861e+11   Mean   :3.448e+12  
##  3rd Qu.:2022   3rd Qu.: 1.034e+12   3rd Qu.:4.795e+11   3rd Qu.:3.885e+12  
##  Max.   :2024   Max.   : 2.299e+12   Max.   :5.748e+11   Max.   :4.832e+12  
##       GVHB                LNG                 LNST          
##  Min.   :1.630e+12   Min.   :6.251e+11   Min.   :4.405e+11  
##  1st Qu.:1.821e+12   1st Qu.:9.793e+11   1st Qu.:5.691e+11  
##  Median :2.124e+12   Median :1.024e+12   Median :6.671e+11  
##  Mean   :2.210e+12   Mean   :1.238e+12   Mean   :1.088e+12  
##  3rd Qu.:2.619e+12   3rd Qu.:1.574e+12   3rd Qu.:1.716e+12  
##  Max.   :2.955e+12   Max.   :2.135e+12   Max.   :2.534e+12  
##       NPT                 QKT                 TTS           
##  Min.   :2.961e+12   Min.   :1.235e+10   Min.   :8.180e+12  
##  1st Qu.:3.479e+12   1st Qu.:4.746e+10   1st Qu.:1.002e+13  
##  Median :3.750e+12   Median :5.691e+10   Median :1.043e+13  
##  Mean   :3.847e+12   Mean   :5.146e+10   Mean   :1.148e+13  
##  3rd Qu.:4.219e+12   3rd Qu.:6.148e+10   3rd Qu.:1.260e+13  
##  Max.   :5.083e+12   Max.   :6.427e+10   Max.   :1.800e+13  
##       VCSH          
##  Min.   :4.878e+12  
##  1st Qu.:6.539e+12  
##  Median :6.820e+12  
##  Mean   :7.603e+12  
##  3rd Qu.:7.734e+12  
##  Max.   :1.377e+13

Giải thích: hàm summary(gmd)trong R tóm tắt thống kê cơ sở thông tin cho từng biến tài chính trong dữ liệu bảng gmd, hiển thị các thống kê:

Min (giá trị nhỏ nhất)

Q 1. (Phần thứ tư - 25%)

Trung vị (Trung vị - 50%)

Mean (Trung bình cộng)

Q3. (Phần thứ tư - 75%)

Max (giá trị lớn nhất) Kết quả: Nhìn chung, bảng thống kê cho thấy doanh nghiệp phát triển về quy mô qua các năm, nhưng cũng có nhiều biến động tài chính, dữ liệu được tổng hợp đầy đủ, thuận lợi cho phân tích chuyên sâu hoặc trực quan hóa theo.

2.1.9 Kiểm tra kiểu dữ liệu của từng cột

sapply(gmd, class)
##       Nam       CFO      CPQL        DT      GVHB       LNG      LNST       NPT 
## "numeric" "numeric" "numeric" "numeric" "numeric" "numeric" "numeric" "numeric" 
##       QKT       TTS      VCSH 
## "numeric" "numeric" "numeric"

Giải thích: Hàm sapply()sẽ kiểm tra và trả về dữ liệu kiểu của từng cột trong bảng gmd.

Kết quả: cho thấy tất cả các cột như LNST, Nam, NPT, CFO, QKT, CPQL, TTS, DT, VCSH, GVHB, LNG đều có kiểu dữ liệu là số (số).

2.1.10 Thống kê số lượng giá trị duy nhất mỗi cột

sapply(gmd, function(x) length(unique(x)))
##  Nam  CFO CPQL   DT GVHB  LNG LNST  NPT  QKT  TTS VCSH 
##   10   10   10   10   10   10   10   10   10   10   10

Giải thích: sapply(gmd, function(x) length(unique(x))) cho biết số lượng giá trị duy nhất duy nhất trong từng cột của dữ liệu bảng gmd.

Kết quả: Mỗi cột (Nam, CFO, CPQL, DT, GVHB, LNG, LNST, NPT, QKT, TTS, VCSH) đều có đúng 10 giá trị khác nhau .

2.1.11 Giá trị các năm

unique(gmd$Nam)
##  [1] 2015 2016 2017 2018 2019 2020 2021 2022 2023 2024

Giải thích: Lệnh unique(gmd$Nam)trong R dùng để liệt kê tất cả các năm giá trị khác nhau trong cột Namcủa dữ liệu bảng gmd Kết quả: các dãy năm từ 2015 đến 2024, tức là dữ liệu bao phủ đều liên tục trong 10 năm.

2.2 Mã hóa, chuẩn hóa dữ liệu

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

gmd <- gmd %>% clean_names()

Giải thích: hàm clean_names() trong R để chuẩn hóa cột tên của dữ liệu bảng gmd. Kết quả, các tên cột trong gmdsẽ được chuyển thành dạng snake_case

2.2.2 Xem lại tên cột sau clean

colnames(gmd)
##  [1] "nam"  "cfo"  "cpql" "dt"   "gvhb" "lng"  "lnst" "npt"  "qkt"  "tts" 
## [11] "vcsh"

Giải thích: Lệnh colnames(gmd)trong R dùng để lấy tên tất cả các cột (biến) của dữ liệu bảng gmd Kết quả: Các tên cột trong gmd sau khi chuẩn hóa đã chuyển thành chữ thường, loại bỏ khoảng trắng, ký tự đặc biệt

2.2.3 Kiểm tra NA —

anyNA(gmd)
## [1] FALSE

Giải thích: Lệnh anyNA(gmd)trong R được sử dụng để kiểm tra xem dữ liệu bảng gmd có thiếu giá trị (NA) hay không . Lệnh này sẽ trả về kết quả TRUEnếu có ít nhất một NA giá trị trong bất kỳ ô nào của bảng và FALSEnếu toàn bộ dữ liệu đầy đủ thì không có NA. Nhận xét: kết quả trả về là FALSE, dữ liệu đã đầy đủ và không có ô trống hoặc thiếu dữ liệu.

2.2.4 Loại bỏ outlier bằng Z-score

z <- scale(gmd$dt)
gmd$DT_Outlier <- ifelse(abs(z) > 2, 1, 0)
table(gmd$DT_Outlier)
## 
##  0 
## 10

Giải thích: - scale(gmd$dt)tính z-score cho từng giá trị doanh thu, tức là đo xem từng giá trị cách xa trung bình bao nhiêu độ lệch chuẩn.

  • Dòng thứ hai tạo mới cột DT_Outliertrong bảng, gán giá trị 1 cho các dòng có z-score lớn hơn 2 hoặc nhỏ hơn -2 (có nghĩa là ngoại lệ), còn lại là 0 .

2.2.5 Chuẩn hóa đơn vị tính

gmd <- gmd %>%
  mutate(across(c(dt, gvhb, lng, lnst, tts, vcsh, cfo, cpql, qkt, npt), ~ ./1e9))

Tất cả các biến được liệt kê sẽ được chuyển đổi sang đơn vị “tỷ đồng” để dễ thao tác

2.2.6 Gắn nhãn Doanh thu cao/thấp

gmd$DT_Nhom <- ifelse(gmd$dt > median(gmd$dt), "Cao", "Thap")
table(gmd$DT_Nhom)
## 
##  Cao Thap 
##    5    5

Giải thích: ifelse(gmd\(dt > median(gmd\)dt), “Cao”, “Thap”)sẽ gán nhãn “Cao” nếu doanh thu năm đó lớn hơn trung vị của toàn bộ dữ liệu, ngược lại là “Thap”.

table(gmd$DT_Nhom)sẽ hiển thị số lượng thuộc tính của nhóm “Cao” và “Thap

Kết quả phân tích nhóm doanh thu thành 5 năm “Cao” và 5 năm “Thấp” cho thấy dữ liệu đã được chia rất cân bằng dựa trên giá trị trung vị.

2.2.7 Chuẩn hóa cột “nam”

gmd$nam <- as.factor(gmd$nam)

as.factor() là hàm chuyển kiểu dữ liệu sang kiểu phân loại (factor) trong R Chuyển cột nam thành biến phân loại (factor) thay vì số hoặc ký tự.

2.2.8 Tạo biến tỷ lệ LNST/DT

gmd$TyLeLNST_DT <- gmd$lnst / gmd$dt
table(gmd$TyLeLNST_DT)
## 
## 0.118592747011267 0.145944207832905 0.169045338833193 0.187522868234338 
##                 1                 1                 1                 1 
## 0.224733813541222 0.232156274903839 0.297901802358114 0.398090565227359 
##                 1                 1                 1                 1 
## 0.658879016112681 0.701832261704493 
##                 1                 1

Biến mới TyLeLNST_DTphản ánh hiệu suất sinh hoạt trên doanh thu mỗi năm. Nhận xét Tỷ lệ LNST/DT dao động từ khoảng 0,12 đến 0,70 , cho thấy mức sinh động trên doanh thu các năm rất khác nhau.

Tỷ lệ năm trên 0,6 là rất cao, có thể tạo ra doanh nghiệp hoạt động hiệu quả, chi phí thấp hoặc có nguồn thu đặc biệt vượt trội.

Tỷ lệ năm dưới 0,2 phản ánh doanh nghiệp gặp khó khăn, hoặc chi phí lớn tạo lợi nhuận thấp.

2.2.9 Tạo biến tăng trưởng doanh thu so với năm trước

gmd <- gmd %>%
  arrange(nam) %>%
  mutate(DT_TangTruong = c(NA, diff(dt)))
head(gmd$DT_TangTruong)
## [1]          NA   729.06437   242.29380 -1276.40629   -64.64246   -37.24756

Giải thích: arrange(nam): Sắp xếp dữ liệu theo thứ tự tăng dần, đảm bảo đúng trình tự trước khi tính toán chênh lệch.

diff(dt): Tính số thu doanh thu của năm hiện tại so với năm trước, kết quả là một độ dài (n-1) .

c(NA, …): Thêm NA giá trị ở đầu để điều chỉnh chiều dài bằng gốc dữ liệu. Giá trị này điền cho năm đầu tiên (không có năm trước để so sánh).

mutate(DT_TangTruong = …): Lưu kết quả vào một cột mới, đại diện cho cấp độ tăng trưởng tuyệt vời thu từng năm .

Kết quả: Năm đầu tiên: NA có giá trị vì không có dữ liệu năm trước để so sánh (hoàn toàn hợp lý).

Giai đoạn sau: Có năm doanh thu tăng rất mạnh (ví dụ, tăng hơn 700 tỷ lệ và 242 tỷ lệ), nhưng cũng có năm giảm độ sâu (giảm đến hơn 1.200 tỷ lệ), bên cạnh đó có các năm tăng/giảm nhỏ hơn ( khoảng -65 hoặc -37 tỷ lệ).

Xu hướng: Sự thay đổi điều này phản ánh doanh thu doanh nghiệp không ổn định, có những năm tăng đột biến nhưng cũng có năm giảm mạnh.

2.2.10 Nhóm năm thành hai giai đoạn

gmd$nam <- as.numeric(as.character(gmd$nam))
gmd$GiaiDoan <- ifelse(gmd$nam <= 2019, "Truoc2020", "Sau2020")
table(gmd$GiaiDoan)
## 
##   Sau2020 Truoc2020 
##         5         5

Kết quả cho thấy dữ liệu đã được chia rất cân bằng. Trước và sau 2020 đều là 5 năm

2.2.11 Tạo biến mới: một số tỷ số tài chính

gmd <- gmd %>%
mutate(
ROA = lnst / tts, 
ROE = lnst / vcsh, 
Debt_ratio = npt / tts, 
CFO_TTS = cfo / tts 
)
gmd %>% select( ROA, ROE, Debt_ratio, CFO_TTS)
## # A tibble: 10 × 4
##       ROA    ROE Debt_ratio   CFO_TTS
##     <dbl>  <dbl>      <dbl>     <dbl>
##  1 0.0691 0.116       0.362  0.0433  
##  2 0.0439 0.0756      0.420  0.0753  
##  3 0.0515 0.0820      0.372  0.0561  
##  4 0.190  0.291       0.346  0.0546  
##  5 0.0606 0.0934      0.351  0.104   
##  6 0.0448 0.0668      0.329  0.0666  
##  7 0.0671 0.102       0.344  0.0899  
##  8 0.0891 0.146       0.390  0.176   
##  9 0.187  0.260       0.282 -0.000212
## 10 0.107  0.140       0.235  0.0848

Giải thích: Toán tử %>%(ống): giúp “chuyền” kết quả của bước này sang bước tiếp theo, tạo ra lệnh đọc chuỗi thành công và rõ ràng hơn trong quá trình xử lý dữ liệu. sử dụng hàm mutate()trong dplyr để tạo thêm các tỷ lệ biến số chính vào dữ liệu bảng gmd ROA : Lợi nhuận sau thuế trên tổng tài sản ( lnst / tts), đo lường hiệu quả sinh lời của tài sản.

ROE : Lợi nhuận sau thuế trên vốn chủ sở hữu ( lnst / vcsh), phản ánh hiệu quả sinh lời của vốn góp.

Tỷ lệ nợ : Tỷ suất nợ trên tổng tài sản ( npt / tts), cho biết cơ cấu tài sản được hình thành thành nợ.

CFO_TTS : Dòng tiền kinh doanh trên tổng tài sản ( cfo / tts), cho thấy hiệu quả tạo tiền mặt. select(ROA, ROE, Debt_ratio, CFO_TTS)chỉ lấy ra 4 cột mới để xem

Kết quả: ROA và ROE đều dao động trong khoảng khá rộng, nhưng chủ yếu nằm trong vùng 0,04–0,19 đối với ROA và 0,06–0,29 đối với ROE. Những năm có ROE vượt trên 0,2 hoặc ROA trên 0,1 có thể mang lại hiệu quả sinh lời khá tốt nên bình quân các năm còn lại.

Tỷ lệ nợ lớn xung quanh 0,32–0,42 các năm đầu, giảm mạnh về sát 0,23 vào các năm gần đây, chứng tỏ doanh nghiệp đang giảm phụ thuộc vào nợ, dấu hiệu tăng tính an toàn tài chính.

CFO_TTS (dòng tiền kinh doanh trên tài sản) dao động từ 0,04 đến 0,17. Các năm giá trị cao phản ánh doanh nghiệp không chỉ tăng lợi nhuận mà còn tạo ra dòng tiền thực sự tốt cho kinh doanh.

2.2.12 Kiểm tra tóm tắt cuối cùng

summary(gmd)
##       nam            cfo               cpql             dt            gvhb     
##  Min.   :2015   Min.   :  -2.87   Min.   :280.5   Min.   :2606   Min.   :1630  
##  1st Qu.:2017   1st Qu.: 567.18   1st Qu.:302.9   1st Qu.:2784   1st Qu.:1821  
##  Median :2020   Median : 708.44   Median :336.1   Median :3474   Median :2124  
##  Mean   :2020   Mean   : 879.43   Mean   :386.1   Mean   :3448   Mean   :2210  
##  3rd Qu.:2022   3rd Qu.:1034.28   3rd Qu.:479.5   3rd Qu.:3885   3rd Qu.:2619  
##  Max.   :2024   Max.   :2299.24   Max.   :574.8   Max.   :4832   Max.   :2955  
##                                                                                
##       lng              lnst             npt            qkt       
##  Min.   : 625.1   Min.   : 440.5   Min.   :2961   Min.   :12.35  
##  1st Qu.: 979.3   1st Qu.: 569.1   1st Qu.:3479   1st Qu.:47.46  
##  Median :1023.7   Median : 667.1   Median :3750   Median :56.91  
##  Mean   :1237.6   Mean   :1088.4   Mean   :3847   Mean   :51.46  
##  3rd Qu.:1574.0   3rd Qu.:1715.5   3rd Qu.:4219   3rd Qu.:61.48  
##  Max.   :2135.5   Max.   :2533.9   Max.   :5083   Max.   :64.27  
##                                                                  
##       tts             vcsh       DT_Outlier.V1   DT_Nhom         
##  Min.   : 8180   Min.   : 4878   Min.   :0     Length:10         
##  1st Qu.:10018   1st Qu.: 6539   1st Qu.:0     Class :character  
##  Median :10426   Median : 6820   Median :0     Mode  :character  
##  Mean   :11483   Mean   : 7603   Mean   :0                       
##  3rd Qu.:12596   3rd Qu.: 7734   3rd Qu.:0                       
##  Max.   :17998   Max.   :13772   Max.   :0                       
##                                                                  
##   TyLeLNST_DT     DT_TangTruong        GiaiDoan              ROA         
##  Min.   :0.1186   Min.   :-1276.41   Length:10          Min.   :0.04386  
##  1st Qu.:0.1737   1st Qu.:  -52.42   Class :character   1st Qu.:0.05378  
##  Median :0.2284   Median :  242.29   Mode  :character   Median :0.06811  
##  Mean   :0.3135   Mean   :  202.16                      Mean   :0.09104  
##  3rd Qu.:0.3730   3rd Qu.:  691.95                      3rd Qu.:0.10244  
##  Max.   :0.7018   Max.   :  986.20                      Max.   :0.19033  
##                   NA's   :1                                              
##       ROE            Debt_ratio        CFO_TTS          
##  Min.   :0.06679   Min.   :0.2348   Min.   :-0.0002119  
##  1st Qu.:0.08482   1st Qu.:0.3329   1st Qu.: 0.0549743  
##  Median :0.10905   Median :0.3486   Median : 0.0709517  
##  Mean   :0.13731   Mean   :0.3430   Mean   : 0.0751293  
##  3rd Qu.:0.14451   3rd Qu.:0.3692   3rd Qu.: 0.0886391  
##  Max.   :0.29105   Max.   :0.4202   Max.   : 0.1764486  
## 

Hàm summary()trong R là công cụ giúp tắt đặc điểm chính của biến hoặc dữ liệu bảng. Kết quả trả về 1 bảng tổng quát dữ liệu.

2.3 THỐNG KÊ MÔ TẢ

2.3.1 Tóm tắt mô tả tổng quan

library(skimr)
str(gmd)
## tibble [10 × 20] (S3: tbl_df/tbl/data.frame)
##  $ nam          : num [1:10] 2015 2016 2017 2018 2019 ...
##  $ cfo          : num [1:10] 354 762 633 545 1057 ...
##  $ cpql         : num [1:10] 280 297 344 321 331 ...
##  $ dt           : num [1:10] 3013 3742 3984 2708 2643 ...
##  $ gvhb         : num [1:10] 2388 2723 2955 1739 1630 ...
##  $ lng          : num [1:10] 625 1018 1029 968 1013 ...
##  $ lnst         : num [1:10] 565 444 581 1900 614 ...
##  $ npt          : num [1:10] 2961 4251 4197 3455 3553 ...
##  $ qkt          : num [1:10] 12.4 56.8 46.8 42.5 62 ...
##  $ tts          : num [1:10] 8180 10118 11291 9984 10120 ...
##  $ vcsh         : num [1:10] 4878 5867 7095 6529 6567 ...
##  $ DT_Outlier   : num [1:10, 1] 0 0 0 0 0 0 0 0 0 0
##  $ DT_Nhom      : chr [1:10] "Thap" "Cao" "Cao" "Thap" ...
##  $ TyLeLNST_DT  : num [1:10] 0.188 0.119 0.146 0.702 0.232 ...
##  $ DT_TangTruong: num [1:10] NA 729.1 242.3 -1276.4 -64.6 ...
##  $ GiaiDoan     : chr [1:10] "Truoc2020" "Truoc2020" "Truoc2020" "Truoc2020" ...
##  $ ROA          : num [1:10] 0.0691 0.0439 0.0515 0.1903 0.0606 ...
##  $ ROE          : num [1:10] 0.1158 0.0756 0.082 0.291 0.0934 ...
##  $ Debt_ratio   : num [1:10] 0.362 0.42 0.372 0.346 0.351 ...
##  $ CFO_TTS      : num [1:10] 0.0433 0.0753 0.0561 0.0546 0.1045 ...

Giải thích: skim() cho ta toàn cảnh dữ liệu — số dòng, trung bình, độ lệch chuẩn, min, max, tỉ lệ NA… Dùng để đánh giá chất lượng dữ liệu sau xử lý. Kết quả: bảng cho thấy có 20 biến và 10 dòng (tương ứng 10 năm). Các biến bao gồm: doanh thu, lợi nhuận sau thuế, dòng tiền, nợ, tổng tài sản… và các biến chỉ số tài chính như ROA, ROE, Debt_ratio, CFO_TTS, cùng các nhóm giai đoạn, nhãn doanh thu cao/ thấp và kiểm tra ngoại lai.

2.3.2 Thống kê mô tả biến doanh thu thuần (dt)

library(psych)

describe(gmd$dt)  
##    vars  n    mean    sd  median trimmed    mad     min     max   range skew
## X1    1 10 3447.68 730.6 3473.98 3379.88 720.07 2605.67 4832.02 2226.36 0.35
##    kurtosis     se
## X1    -1.21 231.03

Giải thích: Hàm describe() cho bạn các thông tin thống kê cơ bản cho từng biến:

mean, sd, median, min, max, range, skew, kurtosis, se

Nhận xét: Biến doanh thu thuần có giá trị trung bình đạt khoảng 3447 tỷ đồng, với độ lệch chuẩn 730.6 tỷ đồng, cho thấy mức độ biến động doanh thu giữa các năm là tương đối cao.

Giá trị trung vị (3473.98) nhỏ hơn một chút so với trung bình (3447.68), điều này cho thấy phân phối hơi lệch phải (skew = 0.35) — tức là có một vài năm doanh thu cao vượt trội hơn so với phần còn lại.

Độ nhọn (kurtosis = -1.21) mang giá trị âm, cho thấy phân phối dẹt hơn phân phối chuẩn, nghĩa là dữ liệu doanh thu trải đều và ít cực trị hơn.

Khoảng dao động (range) khoảng 2226.36 tỷ đồng, thể hiện sự thay đổi khá lớn giữa năm có doanh thu thấp nhất và cao nhất.

Sai số chuẩn (SE = 231.03) tương đối nhỏ so với trung bình, chứng tỏ ước lượng trung bình khá ổn định.

Nhìn chung, doanh thu thuần của doanh nghiệp có xu hướng tăng dần theo thời gian, nhưng vẫn tồn tại một vài năm tăng đột biến, làm cho phân phối hơi lệch về bên phải.

2.3.3 Thống kê mô tả biến giá vốn hàng bán (gvhb)

library(psych)
describe(gmd$gvhb)  
##    vars  n    mean     sd median trimmed    mad     min     max   range skew
## X1    1 10 2210.05 470.62   2124 2189.44 631.93 1630.14 2954.82 1324.68 0.19
##    kurtosis     se
## X1    -1.59 148.82

Nhận xét: Biến giá vốn hàng bán có giá trị trung bình khoảng 2210.05 tỷ, với độ lệch chuẩn 470.62 tỷ đồng, cho thấy mức độ dao động giữa các năm ở mức trung bình, không quá lớn so với quy mô trung bình.

Giá trị trung vị (2124 tỷ) khá gần với trung bình (2210.05 ), chứng tỏ dữ liệu phân bố khá cân đối, không có sự chênh lệch lớn giữa các năm.

Hệ số skew = 0.19 dương nhẹ cho thấy phân phối hơi lệch phải, nghĩa là có một vài năm giá vốn cao hơn mức trung bình.

Độ nhọn (kurtosis = -1.59) âm, thể hiện phân phối dẹt hơn phân phối chuẩn, tức là giá trị các năm khá đồng đều, không có nhiều giá trị cực trị.

Khoảng biến thiên (range = 1324.68) cho thấy sự chênh lệch vừa phải giữa năm có giá vốn thấp nhất và cao nhất.

Nhìn chung, giá vốn hàng bán tương đối ổn định qua các năm, biến động không quá lớn và có xu hướng phân bố đều quanh giá trị trung bình.

2.3.4 Thống kê mô tả lưu chuyển tiền từ hdkd (cfo)

library(psych)
describe(gmd$cfo)  
##    vars  n   mean     sd median trimmed    mad   min     max   range skew
## X1    1 10 879.43 646.81 708.44  812.24 448.76 -2.87 2299.24 2302.11 0.83
##    kurtosis     se
## X1    -0.18 204.54

Nhận xét: Biến CFO có giá trị trung bình khoảng 879.43,

Độ lệch chuẩn 646.81 cho thấy biến động lớn giữa các quan sát,

Phân phối lệch phải nhẹ (skew = 0.83) → có một số giá trị CFO rất cao,

Không có dấu hiệu phân phối quá nhọn (kurtosis ≈ 0)

2.3.5 Thống kê mô tả biến lợi nhuận sau thuế (lnst)

library(psych)
describe(gmd$lnst)  
##    vars  n    mean     sd median trimmed    mad    min     max   range skew
## X1    1 10 1088.38 758.56 667.07  988.67 333.53 440.48 2533.93 2093.46 0.73
##    kurtosis     se
## X1    -1.23 239.88

Nhận xét:

Biến lợi nhuận sau thuế (LNST) có trung bình đạt 1088.38 tỷ đồng, phản ánh khả năng sinh lời khá ổn định của doanh nghiệp trong giai đoạn nghiên cứu. Tuy nhiên, độ lệch chuẩn cao (758.56) và khoảng biến thiên lớn (2093.46) cho thấy mức lợi nhuận giữa các năm chênh lệch đáng kể, thể hiện ảnh hưởng từ biến động chi phí và doanh thu.

Trung vị (667.07) nhỏ hơn trung bình (1088.38), cùng với skewness = 0.73 dương nhẹ, cho thấy dữ liệu hơi lệch phải, có một vài năm doanh nghiệp đạt lợi nhuận cao vượt trội so với mặt bằng chung.

Kurtosis = -1.23 âm, nghĩa là phân phối dẹt, lợi nhuận phân tán rộng và ít tập trung quanh giá trị trung bình, phản ánh sự dao động đáng kể trong kết quả kinh doanh qua các năm.

Tổng thể, lợi nhuận sau thuế có xu hướng biến động trung bình, nhưng vẫn giữ mức lợi nhuận ổn định qua các năm, chứng tỏ doanh nghiệp duy trì hiệu quả hoạt động tích cực, dù vẫn chịu ảnh hưởng từ chi phí và biến động thị trường

2.3.6 Thống kê mô tả biến Tổng tài sản (tts)

library(psych)
describe(gmd$tts)  
##    vars  n     mean      sd   median  trimmed     mad     min      max   range
## X1    1 10 11483.32 2774.76 10425.56 11081.94 1079.83 8179.78 17997.85 9818.07
##    skew kurtosis     se
## X1 1.12     0.31 877.46

Kết quả thống kê mô tả cho biến Tổng tài sản (TTS) cho thấy, giá trị trung bình đạt khoảng 11483.32 tỷ đồng, trong khi giá trị trung vị là 10425.56 tỷ đồng. Điều này phản ánh rằng mức tổng tài sản của doanh nghiệp nhìn chung nằm quanh ngưỡng trên 10000 tỷ đồng, tuy nhiên có sự chênh lệch nhất định giữa các năm.

Độ lệch chuẩn (SD = 2774.76 tỷ) cho thấy mức độ biến động của tổng tài sản giữa các năm là tương đối lớn, thể hiện quy mô tài sản không ổn định hoàn toàn. Khoảng biến thiên (range = 9818.07 tỷ) cùng với giá trị nhỏ nhất (8179.78 tỷ) và lớn nhất (17997.85 tỷ) càng củng cố nhận định này.

Hệ số độ lệch (skewness) = 1,12 cho thấy phân phối dữ liệu lệch phải, tức là có một số năm doanh nghiệp sở hữu tổng tài sản cao vượt trội so với mặt bằng chung. Bên cạnh đó, độ nhọn (kurtosis) = 0,31 cho thấy phân phối khá phẳng, ít tập trung quanh giá trị trung bình hơn so với phân phối chuẩn.

Nhìn chung, các chỉ tiêu thống kê trên cho thấy tổng tài sản của doanh nghiệp có xu hướng tăng nhưng biến động đáng kể giữa các năm, phản ánh sự mở rộng quy mô hoạt động song vẫn chịu ảnh hưởng của các yếu tố thị trường và đầu tư.

2.3.7 Thống kê trung bình các chỉ tiêu theo năm

gmd %>%
  group_by(nam) %>%
  summarise(across(where(is.numeric), mean, na.rm = TRUE))
## Warning: There was 1 warning in `summarise()`.
## ℹ In argument: `across(where(is.numeric), mean, na.rm = TRUE)`.
## ℹ In group 1: `nam = 2015`.
## Caused by warning:
## ! The `...` argument of `across()` is deprecated as of dplyr 1.1.0.
## Supply arguments directly to `.fns` through an anonymous function instead.
## 
##   # Previously
##   across(a:b, mean, na.rm = TRUE)
## 
##   # Now
##   across(a:b, \(x) mean(x, na.rm = TRUE))
## # A tibble: 10 × 18
##      nam     cfo  cpql    dt  gvhb   lng  lnst   npt   qkt    tts   vcsh
##    <dbl>   <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>  <dbl>  <dbl>
##  1  2015  354.    280. 3013. 2388.  625.  565. 2961.  12.4  8180.  4878.
##  2  2016  762.    297. 3742. 2723. 1018.  444. 4251.  56.8 10118.  5867.
##  3  2017  633.    344. 3984. 2955. 1029.  581. 4197.  46.8 11291.  7095.
##  4  2018  545.    321. 2708. 1739.  968. 1900. 3455.  42.5  9984.  6529.
##  5  2019 1057.    331. 2643. 1630. 1013.  614. 3553.  62.0 10120.  6567.
##  6  2020  655.    341. 2606. 1656.  950.  440. 3240.  60.1  9835.  6595.
##  7  2021  965.    295. 3206. 2064. 1142.  721. 3687.  57.1 10731.  7045.
##  8  2022 2299.    524. 3898. 2180. 1718. 1161. 5083.  63.5 13031.  7948.
##  9  2023   -2.87  552. 3846. 2068. 1778. 2534. 3814.  49.4 13546.  9732.
## 10  2024 1527.    575. 4832. 2697. 2135. 1924. 4226.  64.3 17998. 13772.
## # ℹ 7 more variables: DT_Outlier <dbl>, TyLeLNST_DT <dbl>, DT_TangTruong <dbl>,
## #   ROA <dbl>, ROE <dbl>, Debt_ratio <dbl>, CFO_TTS <dbl>

Giải thích: %>% (pipe operator) → Là toán tử kết nối của dplyr, giúp chuyển kết quả bước trước làm đầu vào của bước sau. Giúp code dễ đọc hơn, thay vì lồng nhiều hàm.

group_by(nam) → Nhóm dữ liệu theo biến nam (tức là theo từng năm). Sau khi nhóm, mọi phép tính tiếp theo (ví dụ mean) sẽ được tính riêng cho từng nhóm năm.

summarise(…) → Dùng để tính toán tóm tắt cho từng nhóm vừa tạo ở trên.

across(where(is.numeric), mean, na.rm = TRUE) → Đây là phần mạnh mẽ nhất của code:

where(is.numeric) → chọn tất cả các cột dạng số trong bảng.

mean → áp dụng hàm trung bình cho các cột đó.

na.rm = TRUE → bỏ qua giá trị NA (thiếu dữ liệu) khi tính trung bình.

-> Kết quả: Nhiều chỉ tiêu tài chính như thu doanh thu ( dt), lợi nhuận nhuận ( lng), lợi nhuận sau thuế ( lnst), tổng tài sản ( tts)… tăng mạnh ở các năm gần đây, đặc biệt là hai năm cuối (2023-2024). Điều này phản ánh doanh nghiệp có bước phát triển vượt bậc, thể hiện ở giá trị cao nổi bật so với các năm trước. Một số năm ở giữa (2018-2020), các số tài chính chính không tăng đều, có năm tăng nhanh, có năm giảm nhẹ hoặc chững lại.

2.3.8 Tổng lợi nhuận

sum_lnst <- sum(gmd$lnst, na.rm=TRUE)
print(sum_lnst)
## [1] 10883.77

Giải thích: financial_wide$lnst → Truy cập cột lnst trong bảng dữ liệu financial_wide.

Đây là cột Lợi nhuận sau thuế (Lợi nhuận ròng) của doanh nghiệp qua các năm.

sum(…, na.rm = TRUE) → Hàm sum() dùng để tính tổng tất cả giá trị trong cột.

na.rm = TRUE có nghĩa là bỏ qua các giá trị bị thiếu (NA) khi tính.

Nếu bạn không có đối số này, R có thể trả về NA nếu cột có dữ liệu bị thiếu.

sum_lnst <- … → Gán kết quả tổng lợi nhuận vào biến mới tên là sum_lnst,

Kết quả: Tổng lợi nhuận sau thuế (LNST) của doanh nghiệp trong toàn bộ giai đoạn nghiên cứu là 10883.77 tỷ đồng.

2.3.9 Hệ số tương quan DT và LNST

cor(gmd$dt, gmd$lnst)
## [1] 0.3902551

Giải thích: cor(gmd\(dt, gmd\)lnst) trong R dùng để tính hệ số tương quan Pearson giữa biến dt(doanh thu) và lnst(lợi nhuận sau thuế) của dataframe gmd. Kết quả trả về là 0,3902551. Kết quả: Hệ số tương lợi quan 0,39 cho thấy giữa doanh thu và thuế sau thuế có mối liên hệ cùng chiều ở mức trung bình yếu. Tức là khi doanh thu tăng, lợi nhuận sau thuế cũng có xu hướng tăng nhưng độ liên hệ không cao, phản ánh ánh sáng ngoài doanh thu, còn nhiều yếu tố khác tác động mạnh đến lợi nhuận sau thuế của doanh nghiệp.

2.3.10 Tổng tài sản trung bình mỗi năm

tas_mean_year <-gmd %>% group_by(nam) %>%
  summarise(Mean_TTS = mean(tts, na.rm=TRUE))
print(tas_mean_year)
## # A tibble: 10 × 2
##      nam Mean_TTS
##    <dbl>    <dbl>
##  1  2015    8180.
##  2  2016   10118.
##  3  2017   11291.
##  4  2018    9984.
##  5  2019   10120.
##  6  2020    9835.
##  7  2021   10731.
##  8  2022   13031.
##  9  2023   13546.
## 10  2024   17998.
  • gmd %>%

Lấy bảng dữ liệu gốc gmdlàm đầu vào (chứa các chỉ tiêu tài chính theo năm).

Ký hiệu %>% (pipe) có nghĩa là “truyền kết quả sang bước tiếp theo”.

  • group_by(nam)

Nhóm dữ liệu theo năm (nam), tức là mỗi nhóm tương ứng với một năm quan sát.

Giúp ta tính toán riêng biệt cho từng năm thay vì toàn bộ dữ liệu chung.

  • summarise(Mean_TTS = mean(tts, na.rm=TRUE))

Tạo một biến tóm tắt mới có tên là Mean_TTS, là giá trị trung bình của tổng tài sản (tts) trong từng năm.

na.rm = TRUE có nghĩa là bỏ qua các giá trị bị thiếu (NA) để tránh lỗi khi tính trung bình.

  • print(tas_mean_year): in bảng kết quả.

Kết quả: Có thể thấy tổng tài sản trung bình qua các năm tăng liên tục và mạnh mẽ. Từ mức thấp nhất là khoảng 8179.782 tỷ đồng (năm 2015) đến mức cao nhất hơn 17997.853 tỷ đồng (năm 2024).

Giai đoạn bùng nổ: Sự tăng trưởng đặc biệt nổi bật ở các năm gần đây, nhất là sau năm 2021, khi tổng tài sản tăng rất nhanh tới đạt gần 18.000 tỷ vào năm 2024.

2.3.11 Đếm số năm có lợi nhuận âm

num_loss <- sum(gmd$lnst < 0)
print(num_loss)
## [1] 0

Giải thích: gmd$lnst: Truy cập cột lợi nhuận sau thuế (lnst) trong bảng dữ liệu gmd.

gmd$lnst < 0: Tạo ra một vector logic (TRUE/FALSE), trong đó:

TRUE nếu lợi nhuận < 0 (tức là lỗ),

FALSE nếu lợi nhuận ≥ 0 (có lãi).

sum(…): Trong R, khi cộng các giá trị TRUE/FALSE, R tự hiểu:

TRUE = 1

FALSE = 0 → Do đó, sum(…) sẽ đếm số năm có lợi nhuận âm.

Kết quả được lưu vào biến num_loss, cho biết có bao nhiêu năm doanh nghiệp bị lỗ trong toàn bộ giai đoạn nghiên cứu.

Kết quả: Trong giai đoạn 2015–2024, doanh nghiệp không có năm nào bị lỗ (lợi nhuận âm). Điều này cho thấy hiệu quả hoạt động kinh doanh rất ổn định, khả năng kiểm soát chi phí và tạo ra lợi nhuận bền vững qua các năm. Việc duy trì lợi nhuận dương liên tục trong 10 năm là một dấu hiệu tích cực, phản ánh năng lực tài chính tốt và chiến lược hoạt động hiệu quả của doanh nghiệp.

2.3.12 Bảng tần suất các mức doanh thu

table_dt <- table(cut(gmd$dt, breaks=4))
print(table_dt)
## 
##  (2.6e+03,3.16e+03] (3.16e+03,3.72e+03] (3.72e+03,4.28e+03] (4.28e+03,4.83e+03] 
##                   4                   1                   4                   1

Hàm cut() chia biến doanh thu (dt) thành 4 khoảng bằng nhau dựa theo giá trị nhỏ nhất và lớn nhất.

table(…): Tạo bảng tần suất cho từng khoảng doanh thu. Cho biết có bao nhiêu năm nằm trong mỗi nhóm.

Kết quả: Doanh thu của các quan sát phân bố không đồng đều, tập trung chủ yếu trong hai khoảng 2,600–3,160 và 3,720–4,280. Số lượng doanh nghiệp đạt doanh thu cao (trên 4,280) rất ít, cho thấy phân phối doanh thu có xu hướng lệch phải, phần lớn doanh nghiệp có doanh thu ở mức trung bình.

2.3.13 Hệ số biến thiên (CV)

gmd %>%
  summarise(across(c(dt, lnst), ~ sd(.) / mean(.) * 100))
## # A tibble: 1 × 2
##      dt  lnst
##   <dbl> <dbl>
## 1  21.2  69.7

Giải thích: %>% Toán tử pipe, giúp nối các bước xử lý trong dplyr summarise() Tóm tắt dữ liệu, tính toán các chỉ tiêu thống kê across(c(dt, lnst), …): Áp dụng cùng một phép tính cho 2 biến dt và lnst ~ sd(.) / mean(.) * 100: Công thức tính hệ số biến thiên (CV) = (Độ lệch chuẩn / Giá trị trung bình) × 100

Nhận xét: Hệ số biến thiên của doanh thu (21.191 %) tương đối thấp, cho thấy doanh thu thuần khá ổn định qua các năm. → Điều này phản ánh rằng quy mô hoạt động kinh doanh không biến động quá lớn, doanh nghiệp duy trì được mức doanh thu ổn định.

Hệ số biến thiên của lợi nhuận sau thuế (69.69639 %) cao hơn nhiều so với doanh thu, cho thấy mức độ dao động mạnh của lợi nhuận. → Mặc dù doanh thu ổn định, hiệu quả sinh lời biến động lớn, có thể do chi phí, giá vốn, hoặc yếu tố thị trường ảnh hưởng đến lợi nhuận ròng.

Tỷ lệ CV(lợi nhuận) > CV(doanh thu) ⇒ doanh nghiệp chưa kiểm soát tốt chi phí hoặc biên lợi nhuận biến động mạnh qua các năm.

2.3.14 Tính tỷ trọng vốn chủ sở hữu so với tổng tài sản

gmd <- gmd %>% mutate(Ty_le_vcsh = vcsh/tts)
head(gmd$Ty_le_vcsh)
## [1] 0.5963461 0.5798244 0.6283235 0.6539404 0.6489444 0.6705882

Giải thích: mutate(…): dùng để thêm hoặc thay đổi biến mới trong dataframe.

vcsh / tts: phép chia giữa vốn chủ sở hữu (VCSH) và tổng tài sản (TTS).

Kết quả gán lại vào financial_wide, nghĩa là bảng giờ có thêm cột Ty_le_vcsh biểu diễn tỷ lệ vốn chủ sở hữu chiếm trong tổng tài sản mỗi năm

Kết quả: Tỷ lệ vốn chủ sở hữu trên tổng tài sản dao động từ 58% đến 67%, thể hiện mức độ tự chủ tài chính cao. Xu hướng tăng nhẹ qua các năm cho thấy doanh nghiệp ngày càng giảm phụ thuộc vào nợ và củng cố năng lực tài chính bền vững.

2.3.15 Xác định năm có doanh thu cao nhất

gmd %>%
  filter(dt == max(dt))
## # A tibble: 1 × 21
##     nam   cfo  cpql    dt  gvhb   lng  lnst   npt   qkt    tts   vcsh
##   <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>  <dbl>  <dbl>
## 1  2024 1527.  575. 4832. 2697. 2135. 1924. 4226.  64.3 17998. 13772.
## # ℹ 10 more variables: DT_Outlier <dbl[,1]>, DT_Nhom <chr>, TyLeLNST_DT <dbl>,
## #   DT_TangTruong <dbl>, GiaiDoan <chr>, ROA <dbl>, ROE <dbl>,
## #   Debt_ratio <dbl>, CFO_TTS <dbl>, Ty_le_vcsh <dbl>

Giải thích: dt == max(dt): điều kiện chọn dòng có doanh thu (dt) bằng giá trị doanh thu lớn nhất trong toàn bộ bảng.

Kết quả: Năm có doanh thu thuần cao nhất là 2024, đạt khoảng 4832.025 tỷ đồng. So với các năm trước, doanh thu năm này tăng đáng kể, cho thấy doanh nghiệp có sự tăng trưởng tốt về quy mô hoạt động, có thể nhờ mở rộng thị trường hoặc cải thiện năng suất bán hàng.

2.3.16 Năm có tổng tài sản thấp nhất

gmd %>%
  filter(tts == min(tts))
## # A tibble: 1 × 21
##     nam   cfo  cpql    dt  gvhb   lng  lnst   npt   qkt   tts  vcsh
##   <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1  2015  354.  280. 3013. 2388.  625.  565. 2961.  12.4 8180. 4878.
## # ℹ 10 more variables: DT_Outlier <dbl[,1]>, DT_Nhom <chr>, TyLeLNST_DT <dbl>,
## #   DT_TangTruong <dbl>, GiaiDoan <chr>, ROA <dbl>, ROE <dbl>,
## #   Debt_ratio <dbl>, CFO_TTS <dbl>, Ty_le_vcsh <dbl>

Giải thích: tts == min(tts): điều kiện chọn dòng có tổng tài sản (tts) bằng giá trị tổng tài sản lớn nhất trong toàn bộ bảng.

Kết quả: Năm có tổng tài sản thấp nhất là 2015, đạt khoảng 8179.782 tỷ đồng.

2.3.17 So sánh lợi nhuận gộp và chi phí quản lý

gmd %>%
  summarise(TB_LNG = mean(lng), TB_CPQL = mean(cpql))
## # A tibble: 1 × 2
##   TB_LNG TB_CPQL
##    <dbl>   <dbl>
## 1  1238.    386.

Giải thích: %>%: toán tử pipe, giúp nối các thao tác lại gọn gàng.

summarise(): dùng để tính toán tóm tắt dữ liệu.

mean(lng): tính giá trị trung bình của lợi nhuận gộp (LNG) qua các năm.

mean(cpql): tính giá trị trung bình của chi phí quản lý doanh nghiệp (CPQL) qua các năm.

Hai kết quả này được lưu tạm thành TB_LNG và TB_CPQL.

Nhận xét: Trong giai đoạn 2015–2024, lợi nhuận gộp trung bình của doanh nghiệp đạt khoảng 1237.631 tỷ đồng, trong khi chi phí quản lý doanh nghiệp bình quân chỉ ở mức 386.0574 tỷ đồng. Như vậy, lợi nhuận gộp cao gấp hơn 3 lần chi phí quản lý, cho thấy doanh nghiệp duy trì được biên lợi nhuận tốt sau khi trừ chi phí vận hành. Đây là tín hiệu tích cực, phản ánh hiệu quả hoạt động kinh doanh cốt lõi ổn định, đồng thời cho thấy doanh nghiệp kiểm soát chi phí hành chính tương đối hiệu quả.

2.3.18 Xu hướng sinh lời theo cấu trúc tài chính

gmd %>%
  mutate(Ty_le_no = npt / tts,
         Ty_le_von = vcsh / tts,
         ROA = lnst / tts,
         ROE = lnst / vcsh) %>%
  summarise(
    mean_Ty_le_no = mean(Ty_le_no, na.rm = TRUE),
    mean_Ty_le_von = mean(Ty_le_von, na.rm = TRUE),
    mean_ROE = mean(ROE, na.rm = TRUE),
    mean_ROA = mean(ROA, na.rm = TRUE)
  )
## # A tibble: 1 × 4
##   mean_Ty_le_no mean_Ty_le_von mean_ROE mean_ROA
##           <dbl>          <dbl>    <dbl>    <dbl>
## 1         0.343          0.653    0.137   0.0910

Ty_le_no (Nợ phải trả / Tổng tài sản): Cho biết tỷ trọng tài sản được tài trợ bằng nợ. Giá trị cao → doanh nghiệp phụ thuộc vào vốn vay nhiều. Ty_le_von (Vốn chủ sở hữu / Tổng tài sản): Phản ánh mức độ tự chủ tài chính của doanh nghiệp. ROA (Lợi nhuận sau thuế / Tổng tài sản): Đo hiệu quả sử dụng tài sản trong việc tạo ra lợi nhuận. ROE (Lợi nhuận sau thuế / Vốn chủ sở hữu): Đo hiệu quả sinh lời của vốn chủ sở hữu, càng cao càng tốt.

Kết quả: Trong giai đoạn phân tích, doanh nghiệp duy trì tỷ lệ nợ ở mức thấp (≈34%), giúp hạn chế rủi ro tài chính. Cùng lúc đó, ROE đạt gần 14% và ROA khoảng 10%, thể hiện khả năng sinh lời tốt từ cả tài sản lẫn vốn chủ sở hữu. Nhìn chung, doanh nghiệp có cấu trúc tài chính lành mạnh và hoạt động hiệu quả, với mức sinh lợi ổn định và khả năng tự chủ vốn cao.

2.3.19 Phân tích hiệu quả sử dụng vốn

gmd_eff <- gmd %>%
  mutate(
    Hieu_qua_TS = dt / tts,      # Hiệu suất sử dụng tài sản
    Hieu_qua_Von = dt / vcsh     # Hiệu suất sử dụng vốn CSH
  )
summary(gmd_eff$Hieu_qua_TS)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##  0.2612  0.2692  0.2913  0.3039  0.3394  0.3698
summary(gmd_eff$Hieu_qua_Von)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##  0.3509  0.3970  0.4349  0.4721  0.5438  0.6378

Nhận xét:

Hiệu suất sử dụng tài sản trung bình ~0.29 → mỗi đồng tài sản tạo ra 0.29 đồng doanh thu, hiệu quả ổn định qua các năm.

Hiệu suất sử dụng vốn chủ sở hữu trung bình ~0.46 → mỗi đồng vốn chủ tạo ra 0.46 đồng doanh thu, cao hơn hiệu suất tài sản → doanh nghiệp quản lý vốn tốt và có khả năng sinh lời cao.

2.3.20 Các chỉ tiêu của doanh thu thuần

gmd %>% 
  summarise(
    mean_dt = mean(dt, na.rm = TRUE),
    sd_dt = sd(dt, na.rm = TRUE),
    skew_dt = psych::skew(dt, na.rm = TRUE),
    kurt_dt = psych::kurtosi(dt, na.rm = TRUE),
    q1_dt = quantile(dt, 0.25, na.rm = TRUE),
    q3_dt = quantile(dt, 0.75, na.rm = TRUE)
  )
## # A tibble: 1 × 6
##   mean_dt sd_dt skew_dt kurt_dt q1_dt q3_dt
##     <dbl> <dbl>   <dbl>   <dbl> <dbl> <dbl>
## 1   3448.  731.   0.351   -1.21 2784. 3885.

Nhận xét: Doanh thu thuần của doanh nghiệp tăng trưởng khá ổn định, ít biến động cực đoan.

Phân phối hơi lệch phải, nghĩa là có vài năm đặc biệt thuận lợi, doanh thu cao đột biến so với trung bình.

Tính ổn định này phản ánh hoạt động kinh doanh bền vững và quản lý doanh thu hiệu quả.

2.3.21 Tần số LNST theo khoảng

gmd %>%
  mutate(LNST_bin = cut(lnst, breaks = 5)) %>%
  tabyl(LNST_bin) %>%
  adorn_totals("row") %>%
  adorn_pct_formatting() -> tần_số_lnst

print(tần_số_lnst)
##             LNST_bin  n percent
##            (438,859]  6   60.0%
##       (859,1.28e+03]  1   10.0%
##   (1.28e+03,1.7e+03]  0    0.0%
##   (1.7e+03,2.12e+03]  2   20.0%
##  (2.12e+03,2.54e+03]  1   10.0%
##                Total 10  100.0%

Giải thích: - mutate(LNST_bin = cut(lnst, breaks = 5)) Chia biến lnst (lợi nhuận sau thuế) thành 5 khoảng đều nhau. Mỗi khoảng thể hiện một mức độ lợi nhuận (thấp → cao). - tabyl(LNST_bin) Tạo bảng tần suất (đếm số quan sát rơi vào từng khoảng).
- adorn_totals(“row”) Thêm tổng cộng ở dòng cuối bảng.
- adorn_pct_formatting() Tính tỷ lệ phần trăm (%) cho từng khoảng lợi nhuận.

Nhận xét: Phần lớn các quan sát (60%) có lợi nhuận sau thuế (LNST) nằm trong khoảng 438–859, cho thấy doanh nghiệp chủ yếu đạt lợi nhuận thấp. Chỉ 30% có LNST trên 1,700, và không có doanh nghiệp nào trong khoảng 1,280–1,700, cho thấy phân phối lợi nhuận không đều, lệch phải, với phần lớn tập trung ở mức thấp.

2.4 TRỰC QUAN HÓA

gmd$nam <- as.factor(gmd$nam)

2.4.1 Trực quan hóa doanh thu

library(tidyverse)
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ forcats   1.0.1     ✔ readr     2.1.5
## ✔ lubridate 1.9.4     ✔ stringr   1.5.2
## ✔ purrr     1.1.0     ✔ tibble    3.2.1
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ ggplot2::%+%()   masks psych::%+%()
## ✖ ggplot2::alpha() masks psych::alpha()
## ✖ dplyr::filter()  masks stats::filter()
## ✖ dplyr::lag()     masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
ggplot(gmd, aes(x = nam, y = dt)) +
  geom_line(group = 1, color = "steelblue", size = 1.3) +
  geom_point(size = 2, color = "darkblue") +
  geom_text(label = round(gmd$dt, 1), vjust = -1.5, size = 3, color = "black") +
  scale_y_continuous(expand = expansion(mult = c(0.05, 0.2))) +
  labs(title = "Doanh thu qua các năm", x = "Năm", y = "Doanh thu (tỷ đồng)") +
  theme(plot.title = element_text(face = "bold", size = 13))

Giải thích:

library(tidyverse): Gọi thư viện tidyverse (bao gồm ggplot2, dplyr,… dùng để xử lý và trực quan hóa dữ liệu).

theme_set(theme_minimal()): Cài đặt giao diện mặc định cho biểu đồ là theme_minimal() giúp biểu đồ gọn gàng, dễ nhìn.

ggplot(gmd, aes(x = nam, y = dt)): Khởi tạo biểu đồ với dữ liệu gmd, trong đó trục hoành (x) là năm, trục tung (y) là doanh thu (dt).

geom_line(color = “steelblue”, size = 1.3): Thêm đường biểu diễn xu hướng doanh thu theo năm, màu xanh nhạt, độ dày 1.3.

geom_point(size = 3, color = “darkblue”): Thêm các điểm tròn thể hiện giá trị doanh thu từng năm, giúp người xem dễ quan sát từng mốc dữ liệu.

geom_text(aes(label = round(dt, 1)), vjust = -0.4, size = 3): Hiển thị nhãn giá trị doanh thu trên từng điểm, làm tròn 1 chữ số thập phân, đặt nhãn hơi cao hơn điểm một chút (vjust = -0.4).

labs(title = “Doanh thu qua các năm”, x = “Năm”, y = “Doanh thu (tỷ đồng)”): Đặt tiêu đề cho biểu đồ và tên cho hai trục.

theme(plot.title = element_text(face = “bold”, size = 13)): Làm đậm và tăng cỡ chữ của tiêu đề để biểu đồ nổi bật hơn.

scale_x_continuous(breaks = gmd$nam): Hiển thị đầy đủ các năm trên trục hoành, không bỏ sót năm nào.

Nhận xét biểu đồ Biểu đồ đường doanh thu theo các năm cho thấy sau giai đoạn giảm mạnh từ 2017 đến 2020, doanh thu đã hồi phục rõ ràng từ năm 2021. Hai năm gần nhất (2023-2024) doanh thu tăng vọt, đạt được cao nhất toàn kỳ. Điều này phản ánh tốc độ tăng trưởng ấn tượng của doanh nghiệp trong những năm gần đây.

2.4.2 Trực quan hóa lợi nhuận sau thuế

ggplot(gmd, aes(x = nam, y = lnst)) +
  geom_col(fill = "darkgreen", alpha = 0.7) +                      
  geom_text(aes(label = round(lnst, 1)), vjust = -0.3, size = 3) + 
  geom_smooth(method = "lm", se = FALSE, color = "red") +          
  labs(title = "Lợi nhuận sau thuế qua năm", x = "Năm", y = "LNST (tỷ đồng)") +  
  theme_minimal() +                                                
  theme(plot.title = element_text(face = "bold", color = "darkgreen"))
## `geom_smooth()` using formula = 'y ~ x'

Giải thích chi tiết:

ggplot(financial_wide, aes(x = nam, y = lnst)): Khởi tạo biểu đồ với dữ liệu từ bảng financial_wide, trong đó trục hoành (x) là năm và trục tung (y) là lợi nhuận sau thuế (lnst).

geom_col(fill = “darkgreen”, alpha = 0.7): Tạo biểu đồ cột (column chart) với màu xanh lá đậm, độ trong suốt 70% (alpha = 0.7) để thể hiện giá trị lợi nhuận từng năm.

geom_text(aes(label = round(lnst, 1)), vjust = -0.3, size = 3): Thêm nhãn giá trị trên đầu mỗi cột, làm tròn đến 1 chữ số thập phân, giúp người xem thấy rõ giá trị cụ thể của từng năm.

geom_smooth(method = “lm”, se = FALSE, color = “red”): Vẽ đường xu hướng hồi quy tuyến tính (linear model – lm) để biểu diễn xu hướng biến động của lợi nhuận qua các năm.

se = FALSE: không hiển thị vùng tin cậy (confidence interval).

color = “red”: đường xu hướng được tô đỏ để nổi bật.

labs(title = “Lợi nhuận sau thuế qua năm”, x = “Năm”, y = “LNST (tỷ đồng)”): Đặt tiêu đề cho biểu đồ và nhãn cho hai trục:

Trục X là năm

Trục Y là lợi nhuận sau thuế (tỷ đồng)

theme_minimal(): Dùng giao diện tối giản giúp biểu đồ gọn gàng, dễ đọc.

theme(plot.title = element_text(face = “bold”, color = “darkgreen”)): Làm đậm và tô màu xanh lá đậm cho tiêu đề, đồng bộ với màu của các cột để tạo sự hài hòa

Nhận xét biểu đồ Biểu đồ thể hiện lợi nhuận sau thuế qua các năm cho thấy doanh nghiệp tăng trưởng mạnh về lợi nhuận từ 2022 đến 2024, đặc biệt 2023 đạt mức cao nhất. Các năm trước đó, lợi nhuận dao động thấp và ổn định riêng năm 2018 đạt được lợi nhuận khá ổn, còn giai đoạn gần đây đã tìm thấy sự bùng nổ rõ ràng về hiệu quả kinh doanh.

2.4.3 Nợ phải trả qua các năm

ggplot(gmd, aes(x = as.factor(nam), y = npt, fill = as.factor(nam))) +
  geom_col(alpha = 0.7, show.legend = TRUE) +
  geom_hline(yintercept = median(gmd$npt), linetype = "dashed", color = "red", size = 1.2) +
  geom_point(aes(y = npt), color = "blue", size = 2.5) +
  geom_text(aes(label = round(npt, 0)),
            vjust = -0.5, size = 3, color = "black", angle = 90) + 
  labs(title = "Nợ phải trả các năm", x = "Năm", y = "NPT", fill = "Năm") +
  theme_minimal() +
  theme(legend.position = "bottom")

Giải thích chi tiết: - ggplot(gmd, aes(x = as.factor(nam), y = npt, fill = as.factor(nam))) + ggplot(gmd, aes(…)): Khởi tạo biểu đồ từ data frame gmd.

x = as.factor(nam): Trục hoành là năm, chuyển về factor (biến phân loại) để mỗi năm là một cột riêng.

y = npt: Trục tung là giá trị nợ phải trả.

fill = as.factor(nam): Mỗi năm có màu khác nhau - geom_col(alpha = 0.7, show.legend = TRUE) + geom_col(): Vẽ biểu đồ cột, chiều cao của cột thể hiện giá trị npt.

alpha = 0.7: Độ trong suốt của màu (giúp nhìn nhẹ hơn).

show.legend = TRUE: Hiển thị chú giải (legend) để phân biệt năm. - geom_hline(yintercept = median(gmd$npt), linetype = “dashed”, color = “red”, size = 1.2) + geom_hline(): Thêm đường ngang vào biểu đồ.

yintercept = median(gmd$npt): Đường nằm ở giá trị trung vị của NPT.

linetype = “dashed”: Dạng nét đứt.

color = “red”, size = 1.2: Màu đỏ, đường hơi dày — để nhấn mạnh. - geom_point(aes(y = npt), color = “blue”, size = 2.5) + geom_point(): Thêm các điểm tròn lên đầu cột, tại vị trí mỗi giá trị NPT.

color = “blue”, size = 2.5: Màu xanh, kích thước vừa phải để làm nổi bật. - geom_text(aes(label = round(npt, 0)), vjust = -0.5, size = 3, color = “black”, angle = 90) + geom_text(): Thêm nhãn (text) hiển thị giá trị cụ thể của NPT.

label = round(npt, 0): Làm tròn giá trị NPT đến số nguyên.

vjust = -0.5: Dịch chữ lên trên đầu cột.

size = 3: Kích cỡ chữ vừa phải.

color = “black”: Màu chữ đen.

angle = 90: Xoay chữ theo chiều dọc - labs(title = “Nợ phải trả các năm”, x = “Năm”, y = “NPT”, fill = “Năm”) + labs(): Thêm tiêu đề và nhãn trục.

title = “Nợ phải trả các năm” → tiêu đề biểu đồ.

x = “Năm” → tên trục hoành.

y = “NPT” → tên trục tung.

fill = “Năm” → tên chú giải - theme_minimal() + theme(legend.position = “bottom”) theme_minimal(): Giao diện gọn, sáng, dễ đọc.

theme(legend.position = “bottom”): Đưa chú giải xuống phía dưới biểu đồ thay vì mặc định ở bên phải.

Nhận xét: Biểu đồ cho thấy khoản nợ phải trả của doanh nghiệp tăng dần đều qua các năm, với một số năm như 2016, 2017 và 2022 nổi bật hơn trung bình (đường đỏ). Tuy nhiên, từ năm 2020 trở đi, nợ phải trả ổn định xung quanh ngưỡng trung bình, phản ánh doanh nghiệp kiểm soát tốt hơn đòn bẫy tài chính trong giai đoạn gần đây

gmd$nam <- as.factor(gmd$nam)

2.4.4 Trực quan hoá cấu trúc tài sản

ggplot(gmd, aes(x = nam)) +
  geom_bar(aes(y = vcsh, fill = "Vốn chủ sở hữu"), stat = "identity") +
  geom_bar(aes(y = npt, fill = "Nợ phải trả"), stat = "identity", position = "stack") +
  geom_text(aes(y = tts, label = round(tts, 0)), hjust = -0.1, size = 3) +
  scale_fill_manual(values = c("Vốn chủ sở hữu" = "darkgreen", "Nợ phải trả" = "orange")) +
  labs(title = "Cấu trúc tài sản (Nợ và vốn chủ sở hữu)", y = "Giá trị (tỷ đồng)", x = "Năm", fill = "") +
  theme_minimal() +
  coord_flip() +
  scale_y_continuous(expand = expansion(mult = c(0, 0.18)))   

Giải thích từng phần ggplot(gmd, aes(x = nam)) + Sử dụng bộ dữ liệu gmd.

x = nam: trục hoành (Năm). Vì biểu đồ sẽ được lật ngang sau đó, năm sẽ nằm trục dọc.

geom_bar(aes(y = vcsh, fill = “Vốn chủ sở hữu”), stat = “identity”) + geom_bar(): vẽ biểu đồ cột.

aes(y = vcsh): chiều cao cột theo giá trị Vốn chủ sở hữu.

fill = “Vốn chủ sở hữu”: gán màu đại diện cho nhóm này.

stat = “identity”: nghĩa là chiều cao cột chính bằng giá trị dữ liệu thực tế geom_bar(aes(y = npt, fill = “Nợ phải trả”), stat = “identity”, position = “stack”) + Vẫn là geom_bar() nhưng thêm:

y = npt → giá trị nợ phải trả.

position = “stack” → chồng phần Nợ phải trả lên phần Vốn chủ sở hữu để tạo biểu đồ cột chồng.

fill = “Nợ phải trả” → giúp tự động chia màu trong legend. geom_text(aes(y = tts, label = round(tts, 0)), hjust = -0.1, size = 3) + geom_text(): thêm nhãn chữ lên biểu đồ.

y = tts: đặt nhãn tại tổng tài sản (tổng Nợ + Vốn).

label = round(tts, 0): hiển thị tổng tài sản, làm tròn đến 0 chữ số thập phân.

hjust = -0.1: đẩy chữ sang bên phải thanh cột một chút (ra ngoài cột).

size = 3: cỡ chữ vừa phải. scale_fill_manual(values = c(“Vốn chủ sở hữu” = “darkgreen”, “Nợ phải trả” = “orange”)) + scale_fill_manual(): gán màu thủ công cho từng nhóm (thay vì mặc định ggplot).

“darkgreen” cho Vốn chủ sở hữu, “orange” cho Nợ phải trả. labs(title = “Cấu trúc tài sản (Nợ & Vốn chủ sở hữu)”, y = “Giá trị (tỷ đồng)”, x = “Năm”, fill = ““) + title: tiêu đề biểu đồ.

x, y: tên trục hoành và tung.

fill = ““: ẩn tiêu đề của chú giải (legend) để trông gọn hơn. theme_minimal() + coord_flip() + scale_y_continuous(expand = expansion(mult = c(0, 0.18))) theme_minimal(): giao diện sáng, gọn, không khung viền rườm rà.

coord_flip(): lật trục hoành và tung, giúp biểu đồ trở thành biểu đồ cột ngang (horizontal stacked bar).

scale_y_continuous(expand = expansion(mult = c(0, 0.18))): chừa khoảng trống bên phải để nhãn chữ (geom_text) không bị cắt.

Nhận xét: Biểu đồ cấu trúc tài sản trong những năm gần đây cho thấy vốn chủ sở hữu tăng nhiều hơn nợ phải trả, đặc biệt ở năm 2024 và 2023. Điều này giúp tổng tài sản doanh nghiệp tăng cường và cơ sở cấu hình tài chính an toàn hơn, phản ánh doanh nghiệp đang củng cố nội lực, giảm sự phụ thuộc vào nguồn vốn vay bên ngoài

2.4.5 Tỷ lệ nợ/tổng tài sản

ggplot(gmd, aes(x = nam, y = 100*npt/tts)) +
  geom_area(fill = "mediumorchid", alpha = 0.25) +
  geom_line(color = "deepskyblue", size = 1.5) +
  geom_point(size = 3, color = "red") +
  geom_text(aes(label = round(100*npt/tts, 1)), vjust = 1.7, color = "black", size = 3) +
  geom_hline(yintercept = median(100*gmd$npt/gmd$tts), linetype = "dotted", color = "orange", size = 1.2) +
  labs(
    title = "Tỷ lệ nợ/tổng tài sản (%) qua các năm",
    x = "Năm",
    y = "%" ) +
  theme_minimal()
## `geom_line()`: Each group consists of only one observation.
## ℹ Do you need to adjust the group aesthetic?

Giải thích từng phần ggplot(gmd, aes(x = nam, y = 100*npt/tts)) + Sử dụng bộ dữ liệu gmd.

x = nam: trục hoành là năm.

y = 100*npt/tts: trục tung là tỷ lệ Nợ phải trả / Tổng tài sản (%). geom_area(fill = “mediumorchid”, alpha = 0.25) + geom_area() tạo biểu đồ vùng (area chart): phần dưới đường được tô màu.

fill = “mediumorchid”: chọn màu tím nhạt.

alpha = 0.25: làm trong suốt 25%, giúp nhìn rõ các lớp khác chồng lên. geom_line(color = “deepskyblue”, size = 1.5) + geom_line() vẽ đường nối các điểm dữ liệu theo năm, biểu diễn xu hướng.

color = “deepskyblue”: màu xanh dương đậm.

size = 1.5: tăng độ dày đường cho nổi bật. geom_point(size = 3, color = “red”) + geom_point() vẽ các điểm tròn nhỏ tại mỗi giá trị thực.

size = 3, color = “red”: làm điểm nổi bật để người xem dễ theo dõi từng năm. geom_text(aes(label = round(100*npt/tts, 1)), vjust = -1, color = “black”, size = 3) + geom_text() thêm nhãn văn bản cho từng điểm.

label = round(100*npt/tts, 1): hiển thị giá trị tỷ lệ đã làm tròn 1 chữ số thập phân.

vjust = -1: dịch nhãn lên trên mỗi điểm.

color = “black”, size = 3: màu đen, cỡ chữ vừa. geom_hline(yintercept = median(100*gmd\(npt/gmd\)tts), linetype = “dotted”, color = “orange”, size = 1.2) + geom_hline() vẽ đường ngang tại giá trị cụ thể trên trục Y.

yintercept = median(100*gmd\(npt/gmd\)tts): vị trí đường ngang là trung vị của tỷ lệ Nợ/Tổng tài sản (%).

linetype = “dotted”: dạng nét chấm chấm.

color = “orange”, size = 1.2: màu cam, nét dày. labs( title = “Tỷ lệ Nợ/Tổng tài sản (%) qua các năm”, x = “Năm”, y = “%”, ) + theme_minimal() labs(): đặt tiêu đề và nhãn trục.

theme_minimal(): giao diện sáng.

Nhận xét: Biểu đồ thể hiện tỷ lệ nợ trên tổng tài sản của doanh nghiệp qua các năm. Nhìn chung, tỷ lệ này dao động xung quanh là 35%, nhưng có sự giảm mạnh sau năm 2021, đặc biệt là năm 2024 chỉ còn khoảng 23,5%. Điều này cho thấy doanh nghiệp đã giảm bớt sự phụ thuộc vào nợ, tăng cường an toàn tài chính trong những năm gần đây.

2.4.6 Hiệu quả sinh lời ROA và ROE

gmd$ROA <- gmd$lnst / gmd$tts
gmd$ROE <- gmd$lnst / gmd$vcsh
library(tidyr)
roa_roe_data <- pivot_longer(gmd, cols = c("ROA", "ROE"),
                             names_to = "chitieu", values_to = "ty_le") %>%
  select(nam, ty_le, chitieu)
ggplot(roa_roe_data, aes(x = nam, y = ty_le, color = chitieu, group = chitieu)) +
  geom_line(size = 1) +
  geom_point(size = 2) +
  geom_text(aes(label = round(ty_le, 2)),
            vjust = -1.5, # hoặc vjust = 2 nếu muốn cho xuống dưới điểm
            size = 2,
            color = "black") +
  geom_smooth(method = "lm", se = FALSE, linetype = "dotted", size = 1) +
  facet_wrap(~chitieu, scales = "free_y") +
  scale_y_continuous(expand = expansion(mult = c(0.05, 0.18))) +
  labs(
    title = "Hiệu quả sinh lời ROA và ROE qua các năm",
    x = "Năm", y = "Tỷ lệ (%)", color = "Chỉ tiêu") +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust = 1))
## `geom_smooth()` using formula = 'y ~ x'

Giải thích: - pivot_longer() chuyển dữ liệu từ dạng rộng (wide) sang dạng dài (long) để dễ vẽ với ggplot.

cols = c(“ROA”, “ROE”): chọn hai cột ROA và ROE để “gộp”.

names_to = “chitieu”: tên cột mới chứa tên chỉ tiêu (“ROA” hoặc “ROE”).

values_to = “ty_le”: cột mới chứa giá trị phần trăm tương ứng.

select(nam, ty_le, chitieu): chỉ giữ lại các cột cần thiết để vẽ biểu đồ. - ggplot(roa_roe_data, aes(x = nam, y = ty_le, color = chitieu, group = chitieu)) + Dữ liệu đầu vào là roa_roe_data.

x = nam: trục hoành là năm.

y = ty_le: trục tung là tỷ lệ (%) ROA hoặc ROE.

color = chitieu: màu sắc thể hiện loại chỉ tiêu (ROA, ROE).

group = chitieu: giúp ggplot hiểu rằng mỗi chỉ tiêu là một nhóm riêng. - geom_line(size = 1) + geom_line(): vẽ đường nối các điểm qua các năm.

size = 1: độ dày đường trung bình. - geom_point(size = 2) + geom_point() thêm các điểm đánh dấu từng năm.

size = 2 nhỏ hơn để không che nhãn. - geom_text(aes(label = round(ty_le, 2)), vjust = -1.5, hjust = 0, size = 3, color = “black”) + aes(label = round(ty_le, 2)): hiển thị giá trị phần trăm (làm tròn 2 số thập phân).

vjust = -1.5: đẩy nhãn lên trên điểm.

hjust = 0: đẩy nhãn sang phải một chút.

size = 2: cỡ chữ nhỏ gọn.

color = “black”: màu chữ khác màu điểm, dễ đọc. - geom_smooth(method = “lm”, se = FALSE, linetype = “dotted”, size = 1) + geom_smooth() vẽ đường hồi quy tuyến tính (linear model) qua các điểm.

method = “lm”: chọn mô hình hồi quy tuyến tính.

se = FALSE: không hiển thị khoảng tin cậy.

linetype = “dotted”: đường chấm chấm.

size = 1: độ dày vừa phải. - facet_wrap(~chitieu, scales = “free_y”) + facet_wrap() chia biểu đồ thành nhiều ô nhỏ, mỗi ô là một chỉ tiêu (ROA, ROE).

scales = “free_y”: cho phép mỗi ô có trục tung riêng (vì ROA nhỏ hơn ROE). - labs( title = “Hiệu quả sinh lời ROA & ROE qua các năm”, x = “Năm”, y = “Tỷ lệ (%)”, color = “Chỉ tiêu” ) + theme_minimal() labs(): thêm tiêu đề, nhãn trục và chú thích.

theme_minimal(): làm giao diện sáng

Nhận xét: Biểu đồ cho thấy hiệu quả sinh lời trên tài sản (ROA) và trên vốn chủ sở hữu (ROE) biến động khá mạnh qua các năm. Một số năm như 2020, 2023, 2024 đạt mức ROA và ROE cao vượt trội, trong khi các năm còn lại chỉ ở mức trung bình hoặc thấp. Về tổng quan, ROA và ROE những năm gần đây có xu hướng tăng, phản ánh doanh nghiệp đang cải thiện hiệu quả sử dụng tài sản và vốn.

2.4.7 Ma trận tương quan các biến tài chính

library(corrplot)
## corrplot 0.95 loaded
cor_matrix <- gmd %>%
  select(dt, gvhb, lnst, cfo, vcsh, npt, cpql, tts) %>%
  cor(use = "complete.obs")
corrplot(
  cor_matrix,
  method = "color",
  addCoef.col = "black",
  tl.col = "black",
  number.cex = 0.5,
  title = "Ma trận tương quan các biến tài chính",
  tl.cex = 0.7,
  mar = c(1,2,4,2),
  tl.srt = 45)

Giải thích từng phần - Tạo ma trận tương quan select(…) → chọn các cột cần tính tương quan (doanh thu, chi phí, lợi nhuận, tài sản, v.v.)

cor() → hàm tính hệ số tương quan Pearson giữa các cặp biến.

use = “complete.obs” → chỉ dùng những dòng không có giá trị NA để tránh lỗi tính toán.

Kết quả (cor_matrix) là ma trận vuông - Vẽ biểu đồ tương quan (corrplot) method = “color”

Mỗi ô được tô màu theo giá trị tương quan:

Xanh dương → tương quan dương (cùng tăng/giảm)

Đỏ → tương quan âm (ngược chiều)

Màu càng đậm → tương quan càng mạnh (|r| gần 1)

🔹 addCoef.col = “black”

Hiển thị giá trị số (hệ số r) trong từng ô bằng màu đen.

🔹 tl.col = “black”

Màu chữ tên biến (label ở trục X, Y).

🔹 number.cex = 0.5

Kích thước chữ hiển thị hệ số (nhỏ gọn hơn).

🔹 title = “Ma trận tương quan các biến tài chính”

Thêm tiêu đề cho biểu đồ.

🔹 tl.cex = 0.7

Giảm kích thước nhãn tên biến (đỡ bị đè nhau).

🔹 mar = c(1,2,4,2)

Tùy chỉnh lề (margin) của biểu đồ: (trên, phải, dưới, trái) → giúp tiêu đề không bị che.

🔹 tl.srt = 45

Xoay tên biến 45 độ để dễ đọc hơn.

Nhận xét: Ma trận tương quan cho thấy nhiều biến tài sản chính có mối liên hệ chặt chẽ với nhau, đặc biệt doanh thu (dt), lợi nhuận (lnst), dòng tiền kinh doanh (cfo) và tổng tài sản (tts) đều có hệ số tương quan rất cao, thường lớn hơn 0,8. Một số biến thể như giá vốn hàng bán (gvhb) có mối quan hệ tương quan âm với lnst, phản ánh đặc tính kế toán và vận hành doanh nghiệp.

2.4.8 Trực quan hóa giữa LNST và CFO

library(tidyr)
gmd_long2 <- pivot_longer(gmd, cols = c("lnst", "cfo"), names_to = "Biến", values_to = "Giá_trị")
ggplot(gmd_long2, aes(x = as.factor(nam), y = Giá_trị, fill = Biến)) +
  geom_bar(stat = "identity", position = "dodge") +
  coord_flip() +
  geom_text(aes(label = scales::comma(Giá_trị)), 
            position = position_dodge(width = 1), hjust = -0.1, size = 3) +
  scale_fill_manual(values = c("lnst" = "dodgerblue", "cfo" = "limegreen")) +
  labs(title = "So sánh LNST và CFO qua các năm",
       x = "Năm", y = "Giá trị", fill = "Biến") +
  theme_minimal()

pivot_longer() biến đổi bảng dữ liệu từ dạng wide → long. - ggplot(gmd_long2, aes(x = as.factor(nam), y = Giá_trị, fill = Biến)) + x = năm (dạng factor, rời rạc).

y = giá trị (lnst hoặc cfo).

fill = Biến → màu sắc khác nhau cho từng biến (“lnst” và “cfo”). - geom_bar(stat = “identity”, position = “dodge”) stat = “identity” → giá trị cột = đúng bằng Giá_trị.

position = “dodge” → hai cột đặt cạnh nhau để dễ so sánh. - coord_flip() Lật biểu đồ thành cột ngang, dễ đọc tên năm và nhãn. - geom_text(aes(label = scales::comma(Giá_trị)), position = position_dodge(width = 1), hjust = -0.1, size = 3) label = scales::comma(Giá_trị) → hiển thị số có dấu phẩy phân cách (vd: 1,234).

position_dodge(width = 1) → căn đúng vị trí theo từng cột.

hjust = -0.1 → đẩy nhãn ra ngoài đầu cột.

size = 3 → cỡ chữ vừa phải. - scale_fill_manual(values = c(“lnst” = “dodgerblue”, “cfo” = “limegreen”)) Chọn màu xanh dương cho LNST và xanh lá sáng cho CFO - labs(title = “So sánh LNST & CFO qua các năm”, x = “Năm”, y = “Giá trị”, fill = “Biến”) Tạo tiêu đề, nhãn trục X/Y, và tên chú giải. - theme_minimal() Nền trắng sáng

Nhận xét: Biểu đồ so sánh được hiển thị trong hầu hết các năm, thu lợi sau thuế (LNST) thường cao hơn dòng tiền từ hoạt động kinh doanh (CFO). Tuy nhiên, các năm như 2021 và 2022, CFO lại vượt qua LNST, chứng tỏ doanh nghiệp có khả năng tạo ra dòng tiền thực sự tốt hơn so với kết quả lợi nhuận ghi nhận, điều này là tín hiệu tích cực về mặt tài chính.

2.4.9 HIỆU QUẢ SỬ DỤNG TÀI SẢN

library(ggplot2)
gmd %>%
  mutate(HQ_TS = dt / tts * 100) %>%
  ggplot(aes(x = as.factor(nam), y = HQ_TS)) +
  geom_segment(aes(xend = as.factor(nam), y = 0, yend = HQ_TS), color = "lightgrey", size = 2) +
  geom_point(size = 4, color = "#1f77b4") +
  geom_text(aes(label = round(HQ_TS, 1)),
          vjust = -1.7,
          nudge_y = 0.6,
          size = 3, color = "black") +
scale_y_continuous(expand = expansion(mult = c(0, 0.18))) +
  labs(title = "Hiệu quả sử dụng tài sản theo năm (Lollipop chart)",
       x = "Năm", y = "%") +
  theme_classic()

mutate(): thêm một biến mới vào bảng dữ liệu gmd.

HQ_TS = dt / tts * 100 → Tính hiệu quả sử dụng tài sản (%) - ggplot(aes(x = as.factor(nam), y = HQ_TS)) x: Năm (nam) chuyển sang dạng factor để vẽ rời rạc.

y: Giá trị hiệu quả sử dụng tài sản (%). → Mỗi năm sẽ có một “kẹo mút” riêng. - geom_segment(aes(xend = as.factor(nam), y = 0, yend = HQ_TS), color = “lightgrey”, size = 2) Vẽ đường thẳng từ 0 → giá trị HQ_TS cho từng năm.

Giống như “thân cây kẹo”.

color = “lightgrey” → màu xám nhạt, size = 2 → độ dày của thanh. - geom_point(size = 4, color = “#1f77b4”) Vẽ điểm tròn trên đầu mỗi thanh — chính là giá trị HQ_TS.

color = “#1f77b4” → màu xanh dương đậm, dễ nhìn.

size = 4 → kích thước điểm. - geom_text(aes(label = round(HQ_TS, 1)), vjust = -1.7, nudge_y = 0.6, size = 3, color = “black”) label = round(HQ_TS, 1) → hiển thị số, làm tròn 1 chữ số sau dấu phẩy.

vjust = -1.7 và nudge_y = 0.6 → đẩy nhãn lên phía trên đầu điểm.

color = “black” → màu chữ đen, size = 3 → vừa phải - labs(title = “Hiệu quả sử dụng tài sản theo năm (Lollipop chart)”, x = “Năm”, y = “%”) Đặt tiêu đề biểu đồ, nhãn trục X, trục Y. - theme_classic(): Dùng theme cổ điển: nền trắng, khung mảnh, trục rõ ràng

Nhận xét: Biểu đồ Lollipop có thể hiện hiệu quả sử dụng tài sản của doanh nghiệp theo từng năm. Giai đoạn 2015–2017 đạt hiệu quả cao nhất (trên 35%), nhưng sau đó giảm dần và ổn định xung quanh mức 26–30% từ năm 2018 đi. Điều này cho thấy doanh nghiệp cần chú ý nâng cao hiệu quả sử dụng tài sản trong các năm để nâng cao khả năng sinh lời.

2.4.10 HIỆU QUẢ SỬ DỤNG VỐN CHỦ

library(ggplot2)
library(dplyr)
gmd %>%
  mutate(HQ_VC = dt / vcsh * 100) %>%
  mutate(ty_trong = round(HQ_VC / sum(HQ_VC, na.rm = TRUE) * 100, 1)) %>%
  ggplot(aes(x = "", y = ty_trong, fill = factor(nam))) +
  geom_col(width = 1, color = "white") +
  coord_polar(theta = "y") +
  geom_text(aes(label = paste0(ty_trong, "%")), 
            position = position_stack(vjust = 0.5),
            size = 3, color = "white") +
  scale_fill_viridis_d(name = "Năm") +
  labs(title = "Tỷ trọng hiệu quả sử dụng vốn chủ sở hữu theo năm") +
  theme_void() +
  theme(
    plot.title = element_text(face = "bold", hjust = 0.5, size = 12),
    legend.position = "right")

ggplot2: Dùng để vẽ biểu đồ.

dplyr: Dùng để xử lý, tính toán dữ liệu trước khi vẽ. - mutate(HQ_VC = dt / vcsh * 100) Tính tỷ lệ Doanh thu (dt) trên Vốn chủ sở hữu (vcsh) nhân 100 → thể hiện hiệu quả sinh lời của vốn chủ sở hữu theo %.

Tạo thêm cột mới HQ_VC trong bảng gmd. - mutate(ty_trong = round(HQ_VC / sum(HQ_VC, na.rm = TRUE) * 100, 1)) Chia HQ_VC của từng năm cho tổng HQ_VC của tất cả các năm → tính ra tỷ trọng (%).

Nhân 100 để đổi sang %.

round(…, 1) → làm tròn 1 chữ số thập phân.

Lưu kết quả vào cột ty_trong. - ggplot(aes(x = ““, y = ty_trong, fill = factor(nam))) Trục x =”” vì biểu đồ tròn không cần trục hoành.

y = ty_trong → độ lớn của từng phần (miếng) trong hình tròn.

fill = factor(nam) → tô màu khác nhau cho từng năm. - geom_col(width = 1, color = “white”) Vẽ các cột đứng theo tỷ trọng.

Sau này khi dùng coord_polar() → chúng sẽ thành miếng hình tròn.

color = “white” tạo đường phân cách trắng giữa các miếng. - coord_polar(theta = “y”) Dùng hệ toạ độ cực (polar coordinates) → chuyển biểu đồ cột thành biểu đồ tròn.

theta = “y” nghĩa là độ lớn của mỗi phần được tính theo giá trị trục y (ty_trong). - geom_text(aes(label = paste0(ty_trong, “%”)), position = position_stack(vjust = 0.5), size = 3, color = “white”) Hiển thị tỷ lệ % lên giữa từng miếng.

position_stack(vjust = 0.5) → căn chữ vào giữa miếng.

color = “white” giúp chữ nổi bật trên nền màu. - scale_fill_viridis_d(name = “Năm”) Dùng bảng màu viridis (màu đẹp, thân thiện cho người mù màu).

name = “Năm” là tiêu đề của chú giải (legend). - labs() → đặt tiêu đề cho biểu đồ.

theme_void() → loại bỏ toàn bộ trục, lưới, nền → chỉ giữ biểu đồ tròn.

theme() → định dạng tiêu đề in đậm, căn giữa (hjust = 0.5), tăng kích thước, và đặt chú giải sang phải.

Nhận xét: Biểu đồ tròn cho thấy hiệu quả sử dụng vốn chủ sở hữu phân tích bổ sung tương đối đồng đều qua các năm, không có năm nào sử dụng tỷ lệ nổi trội. Tuy nhiên, các năm đầu giai đoạn (2015–2017) có tỷ lệ cao hơn một chút so với các năm gần đây, Phản ánh hiệu quả sử dụng vốn có xu hướng giảm nhẹ hoặc đi ngang qua thời gian gần đây.

2.4.11 Boxplot nợ phải trả từng năm

ggplot(gmd, aes(x = nam, y = npt, fill = as.factor(nam))) +
  geom_boxplot() +
  stat_summary(fun = mean, geom = "point", shape = 20, size = 3, color = "red", fill = "red") +
  labs(title = "Boxplot nợ phải trả từng năm", fill = "Năm", x = "Năm", y = "NPT") +
  theme_light() +
  theme(axis.title.x = element_blank())

  • ggplot(gmd, aes(x = nam, y = npt, fill = as.factor(nam))) + x = nam: trục hoành là năm.

y = npt: trục tung là nợ phải trả.

fill = as.factor(nam): mỗi năm có màu riêng (đổi sang factor để ggplot hiểu là biến phân loại). - Vẽ hộp boxplot: geom_boxplot() - stat_summary(fun = mean, geom = “point”, shape = 20, size = 3, color = “red”, fill = “red”) stat_summary(): thêm thống kê bổ sung (ở đây là mean).

geom = “point”: vẽ bằng điểm.

shape = 20: chấm tròn đặc.

color = “red”: màu đỏ nổi bật. - labs() đặt tiêu đề, nhãn trục, và tên chú thích. - theme_light(): bố cục sáng, đường kẻ rõ.

axis.title.x = element_blank(): ẩn tên trục X cho gọn.

Nhận xét: Biểu đồ boxplot cho thấy nợ phải trả có xu hướng tăng dần qua các năm, đặc biệt tăng mạnh từ năm 2021 trở đi. Các giá trị trung vị và phân chia nợ đều cao hơn những năm trước, phản ánh doanh nghiệp sử dụng đòn bẫy tài chính lớn hơn trong giai đoạn gần đây. Đây có thể là dấu hiệu của quy mô mở rộng chiến lược hoặc đáp ứng nhu cầu tăng vốn

2.4.12 Line chart xu hướng doanh thu/lợi nhuận qua các năm

ggplot(gmd, aes(x = as.factor(nam))) +
  geom_line(aes(y = dt, color = "Doanh thu"), group = 1, size = 1.2) +
  geom_point(aes(y = dt, color = "Doanh thu"), size = 2.5) +
  geom_line(aes(y = lnst, color = "Lợi nhuận"), group = 1, size = 1.2) +
  geom_point(aes(y = lnst, color = "Lợi nhuận"), size = 2.5) +
  scale_color_manual(values = c("Doanh thu" = "blue", "Lợi nhuận" = "orange")) +
  labs(
    title = "Xu hướng doanh thu và lợi nhuận qua các năm",
    x = "Năm", y = "Giá trị",
    color = "Chỉ tiêu") +
  theme_minimal()

ggplot(gmd, aes(x = as.numeric(as.character(nam))))

Tạo khung dữ liệu cho biểu đồ:

Chuyển nam sang dạng số, để trục X hiển thị đúng thứ tự năm.

geom_line(aes(y = dt, color = “Doanh thu”))

Vẽ đường biểu diễn doanh thu qua các năm, màu được gán nhãn “Doanh thu”.

geom_line(aes(y = lnst, color = “Lợi nhuận”))

Vẽ đường biểu diễn lợi nhuận sau thuế (LNST), màu gán nhãn “Lợi nhuận”.

labs(title = “Xu hướng doanh thu & lợi nhuận qua các năm”, x = “Năm”)

Thêm tiêu đề và tên trục X cho biểu đồ.

theme_minimal()

Áp dụng giao diện tối giản, giúp biểu đồ rõ ràng và hiện đại.

scale_color_manual(values = c(“Doanh thu” = “blue”, “Lợi nhuận” = “orange”))

Tùy chỉnh màu sắc thủ công:

Màu xanh cho doanh thu,

Màu cam cho lợi nhuận.

Nhận xét biểu đồ Xu hướng doanh thu: Đường màu xanh biểu hiện diễn doanh thu tăng khá đều qua các năm và có xu hướng tăng mạnh ở giai đoạn cuối kỳ (từ 2021 trở đi). Điều này cho thấy doanh nghiệp duy trì tốt tốc độ tăng trưởng về quy mô hoạt động.

Xu hướng thu lợi: Đường màu vàng (lợi nhuận) biến hoàn hảo hơn. Sau giai đoạn tăng từ 2015–2017, lợi nhuận giảm mạnh, nhất là khoảng 2019–2020, sau đó hồi phục tăng nhưng vẫn chưa đạt được mức trưởng tăng ổn định như doanh thu.

So sánh: Doanh thu tăng ổn định nhưng thu lợi nhuận từ biến động và khả năng không theo tỷ lệ tương thích doanh thu, mẹo về việc biến động chi phí, giá vốn hoặc vấn đề quản lý.

Ý nghĩa: Biểu đồ này giúp nhận diện rủi ro về tài chính ngắn hạn (biến động lợi nhuận), đồng thời khẳng định cường độ dài hạn (tăng trưởng doanh thu đều), từ đó định hướng phân tích sâu nguyên biến nhân động ở các giai đoạn.

Kết luận: Biểu đồ cho thấy công ty có xu hướng tăng trưởng tốt về doanh thu nhưng cần quan tâm cải thiện kiểm soát chi phí hoặc tối ưu hóa hoạt động thúc đẩy tăng lợi nhuận tương ứng về thời gian dài hạn.

2.4.13 Barplot tăng trưởng phần trăm doanh thu

gmd$DT_TangTruong_pct <- c(NA, diff(gmd$dt)/head(gmd$dt, -1) * 100)
ggplot(data = gmd[-1, ], aes(x = factor(nam), y = DT_TangTruong_pct, fill = factor(nam))) +
  geom_bar(stat = 'identity') +
  scale_fill_brewer(palette = 'Pastel1') +
  geom_text(
    aes(label = paste0(round(DT_TangTruong_pct, 2), '%')),
    angle = 90,         # Xoay số nằm dọc
    vjust = -0.3,       # Đặt nhãn lên phía trên cột
    color = 'black', size =2) +
  scale_y_continuous(expand = expansion(mult = c(0.05, 0.18))) +
  labs(title = 'Tăng trưởng DT (%) qua các năm',
       x = 'Năm', y = 'Phần trăm tăng trưởng', fill = 'Năm') +
  theme_minimal()

- Tính tốc độ tăng trưởng doanh thu: gmd\(DT_TangTruong_pct <- c(NA, diff(gmd\)dt)/gmd$dt[-nrow(gmd)]*100)

  • ggplot(gmd[-1,], aes(x = factor(nam), y = DT_TangTruong_pct, fill = factor(nam))) + geom_bar(stat = ‘identity’) gmd[-1,]: loại năm đầu vì có NA.

geom_bar(stat=‘identity’): biểu đồ cột, chiều cao bằng giá trị thật (không đếm số lượng).

fill = factor(nam): mỗi năm có một màu khác nhau. - scale_fill_brewer(palette=“Pastel1”): dùng bảng màu pastel nhẹ nhàng.

labs(): thêm tiêu đề và tên trục.

theme_minimal(): bố cục đơn giản, chuyên nghiệp. - geom_text(aes(label = paste0(round(DT_TangTruong_pct, 2), ‘%’)), vjust = 1.0, color = ‘black’) Hiển thị nhãn phần trăm trên mỗi cột.

vjust = 1.0 đặt chữ trong thân cột, nếu muốn bên trên thì dùng vjust = -0.5

Nhận xét: Biểu đồ cho thấy tốc độ tăng trưởng doanh thu qua các năm biến động mạnh: có năm tăng rất cao như 2016, 2021, 2022 và 2024 (trên 20%), nhưng cũng có năm giảm đáng kể, đặc biệt năm 2018 giảm hơn 32%. Điều này phản ánh ánh hoạt động kinh doanh chưa ổn định giữa các năm, tuy nhiên những năm gần đây doanh thu đang tăng tích cực trở lại.

2.4.14 Pie chart tỷ trọng LNST các năm

df_pie <- gmd %>%
  mutate(LNST_share = lnst / sum(lnst)) %>%
  select( nam, LNST_share)
ggplot(df_pie, aes(x='', y=LNST_share, fill=factor(nam))) +
  geom_bar(stat='identity', width=1) +
  coord_polar('y') +
  labs(title='Tỷ trọng LNST từng năm', fill="Năm") +
  theme_void() +
  geom_text(aes(
    label = paste0(round(LNST_share*100,1),'%'),
    angle = 90
  ), position = position_stack(vjust = 0.5))

- mutate(LNST_share = lnst / sum(lnst)) → tính tỷ trọng LNST của từng năm so với tổng toàn bộ.

select(nam, LNST_share) → chỉ lấy 2 cột cần thiết: năm và tỷ trọng. - Tạo biểu đồ tròn: geom_bar(stat=‘identity’) → vẽ cột với chiều cao bằng giá trị thật (LNST_share).

coord_polar(‘y’) → biến cột thành hình tròn theo trục y → pie chart.

x=’’ → vì không cần chia trục x (chỉ có một vòng tròn). - labs(title = ‘Tỷ trọng LNST từng năm’, fill = “Năm”) + theme_void() labs() → thêm tiêu đề và legend.

theme_void() → loại bỏ tất cả đường viền, trục, lưới - paste0(round(LNST_share*100,1),‘%’) → hiển thị phần trăm.

position_stack(vjust=0.5) → căn giữa nhãn trong mỗi miếng.

angle = 90 → xoay nhãn 90 độ (đẹp hơn nếu phần trăm lớn).

Nhận xét: Biểu đồ tròn cho thấy tỷ lệ lợi nhuận sau thuế phân tích không đều qua các năm. Một số năm như 2023 và 2024 có tỷ lệ LNST rất lớn (trên 17%), trong khi các năm trước đó có tỷ lệ chênh lệch nhỏ hơn. Điều này phản ánh doanh nghiệp đạt được lợi nhuận vượt trội ở các năm gần đây, cho thấy sự tăng trưởng mạnh về hiệu quả kinh doanh so với những năm trước đó.

2.4.15 Violin plot DT theo nhóm năm

ggplot(gmd, aes(x=GiaiDoan, y=dt, fill=GiaiDoan)) +
  geom_violin() +
  geom_boxplot(width=0.1) +
  geom_jitter(width=0.15) +
  labs(title="Violin plot DT theo nhóm năm") +
  theme_minimal() +
  scale_fill_manual(values=c("blue", "pink"))

- ggplot(gmd, aes(x = GiaiDoan, y = dt, fill = GiaiDoan)) x = GiaiDoan → trục hoành là nhóm giai đoạn (ví dụ: “2015–2019”, “2020–2024”).

y = dt → trục tung là Doanh thu (dt).

fill = GiaiDoan → mỗi giai đoạn có màu riêng - geom_violin() hiển thị dạng “đàn violin” biểu diễn mật độ phân phối dữ liệu (tương tự biểu đồ mật độ nhưng dựng đứng). - geom_boxplot(width = 0.1) geom_boxplot() được chèn lên giữa violin, giúp thấy:

Trung vị (median),

Khoảng tứ phân vị (IQR),

Ngoại lệ (outlier).

width = 0.1 → làm hộp nhỏ gọn, không che hết hình violin. - geom_jitter(width = 0.15) geom_jitter() → thêm từng điểm dữ liệu thực (mỗi quan sát).

width = 0.15 → làm điểm lệch nhẹ theo chiều ngang để tránh chồng lên nhau. - labs(title = “Violin plot DT theo nhóm năm”) + theme_minimal() labs() → thêm tiêu đề.

theme_minimal() → giao diện sáng - scale_fill_manual(values = c(“blue”, “pink”)) Gán màu tùy ý cho từng nhóm “GiaiDoan”.

Nhận xét: Biểu đồ violin so sánh doanh thu giữa hai giai đoạn cho thấy sau năm 2020, doanh thu có xu hướng cao và phân tán rộng hơn so với trước năm 2020. Điều này phản ánh doanh nghiệp tăng trưởng tốt hơn ở giai đoạn sau 2020, với doanh thu trung bình và biên độ động đều tăng cường tối ưu.

2.4.16 Ảnh hưởng nhóm tài sản đến LNST

library(dplyr)
gmd$TTS_group <- ifelse(gmd$tts > median(gmd$tts), "Tài sản lớn", "Tài sản nhỏ")
ggplot(gmd, aes(x = TTS_group, y = lnst, fill = TTS_group)) +
  geom_boxplot(alpha = 0.5) +
  geom_jitter(width = 0.1, color = "black", size = 2) +
  labs(
    title = "Ảnh hưởng nhóm tài sản lớn/nhỏ đến LNST",
    x = "",
    y = "LNST" ) +
  theme_minimal() +
  theme(legend.position = "none")

  • gmd\(TTS_group <- ifelse(gmd\)tts > median(gmd$tts), “Tài sản lớn”, “Tài sản nhỏ”) ifelse(): chia dữ liệu thành hai nhóm dựa trên giá trị median (trung vị) của tổng tài sản tts.

Nếu tts > trung vị → gán “Tài sản lớn”, ngược lại “Tài sản nhỏ”.

Tạo biến mới TTS_group dùng cho phân tích nhóm. - ggplot(gmd, aes(x = TTS_group, y = lnst, fill = TTS_group)) + geom_boxplot(alpha = 0.5) x = TTS_group → trục hoành là nhóm tài sản (“lớn” vs “nhỏ”).

y = lnst → trục tung là lợi nhuận sau thuế.

fill = TTS_group → tô màu theo nhóm.

geom_boxplot(alpha = 0.5) → vẽ hộp biểu diễn phân phối dữ liệu (giá trị trung vị, tứ phân vị, ngoại lệ).

alpha = 0.5 làm hộp trong suốt nhẹ, giúp nhìn rõ điểm chồng lên. - geom_jitter(width = 0.1, color = “black”, size = 2) geom_jitter() → thêm các điểm dữ liệu thật, hơi lệch ngang để tránh chồng.

width = 0.1 → điều chỉnh độ lệch ngang giữa các điểm.

color = “black” → màu viền đen.

size = 2 → điểm vừa phải, dễ đọc. - labs( title = “Ảnh hưởng nhóm tài sản lớn/nhỏ đến LNST”, x = ““, y =”LNST” ) title → tiêu đề rõ nghĩa.

x = “” → bỏ nhãn trục hoành (vì nhóm đã rõ rồi).

y = “LNST” → trục tung là lợi nhuận sau thuế. - theme_minimal() + theme(legend.position = “none”) theme_minimal() → loại bỏ nền, làm biểu đồ sáng, chuyên nghiệp.

legend.position = “none” → ẩn chú thích vì hai nhóm đã thể hiện trên trục X rồi.

Nhận xét: Biểu đồ cho thấy nhóm có tổng tài sản đạt lợi nhuận sau thuế (LNST) cao và biến động nhiều hơn so với nhóm tài sản nhỏ. Nhóm tài sản nhỏ chủ yếu tập trung ở tốc độ lợi nhuận thấp và ổn định, trong khi nhóm tài sản lớn có giá trị LNST cao vượt trội. Điều này chứng minh, quy mô tài sản giúp doanh nghiệp có khả năng tạo ra lợi nhuận tốt hơn, dù mức độ biến động cũng cao hơn.

2.4.17 Ảnh hưởng của chi phí quản lý đến LNST

library(ggplot2)
ggplot(gmd, aes(x = cpql, y = lnst, color = as.factor(nam))) +
  geom_point(size = 4, alpha = 0.95) +                             
  geom_smooth(method = "lm", se = TRUE, color = "firebrick", fill = "orange", alpha = 0.22) + 
  scale_color_brewer(palette = "Dark2") +                           
  labs(
    title = "Ảnh hưởng chi phí quản lý lên LNST",
    x = "Chi phí quản lý (tỷ đồng)",
    y = "Lợi nhuận sau thuế (LNST, tỷ đồng)",
    color = "Năm") +                                                               
  theme_minimal() +                                                 
  theme(legend.position = "bottom")
## `geom_smooth()` using formula = 'y ~ x'
## Warning in RColorBrewer::brewer.pal(n, pal): n too large, allowed maximum for palette Dark2 is 8
## Returning the palette you asked for with that many colors
## Warning: Removed 2 rows containing missing values or values outside the scale range
## (`geom_point()`).

Giải thích: - ggplot(gmd, aes(x = cpql, y = lnst, color = as.factor(nam))) gmd → bảng dữ liệu gốc.

aes(x = cpql, y = lnst) → trục hoành là Chi phí quản lý, trục tung là Lợi nhuận sau thuế.

color = as.factor(nam) → đổi năm sang dạng nhóm phân loại (factor) để tô màu khác nhau cho từng năm. - geom_point(size = 4, alpha = 0.95) geom_point() → vẽ các điểm biểu diễn từng quan sát.

size = 4 → kích thước điểm tương đối lớn để dễ nhìn.

alpha = 0.95 → độ trong suốt gần như đặc, giúp màu nổi bật hơn. - geom_smooth(method = “lm”, se = TRUE, color = “firebrick”, fill = “orange”, alpha = 0.22) method = “lm” → vẽ đường hồi quy tuyến tính (linear model).

se = TRUE → hiển thị vùng tin cậy 95% (confidence interval).

color = “firebrick” → đường hồi quy màu đỏ đậm.

fill = “orange” → vùng tin cậy màu cam nhạt.

alpha = 0.22 → làm vùng tin cậy mờ nhẹ - scale_color_brewer(palette = “Dark2”) Sử dụng bảng màu “Dark2” trong bộ RColorBrewer → màu đậm - labs( title = “Ảnh hưởng chi phí quản lý lên LNST”, x = “Chi phí quản lý (tỷ đồng)”, y = “Lợi nhuận sau thuế (LNST, tỷ đồng)”, color = “Năm” ) title → tiêu đề biểu đồ.

x, y → nhãn trục rõ ràng, đơn vị cụ thể.

color = “Năm” → tiêu đề chú thích cho màu sắc. - theme_minimal() + theme(legend.position = “bottom”) theme_minimal() → loại bỏ nền và khung, giữ biểu đồ sáng, sạch.

legend.position = “bottom” → đưa chú thích (legend) xuống dưới cho cân đối.

Nhận xét: Biểu đồ thể thể hiện sự liên hệ giữa chi phí quản lý và thuế sau thuế (LNST), trong đó mỗi điểm là một năm. Đường màu đỏ cho thấy xu hướng chung là chi phí quản lý càng cao thì LNST thường càng lớn, phản ánh ánh quy mô doanh nghiệp mở rộng thì kết quả kinh doanh cũng tốt hơn. Vùng màu cam thể hiện độ tin cậy của xu hướng, vùng này cho thấy số lượng biến thể dữ liệu rộng hơn, nhưng không thực sự đồng đều. Một số năm có chi phí quản lý cao nhưng LNST chưa tăng mạnh, cần chú ý hiệu quả sử dụng chi phí. Nhìn chung, chi phí quản lý tăng đi kèm với LNST tăng, nhưng xu hướng này còn dao động tùy theo từng năm nên doanh nghiệp cần quản lý chi phí hiệu quả để đảm bảo lợi nhuận ổn định.

2.4.18 Ảnh hưởng vốn chủ sở hữu và TTS lên LNST

ggplot(gmd, aes(x = vcsh, y = lnst, size = tts, color = nam)) +
  geom_point(alpha = 0.6) +                                
  geom_smooth(method = "lm", se = FALSE, linetype = "dashed", color = "black", size = 1.1) +  
  scale_size_continuous(name = "Tổng tài sản") +           
  labs(title = "Ảnh hưởng vốn chủ sở hữu và TTS lên LNST", x = "Vốn chủ sở hữu", y = "LNST", color = "Năm") + 
  theme_minimal()
## `geom_smooth()` using formula = 'y ~ x'

Giải thích: - ggplot(gmd, aes(x = vcsh, y = lnst, size = tts, color = nam)) x = vcsh → trục hoành là vốn chủ sở hữu

y = lnst → trục tung là lợi nhuận sau thuế

size = tts → kích thước bong bóng biểu diễn tổng tài sản, công ty/năm nào có tổng tài sản lớn hơn → bong bóng to hơn

color = nam → màu sắc đại diện cho năm, giúp so sánh qua các kỳ - geom_point(alpha = 0.6) geom_point() → thêm các điểm dữ liệu lên đồ thị

alpha = 0.6 → làm bong bóng hơi trong suốt để nếu chồng lên nhau vẫn thấy rõ - geom_smooth(method = “lm”, se = FALSE, linetype = “dashed”, color = “black”, size = 1.1) geom_smooth(method = “lm”) → vẽ đường hồi quy tuyến tính (Linear Model) thể hiện xu hướng chung

se = FALSE → tắt vùng tin cậy xung quanh đường

linetype = “dashed” → đường nét đứt

color = “black” → màu đen nổi bật

size = 1.1 → độ dày vừa phải - scale_size_continuous(name = “Tổng tài sản”) Điều chỉnh chú thích (legend) của kích thước bong bóng

Thêm tiêu đề “Tổng tài sản” để người xem hiểu kích thước đại diện cho biến gì - labs(title = “Ảnh hưởng vốn chủ sở hữu và TTS lên LNST”, x = “Vốn chủ sở hữu”, y = “LNST”, color = “Năm”) Thêm tiêu đề, tên trục, và tiêu đề chú thích màu sắc (legend)

Giúp biểu đồ rõ nghĩa và chuyên nghiệp hơn.

Kết quả: Biểu đồ thể hiện mối quan hệ giữa vốn sở hữu và LNST, với kích thước bong bóng biểu thị tổng tài sản và màu sắc theo từng năm. Kết quả cho thấy chủ sở hữu vốn tăng lên thì LNST cũng có xu hướng tăng theo, đồng thời các dữ liệu của những năm gần đây có tổng tài sản lớn hơn, góp phần nâng cao LNST. Đường xu hướng cắt lên càng nhanh càng thể hiện liên kết tích cực giữa hai biến này, phản ánh ánh hoạt động tài chính chính của doanh nghiệp đang tích cực và quy mô mở rộng qua thời gian.

2.4.19 Biểu đồ phân bổ tổng tài sản

library(treemapify)

ggplot(gmd, aes(area = tts, fill = as.factor(nam), label = paste0(nam, "\n", round(tts,0)))) +
  geom_treemap(alpha = 0.8) +                                                    
  geom_treemap_text(colour = "black", place = "centre") +                        
  scale_fill_brewer(palette = "Pastel1") +                                       
  labs(title = "Treemap phân bổ tổng tài sản các năm", fill = "Năm") +           
  theme_void()  
## Warning in RColorBrewer::brewer.pal(n, pal): n too large, allowed maximum for palette Pastel1 is 9
## Returning the palette you asked for with that many colors

Giải thích: - khai báo khung ggplot trước khi thêm các lớp geom_treemap(), geom_treemap_text(), v.v - geom_treemap(alpha = 0.8) Vẽ các ô hình chữ nhật tương ứng với từng năm.

Diện tích ô ∝ tổng tài sản (tts) → năm nào tài sản lớn hơn, ô to hơn.

alpha = 0.8 giúp màu nhẹ - geom_treemap_text(colour = “black”, place = “centre”) Hiển thị năm + giá trị tài sản bên trong mỗi ô.

colour = “black”: chữ đen, dễ đọc.

place = “centre”: căn giữa nhãn trong ô - scale_fill_brewer(palette = “Pastel1”) Dùng bảng màu nhẹ nhàng, không quá chói, phù hợp cho treemap - labs(title = “Treemap phân bổ Tổng tài sản các năm”, fill = “Năm”) title: tiêu đề của biểu đồ.

fill: tên chú thích màu (legend). - theme_void(): Ẩn toàn bộ lưới, trục, và khung → chỉ giữ lại các khối treemap.

Kết quả: Biểu đồ cây có thể hiện tổng tài sản các năm đều tăng qua từng giai đoạn, nổi bật nhất là năm 2024 với giá trị tài sản cao nhất. Các năm 2022, 2023 cũng đạt được tài sản lớn, trong khi năm 2015 có giá trị thấp nhất. Nhìn chung, tổng tài sản tăng dần theo thời gian, chứng tỏ doanh nghiệp phát triển ổn định và mở rộng quy mô hoạt động.

2.4.20 Quan hệ giữa Doanh thu và CFO

ggplot(gmd, aes(x = dt, y = cfo)) +
  geom_point(size = 3, color = "#46aee6", alpha = 0.75) +    
  geom_smooth(method = "lm", se = FALSE, color = "#de4466", size = 1.2) + 
  geom_text(aes(label = nam), vjust = -1, size = 3, color = "black") +    
  labs(
    title = "Quan hệ giữa doanh thu và CFO",
    x = "Doanh thu",
    y = "Dòng tiền từ hoạt động KD",
    caption = "Nguồn: Báo cáo tài chính") +                                                    
  theme_minimal()   
## `geom_smooth()` using formula = 'y ~ x'

Giải thích: - ggplot(gmd, aes(x = dt, y = cfo)) + ggplot(gmd, aes(…)): gọi hàm vẽ biểu đồ với dữ liệu là gmd.

aes(x = dt, y = cfo): thiết lập trục hoành là Doanh thu, trục tung là CFO. - geom_point(size = 3, color = “#46aee6”, alpha = 0.75) geom_point(): vẽ các điểm dữ liệu (mỗi năm = 1 điểm).

size = 3: cỡ điểm vừa phải, dễ quan sát.

color = “#46aee6”: chọn màu xanh dương tươi.

alpha = 0.75: độ trong suốt nhẹ (giúp nhìn rõ nếu điểm chồng lên nhau). - geom_smooth(method = “lm”, se = FALSE, color = “#de4466”, size = 1.2) geom_smooth(): vẽ đường hồi quy (trend line) giúp nhận biết xu hướng.

method = “lm”: dùng mô hình linear model (hồi quy tuyến tính).

se = FALSE: không hiển thị vùng sai số (confidence interval).

color = “#de4466”: tô đường màu đỏ hồng nổi bật.

size = 1.2: độ dày đường để dễ nhìn. - geom_text(aes(label = nam), vjust = -1, size = 3, color = “black”) geom_text(): thêm chữ (label) lên biểu đồ.

aes(label = nam): mỗi điểm được gắn năm tương ứng (ví dụ 2015, 2016, …).

vjust = -1: nhãn được đặt trên điểm (nếu để vjust = 1 là bên dưới).

size = 3: kích thước chữ nhỏ gọn.

color = “black”: màu chữ đen để dễ đọc. - labs( title = “Quan hệ giữa Doanh thu và CFO”, x = “Doanh thu”, y = “Dòng tiền từ hoạt động KD”, caption = “Nguồn: Báo cáo tài chính” ) labs() dùng để thêm phần mô tả:

title: tiêu đề của biểu đồ.

x, y: tên trục hoành và tung.

caption: chú thích nhỏ ở góc dưới, ghi nguồn dữ liệu. - theme_minimal(): Chọn chủ đề tối giản

Kết quả: Biểu đồ thể hiện mối quan hệ đồng biến giữa doanh thu và dòng tiền từ hoạt động kinh doanh (CFO): doanh thu tăng thì CFO cũng tăng theo, phản ánh hiệu quả tích cực của hoạt kinh doanh qua các năm. Tuy nhiên, một số năm như 2015, 2016, 2023 có dòng tiền thấp, điều này cho thấy hiệu quả chuyển hóa doanh thu thành tiền mặt chưa ổn định ở tất cả các kỳ