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

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

Giới thiệu tổng quát

  • Bộ dữ liệu này bao gồm 100.000 quan sát và 11 biến, 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 sinh viê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)
## Warning: package 'readxl' was built under R version 4.5.1
data <- read_excel("D:/DuLieuThayTuongST3.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>

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

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>

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

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

dim(data)
## [1] 100000     11

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

1.1.3 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"

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.4 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.

1.1.5 Kiểm tra xem trong toàn bộ dữ liệu có giá trị NA hay không

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.

1.1.6 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(
    "Ma so benh nhan",                   
    "Tuoi (nam)",                                  
    "Gioi tinh (Nam/Nu)",                         
    "Chieu cao (cm)",                             
    "Can nang (kg)",                              
    "Nhom mau (A, B, AB, O)",                    
    "Chi so BMI (khoi luong co the)",            
    "Nhiet do co the",                      
    "Nhip tim (bpm)",                             
    "Huyet ap (mmHg)",                            
    "Cholesterol (mg/dL)"
  ),
  stringsAsFactors = FALSE
)
library(knitr)
## Warning: package 'knitr' was built under R version 4.5.1
kable(variable_meaning, booktabs = TRUE)
Variable Meaning
Student.ID Ma so benh nhan
Age Tuoi (nam)
Gender Gioi tinh (Nam/Nu)
Height Chieu cao (cm)
Weight Can nang (kg)
Blood.Type Nhom mau (A, B, AB, O)
BMI Chi so BMI (khoi luong co the)
Temperature Nhiet do co the
Heart.Rate Nhip tim (bpm)
Blood.Pressure Huyet ap (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.7 Phân loại biến định lượng và định tính:

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"
  • 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

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"

Nhận xét: Bộ dữ liệu gồm 2 biến định tính.

table(sapply(data, class))
## 
## character   numeric 
##         2         9

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

nrow(data)
## [1] 100000

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

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

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. Thay giá trị thiếu trong cột Weight bằng trung bình

data$Weight[is.na(data$Weight)] <- mean(data$Weight, 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 (viết hoa chữ đầu)

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 & Blood.Type thành factor

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

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.6 Mã hóa biến Gender thành dạng nhị phân (Male = 1, Female = 0)

data$Gender_num <- ifelse(data$Gender == "Male", 1, 0)
  • 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 == “Male”: kiểm tra từng dòng trong cột Gender xem có bằng “Male” 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 Gender_num.

1.2.7 Tạo biến mới “BMI_Level” phân loại theo chỉ số BMI

data$BMI_Level <- cut(data$BMI,
                      breaks = c(0, 18.5, 24.9, 29.9, Inf),
                      labels = c("Underweight", "Normal", "Overweight", "Obese"))
  • data$BMI Cột dữ liệu BMI (Body Mass Index), dạng số thực (numeric).

  • cut() Hàm phân loại (chia khoảng) trong R. Nó cắt biến liên tục thành các nhóm.

  • breaks = c(0, 18.5, 24.9, 29.9, Inf) Các ranh giới (ngưỡng) để chia BMI thành các nhóm:

0 → 18.5 → Underweight: Thiếu cân

18.5 → 24.9 → Normal: Cân đối

24.9 → 29.9 → Overweight: Thừa cân

≥ 30 (Inf = vô cùng) → Obese: Béo phì

  • labels = c(“Underweight”, “Normal”, “Overweight”, “Obese”) Tên nhóm tương ứng với từng khoảng.

  • data$BMI_Level Cột mới được tạo ra để lưu kết quả phân loại.

1.2.8 Chuẩn hóa nhiệt độ về độ C

data$Temperature_C <- (data$Temperature - 32) * 5/9

Giải thích:

data$Temperature Cột nhiệt độ hiện tại trong dữ liệu (đơn vị °F).

  • 32: Trừ đi 32 — bước đầu tiên trong công thức chuyển đổi

  • 5/9: Nhân với 5/9 để ra đơn vị °C

data$Temperature_C Cột mới được tạo ra để lưu giá trị nhiệt độ đã đổi sang °C.

1.2.9 Bảng kết quả

library(knitr)
kable(head(data[, c("Gender", "Gender_num", "BMI", "BMI_Level", "Temperature", "Temperature_C")]),
      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 Gender_num BMI BMI_Level Temperature Temperature_C
Female 0 27.64584 Overweight 99.09644 37.27580
Male 1 21.63422 Normal 98.71498 37.06388
Female 0 16.72902 Underweight 98.26029 36.81127
Male 1 19.09604 Normal 98.83960 37.13311
Female 0 18.76571 Normal 98.48001 36.93334
Male 1 29.91240 Obese 99.66837 37.59354

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(“Gender”, “Gender_num”, “BMI”, “BMI_Level”, “Temperature”, “Temperature_C”)] Chọn 6 cột cụ thể từ dataset 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:

Bảng trên thể hiện một phần dữ liệu sau khi đã chuẩn hóa và xử lý các biến liên quan đến giới tính, chỉ số cơ thể và nhiệt độ.

  • Biến Gender và Gender_num

Cột Gender hiển thị giá trị dạng chữ (“Male”, “Female”),

Trong khi Gender_num là dạng mã hóa nhị phân tương ứng (Male = 1, Female = 0).

-> Việc mã hóa này giúp thuận tiện cho các mô hình phân tích định lượng.

  • Biến BMI và BMI_Level

BMI là chỉ số khối cơ thể (Body Mass Index) – biến định lượng.

BMI_Level là biến phân loại, được chia thành 4 nhóm: Underweight, Normal, Overweight, Obese.

-> Trong mẫu minh họa, ta thấy có đủ các nhóm từ thiếu cân đến béo phì, cho thấy dữ liệu đa dạng và cân bằng.

  • Biến Temperature và Temperature_C

Temperature được ghi nhận theo đơn vị Fahrenheit,

Còn Temperature_C là kết quả chuyển đổi sang Celsius theo công thức

-> Nhiệt độ sau khi chuyển đổi dao động quanh 36.8°C – 37.6°C, tương ứng với mức bình thường của cơ thể con người.

Kết luận: Các biến định tính như Gender và Blood.Type đã được mã hóa và chuẩn hóa cách viết, trong khi các biến định lượng như BMI và Temperature đã được xử lý và chuyển đổi đơn vị thích hợp. Việc chuẩn hóa giúp đảm bảo tính nhất quán và sẵn sàng cho các bước phân tích tiếp theo.

1.2.10 Xem lại

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

sapply() là hàm trong R giúp áp dụng một hàm nào đó cho từng cột (hoặc từng phần tử) trong một data frame.

Áp dụng hàm class, tức là yêu cầu R cho biết kiểu dữ liệu của mỗi cột.

Kết quả trả về là một vector, trong đó tên của mỗi phần tử là tên cột, và giá trị là kiểu dữ liệu của cột đó.

-> Việc kiểm tra kiểu dữ liệu bằng sapply(data, class) giúp đảm bảo rằng tất cả các biến đã ở đúng định dạng sau khi chuẩn hóa dữ liệu.

Qua kết quả có thể thấy biến Gender và Blood.Type đã thành factor.

1.3 Thống kê cơ bản

library(psych)
## Warning: package 'psych' was built under R version 4.5.1
library(moments)
library(dplyr)
## Warning: package 'dplyr' was built under R version 4.5.1
## 
## Attaching package: 'dplyr'
## The following objects are masked from 'package:stats':
## 
##     filter, lag
## The following objects are masked from 'package:base':
## 
##     intersect, setdiff, setequal, union
  • library(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 × 14] (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    : Factor w/ 4 levels "A","AB","B","O": 4 3 1 3 4 3 2 2 3 3 ...
##  $ 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 ...
##  $ Gender_num    : num [1:100000] 0 1 0 1 0 1 1 1 1 1 ...
##  $ BMI_Level     : Factor w/ 4 levels "Underweight",..: 3 2 1 2 2 4 1 1 2 1 ...
##  $ Temperature_C : num [1:100000] 37.3 37.1 36.8 37.1 36.9 ...

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: Đối tượng: tibble [100,000 × 11], tức có 100.000 quan sát (observations) và 11 biến (variables).

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

describe(data[, c("Age", "Height", "Weight", "BMI", "Temperature_C" , "Cholesterol" , "Heart.Rate" , "Blood.Pressure")])
##                vars     n   mean    sd median trimmed   mad    min    max
## Age               1 1e+05  26.02  4.89  26.00   26.02  5.93  18.00  34.00
## Height            2 1e+05 174.91 14.47 174.79  174.89 18.59 150.00 200.00
## Weight            3 1e+05  69.89 17.35  69.84   69.86 22.28  40.00 100.00
## BMI               4 1e+05  23.32  7.03  22.62   22.93  7.42  10.07  44.31
## Temperature_C     5 1e+05  37.00  0.28  37.00   37.00  0.28  35.78  38.24
## Cholesterol       6 1e+05 184.56 37.57 184.00  184.57 48.93 120.00 249.00
## Heart.Rate        7 1e+05  79.51 11.55  79.00   79.52 14.83  60.00  99.00
## Blood.Pressure    8 1e+05 114.52 14.41 115.00  114.52 17.79  90.00 139.00
##                 range skew kurtosis   se
## Age             16.00 0.00    -1.20 0.02
## Height          50.00 0.01    -1.20 0.05
## Weight          60.00 0.01    -1.20 0.05
## BMI             34.24 0.45    -0.41 0.02
## Temperature_C    2.46 0.01     0.01 0.00
## Cholesterol    129.00 0.00    -1.20 0.12
## Heart.Rate      39.00 0.00    -1.20 0.04
## Blood.Pressure  49.00 0.00    -1.20 0.05

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)

Nhận xét:

  • Biến Age (Tuổi):

Tuổi trung bình của mẫu là 26,02, trung vị 26,00 và độ lệch chuẩn 4,89 → dữ liệu phân bố khá đồng đều, không bị lệch. Khoảng tuổi dao động từ 18 đến 34 tuổi, phạm vi hẹp. Độ lệch (Skew = 0) và độ nhọn (Kurtosis = -1,2) cho thấy phân bố chuẩn, hơi bẹt. Sai số chuẩn (SE = 0,02) rất nhỏ, chứng tỏ trung bình đáng tin cậy.

  • Biến Height (Chiều cao):

Chiều cao trung bình 174,91 cm, trung vị 174,79 cm, độ lệch chuẩn 14,47 cm, dao động 150–200 cm → dữ liệu phân tán vừa phải. Skew ≈ 0 và Kurtosis = -1,2 cho thấy phân bố cân đối, hơi bẹt, không có giá trị ngoại lai rõ rệt.

  • Biến Weight (Cân nặng):

Cân nặng trung bình 69,89 kg, trung vị 69,84 kg, độ lệch chuẩn 17,35 kg → dữ liệu phân tán khá rộng. Khoảng dao động từ 40 đến 100 kg, cho thấy sự khác biệt rõ giữa các cá nhân. Skew ≈ 0, Kurtosis = -1,2 → phân bố gần chuẩn.

  • Biến BMI (Chỉ số khối cơ thể):

Giá trị trung bình 23,32, trung vị 22,62, độ lệch chuẩn 7,03, dao động từ 10,07 đến 44,31 → dữ liệu phân tán cao, có thể gồm cả người gầy và béo phì. Skew = 0,45 và Kurtosis = -0,41 → phân bố hơi lệch phải, có một số giá trị cao hơn mức trung bình.

  • Biến Temperature_C (Nhiệt độ cơ thể):

Trung bình 37,00°C, trung vị 37,00°C, độ lệch chuẩn 0,28°C, phạm vi 35,78–38,24°C → dữ liệu rất ổn định, gần như không biến động. Skew ≈ 0, Kurtosis = 0 → phân bố chuẩn hoàn hảo

  • Cholesterol (Mức cholesterol trong máu):

Giá trị trung bình 184,56 mg/dL, trung vị 184,00 mg/dL, độ lệch chuẩn 37,57 → dữ liệu phân tán vừa phải quanh mức trung bình. Khoảng dao động từ 120 đến 249 mg/dL, cho thấy một số người có cholesterol cao. Độ lệch Skew = 0,01 và Kurtosis = -1,20 → phân bố gần chuẩn, hơi bẹt, không có ngoại lệ rõ rệt. Cholesterol chủ yếu nằm trong mức bình thường, nhưng có sự khác biệt nhỏ giữa các cá nhân.

  • Heart.Rate (Nhịp tim):

Trung bình 79,51 nhịp/phút, trung vị 79,00, độ lệch chuẩn 11,55, dao động 60–99 nhịp/phút → dữ liệu ổn định và tập trung quanh mức trung bình. Giá trị Skew = 0, Kurtosis = -1,20 → phân bố chuẩn, hơi bẹt, thể hiện sự đồng đều giữa các cá nhân. Nhịp tim trung bình nằm trong khoảng bình thường của người trưởng thành (khoảng 60–100 nhịp/phút), không có dấu hiệu bất thường.

  • Blood.Pressure (Huyết áp):

Trung bình 114,52 mmHg, trung vị 115,00, độ lệch chuẩn 14,41, dao động 90–139 mmHg → phần lớn mẫu nằm trong ngưỡng huyết áp bình thường. Độ lệch Skew = 0,00, Kurtosis = -1,20 → phân bố đối xứng, hơi bẹt. Huyết áp của các cá nhân trong mẫu khá ổn định, không có giá trị ngoại lệ cao hoặc thấp đáng kể.

1.3.3 Trung bình, trung vị, độ lệch chuẩn của chiều cao

mean(data$Height)
## [1] 174.9051
median(data$Height)
## [1] 174.7908
sd(data$Height)
## [1] 14.4696

1.3.4 Độ lệch chuẩn và phương sai của cân nặng

sd(data$Weight)
## [1] 17.34591
var(data$Weight)
## [1] 300.8804

1.3.5 Hệ số biến thiên (CV = sd / mean)

cv_height <- sd(data$Height) / mean(data$Height)
cv_height
## [1] 0.08272829

Hệ số biến thiên (CV) của biến Height là khoảng 8.28%, cho thấy chiều cao giữa các sinh viên có sự biến động thấp và khá đồng nhất trong toàn bộ mẫu quan sát

1.3.6 Tính giá trị nhỏ nhất, lớn nhất, khoảng biến thiên của BMI

min(data$BMI)
## [1] 10.07484
max(data$BMI)
## [1] 44.31407
range(data$BMI)
## [1] 10.07484 44.31407

1.3.7 Tính tứ phân vị của BMI

quantile(data$BMI)
##       0%      25%      50%      75%     100% 
## 10.07484 17.84263 22.62432 27.96283 44.31407

quantile(data$BMI) tính các phân vị (quantiles) của biến BMI trong bộ dữ liệu.

0% (Min): Giá trị nhỏ nhất BMI thấp nhất trong mẫu 25% (1st Qu.): Phân vị thứ nhất
50% (Median): Trung vị BMI ở giữa tập dữ liệu 75% (3rd Qu.): Phân vị thứ ba
100% (Max): Giá trị lớn nhất

Kết quả: Giá trị BMI của các sinh viên dao động từ 10.07 đến 44.29. 25% sinh viên có chỉ số BMI thấp hơn 17.86, trong khi 25% sinh viên có BMI cao hơn 27.93. Trung vị BMI là 22.66, cho thấy phần lớn sinh viên có chỉ số BMI nằm trong khoảng từ 18 đến 28, tương ứng với nhóm cân nặng bình thường đến hơi thừa cân theo phân loại thông thường của WHO.

1.3.8 Vẽ biểu đồ histogram để kiểm tra phân phối BMI

hist(data$BMI, main="Phan phoi chi so BMI", xlab="BMI", col="lightblue", breaks=30)

Hàm hist() dùng để vẽ biểu đồ tần suất (Histogram): giúp ta quan sát phân phối của một biến định lượng (numeric variable). main = “Phân phối chỉ số BMI” : Tiêu đề biểu đồ xlab = “BMI”, : Nhãn trục hoành col = “lightblue”, : Màu cột histogram breaks = 30) : Số lượng cột chia

Nhận xét: Biểu đồ tần suất cho thấy chỉ số BMI của sinh viên phân bố theo dạng gần đối xứng, tập trung chủ yếu quanh giá trị từ 18 đến 28. Điều này phù hợp với nhận định rằng phần lớn sinh viên có mức BMI nằm trong phạm vi bình thường đến hơi thừa cân. Một số ít sinh viên có BMI thấp (<18) hoặc cao (>30), thể hiện các trường hợp gầy hoặc béo phì.

1.3.9 Kiểm tra có bao nhiêu giá trị khác biệt (unique)

sapply(data, function(x) length(unique(x)))
##     Student.ID            Age         Gender         Height         Weight 
##         100000             17              2          89924          90041 
##     Blood.Type            BMI    Temperature     Heart.Rate Blood.Pressure 
##              4          89952          89953             40             50 
##    Cholesterol     Gender_num      BMI_Level  Temperature_C 
##            130              2              4          89953

sapply() → áp dụng hàm cho mỗi cột trong data.

function(x) length(unique(x)) → đếm số lượng giá trị duy nhất trong từng cột.

Kết quả sẽ là một vector, trong đó:

Tên là tên biến

Giá trị là số lượng giá trị khác nhau của biến đó

1.3.10 Xem tỷ lệ giới tính trong từng nhóm BMI_Level

table(data$Gender, data$BMI_Level)
##         
##          Underweight Normal Overweight Obese
##   Female       12777  15004       8900  8347
##   Male         15718  18123      10910 10221
prop.table(table(data$Gender, data$BMI_Level), 1) * 100
##         
##          Underweight   Normal Overweight    Obese
##   Female    28.37568 33.32149   19.76548 18.53735
##   Male      28.59274 32.96769   19.84647 18.59310
  • 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 (BMI_Level): 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 (%).

1.3.11 Trung bình Cholesterol theo giới tính

aggregate(Weight ~ Gender, data = data, FUN = mean)
##   Gender   Weight
## 1 Female 69.89192
## 2   Male 69.88608

aggregate() là hàm dùng để tính toán thống kê theo nhóm (grouped summary) trong R.

Weight ~ Gender nghĩa là bạn tính trung bình của biến Weight theo từng nhóm Gender (giới tính).

data = data chỉ rõ rằng bạn đang làm việc với bộ dữ liệu data.

FUN = mean nghĩa là bạn muốn tính giá trị trung bình.

Nhận xét: Trọng lượng trung bình của sinh viên nữ là 69.98 kg, trong khi sinh viên nam có trọng lượng trung bình là 69.77 kg. Sự chênh lệch giữa hai giới rất nhỏ (chỉ khoảng 0.21 kg), cho thấy cân nặng trung bình của nam và nữ trong mẫu dữ liệu gần như tương đương nhau. Điều này có thể phản ánh rằng trong bộ dữ liệu này, sự khác biệt giới tính về cân nặng không đáng kể, có thể do chiều cao, thói quen ăn uống hoặc đặc điểm dân số tương đồng.

1.3.12 Trung bình BMI theo nhóm máu

aggregate(BMI ~ Blood.Type, data = data, mean)
##   Blood.Type      BMI
## 1          A 23.39142
## 2         AB 23.33637
## 3          B 23.29708
## 4          O 23.28116

aggregate(): Dùng để tính toán thống kê theo nhóm.

BMI là biến cần tính trung bình,

Blood.Type là biến nhóm (phân loại theo nhóm máu),

mean là hàm dùng để tính giá trị trung bình BMI của từng nhóm máu.

Nhận xét:

Giá trị trung bình BMI của các nhóm máu dao động từ 23.27 đến 23.39, cho thấy sự khác biệt giữa các nhóm là rất nhỏ. Nhóm máu A có BMI trung bình cao nhất (23.39), trong khi nhóm O có BMI trung bình thấp nhất (23.27). Tuy nhiên, chênh lệch giữa các nhóm chỉ khoảng 0.12 đơn vị, cho thấy nhóm máu không phải là yếu tố có ảnh hưởng đáng kể đến chỉ số BMI của sinh viên trong bộ dữ liệu này.

1.3.13 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

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.

Nhận xét: Kết quả cho thấy BMI trung bình của sinh viên nữ là 23.37, cao hơn một chút so với sinh viên nam (23.28). Độ lệch chuẩn của hai nhóm khá tương đồng (khoảng 7.0), cho thấy mức độ phân tán BMI giữa các cá nhân trong mỗi nhóm là tương đương. Nhìn chung, sự khác biệt giữa nam và nữ về chỉ số BMI là rất nhỏ, cho thấy giới tính không phải yếu tố ảnh hưởng đáng kể đến BMI trong bộ dữ liệu này.

1.4 Phân tổ biến định tính:

1.4.1 Giới tính (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

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).

library(ggplot2)
## Warning: package 'ggplot2' was built under R version 4.5.1
## 
## Attaching package: 'ggplot2'
## The following objects are masked from 'package:psych':
## 
##     %+%, alpha
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.

Kết quả cho thấy có 45,028 sinh viên nữ (chiếm 45.03%) và 54,972 sinh viên nam (chiếm 54.97%) trong tổng số 100,000 quan sát.

Như vậy, tỷ lệ sinh viên nam cao hơn nữ khoảng 10%, cho thấy mẫu dữ liệu có sự cân đối tương đối giữa hai giới, tuy nhiên nam vẫn chiếm ưu thế nhẹ.

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

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

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.

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.2 Nhóm máu (Blood.Type):

tanso2 <- table(data$Blood.Type)
bang_tanso2 <- data.frame(NhomMau = names(tanso2),  TanSo = as.numeric(tanso2), TanSoTichLuy = cumsum(as.numeric(tanso2)))
print(bang_tanso2)
##   NhomMau TanSo TanSoTichLuy
## 1       A 22236        22236
## 2      AB 22183        44419
## 3       B 22781        67200
## 4       O 32800       100000
library(ggplot2)
library(RColorBrewer)
my_colors <- brewer.pal(n = 6, name = "Set2")  
ggplot(bang_tanso2, aes(x = NhomMau, y = TanSo, fill = NhomMau)) +
  geom_col() +
  geom_text(aes(label = TanSo), vjust = -0.5, size = 4) +
  scale_fill_manual(values = my_colors) +
  labs(title = "Biểu đồ cột tần số theo nhóm máu",
       x = "Nhóm máu",
       y = "Tần số") +
  theme_minimal() +
  theme(legend.position = "none")

Giải thích

RColorBrewer là thư viện chuyên dùng để lấy bảng màu phối sẵn (đẹp, hài hòa, dễ nhìn).

brewer.pal(n = 6, name = “Set2”): → Lấy 6 màu từ bảng Set2 (tông pastel nhẹ, thường dùng trong biểu đồ định tính — như nhóm máu, giới tính…). → my_colors là vector chứa 6 màu, ví dụ: “#66C2A5” “#FC8D62” “#8DA0CB” “#E78AC3” “#A6D854” “#FFD92F”

  • ggplot(bang_tanso2, aes(x = NhomMau, y = TanSo, fill = NhomMau))

Khởi tạo ggplot với dữ liệu đầu vào là bang_tanso2.

aes(…) định nghĩa:

x = NhomMau: trục hoành (tên nhóm máu).

y = TanSo: trục tung (tần số).

fill = NhomMau: mỗi cột có màu khác nhau tương ứng từng nhóm máu.

  • geom_col() Vẽ biểu đồ cột với chiều cao cột tương ứng với giá trị thực của TanSo (khác với geom_bar() – vốn tự đếm số lượng).

  • geom_text(aes(label = TanSo), vjust = -0.5, size = 4) Thêm nhãn số liệu trên đầu mỗi cột.

label = TanSo → hiện số tần số.

vjust = -0.5 → đẩy chữ lên một chút so với đỉnh cột.

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

  • scale_fill_manual(values = my_colors) Dùng bảng màu tùy chỉnh (my_colors) vừa tạo để tô màu cột.

Giúp biểu đồ sinh động hơn, tránh bị trùng màu khi nhiều nhóm máu.

  • labs( title = “Biểu đồ cột tần số theo nhóm máu”, x = “Nhóm máu”, y = “Tần số”)

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

  • theme_minimal() + theme(legend.position = “none”)

theme_minimal() → làm biểu đồ sáng, đơn giản, chuyên nghiệp.

legend.position = “none” → ẩn chú thích (legend), vì tên nhóm máu đã hiển thị rõ trên trục X nên không cần nữa.

Nhận xét:

Trong bốn nhóm máu, nhóm O có tần số cao nhất với 32800 người, chiếm tỷ trọng lớn trong mẫu khảo sát.

Ba nhóm máu còn lại (A, B, AB) có số lượng xấp xỉ nhau, dao động từ 22183 đến 22781 người, cho thấy sự phân bố khá đồng đều giữa các nhóm này.

Sự chênh lệch rõ rệt chỉ xuất hiện giữa nhóm O và các nhóm còn lại.

Kết luận:Phân bố nhóm máu trong mẫu cho thấy nhóm O phổ biến nhất, phản ánh xu hướng tương tự như nhiều quần thể dân số khác. Các nhóm A, B và AB có tỷ lệ tương đương, cho thấy sự đa dạng nhóm máu nhưng không có khác biệt lớn giữa các nhóm nhỏ.

tansuat2 <- prop.table(tanso2) * 100
bang_tansuat2 <- data.frame(NhomMau = names(tansuat2), TanSuat = round(as.numeric(tansuat2), 2), TanSuatTichLuy = round(cumsum(as.numeric(tansuat2)), 2))
print(bang_tansuat2)
##   NhomMau TanSuat TanSuatTichLuy
## 1       A   22.24          22.24
## 2      AB   22.18          44.42
## 3       B   22.78          67.20
## 4       O   32.80         100.00
Sys.setlocale("LC_ALL", "Vietnamese")
## Warning in Sys.setlocale("LC_ALL", "Vietnamese"): 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"
library(ggplot2)

ggplot(bang_tansuat2, aes(x = "", y = TanSuat, fill = NhomMau)) +
  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",
    "#FFD6A5"
  )) +
  labs(
    title = "Biểu đồ tròn tần suất theo nhóm máu",
    fill = "Nhóm máu"
  ) +
  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"
  )

Nhận xét:

Biểu đồ cho thấy sự phân bố tần suất các nhóm máu trong tập dữ liệu có sự khác biệt rõ ràng giữa các nhóm.

Cụ thể, nhóm máu O chiếm tỷ lệ cao nhất (32,53%), thể hiện đây là nhóm máu phổ biến nhất trong mẫu khảo sát.

Ba nhóm máu còn lại gồm A (22,5%), AB (22,36%) và B (22,61%) có tỷ lệ gần tương đương nhau, dao động quanh mức 22%, cho thấy sự phân bố khá đồng đều giữa các nhóm này.

Kết luận: Nhóm máu O chiếm ưu thế trong tập dữ liệu, trong khi các nhóm A, B, AB có tỷ lệ tương đối cân bằng. Điều này phản ánh xu hướng chung trong dân số, khi nhóm máu O thường phổ biến nhất so với các nhóm khác.

1.5 Phân tích biến định lượng:

1.5.1 Phân tích biến Cholesterol:

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

Chol_stats <- c(Min = min(data$Cholesterol, na.rm = TRUE),  Max = max(data$Cholesterol, na.rm = TRUE), Mean = mean(data$Cholesterol, na.rm = TRUE),  Median = median(data$Cholesterol, na.rm = TRUE), SD = sd(data$Cholesterol, na.rm = TRUE), Var = var(data$Cholesterol, na.rm = TRUE))
Chol_stats
##        Min        Max       Mean     Median         SD        Var 
##  120.00000  249.00000  184.56104  184.00000   37.57068 1411.55567

Nhận xét:

Mức cholesterol của các cá nhân trong tập dữ liệu dao động từ 120 mg/dL đến 249 mg/dL, cho thấy sự khác biệt tương đối lớn giữa những người có chỉ số mỡ máu thấp và cao.

Giá trị trung bình (Mean) đạt 184.56 mg/dL, rất gần với trung vị (Median = 184 mg/dL), phản ánh phân phối dữ liệu cân đối, không có dấu hiệu lệch phải hay lệch trái rõ rệt.

Độ lệch chuẩn (SD = 37.57) và phương sai (Var = 1411.56) cho thấy mức độ phân tán tương đối lớn — nghĩa là nồng độ cholesterol giữa các cá nhân có sự dao động đáng kể, có thể do khác biệt về chế độ ăn uống, thể trạng hoặc thói quen sinh hoạt.

Giá trị tối đa 249 mg/dL nằm gần ngưỡng cholesterol cao theo khuyến nghị y tế (≥240 mg/dL), trong khi giá trị tối thiểu 120 mg/dL lại nằm trong vùng cholesterol lý tưởng. Điều này phản ánh sự đa dạng trong tình trạng sức khỏe tim mạch của các cá nhân được khảo sát.

Kết luận: Phân bố cholesterol có xu hướng ổn định quanh mức trung bình 185 mg/dL, với phần lớn cá nhân nằm trong vùng cholesterol bình thường đến hơi cao, cho thấy tình trạng sức khỏe nhìn chung là tốt, song vẫn có một nhóm nhỏ có nguy cơ cao về tim mạch cần được chú ý theo dõi.

library(ggplot2)
ggplot(data, aes(x = Cholesterol)) +
  geom_histogram(
    bins = 10,
    fill = "lightblue",
    color = "white"
  ) +
  geom_text(
    stat = "bin",
    bins = 10,
    aes(label = ..count..),
    vjust = -0.3,      
    size = 3,
    color = "black"
  ) +
  scale_x_continuous(
    breaks = seq(floor(min(data$Cholesterol)),
                 ceiling(max(data$Cholesterol)),
                 by = 20)
  ) +
  labs(
    title = "Bieu do cot phan to muc Cholesterol",
    x = "Cholesterol (mg/dL)",
    y = "Tan so"
  ) +
  theme_minimal()
## Warning: The dot-dot notation (`..count..`) was deprecated in ggplot2 3.4.0.
## i 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: Khởi tạo biểu đồ ggplot, dùng dữ liệu data và gán trục hoành (x) là biến Cholesterol.

geom_histogram() tạo biểu đồ tần số (histogram).

bins = 10 → chia trục X thành 10 khoảng giá trị để nhóm các quan sát (càng nhiều bins, biểu đồ càng chi tiết).

fill = “lightblue” → tô màu xanh nhạt cho các cột.

color = “white” → viền trắng giữa các cột, giúp tách biệt rõ hơn.

Thêm nhãn số liệu lên trên mỗi cột.

..count.. là tần số của từng khoảng (được ggplot tự tính).

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

size = 3 và color = “black” → kích thước và màu chữ.

Tùy chỉnh thang chia trục X (mỗi 20 đơn vị mg/dL).

floor() và ceiling() giúp làm tròn giá trị min–max của cholesterol về số nguyên gần nhất.

Nhận xét:

Phân bố tương đối đồng đều, các cột có chiều cao khá gần nhau, cho thấy mức cholesterol của người trong mẫu không quá chênh lệch giữa các khoảng giá trị.

Khoảng cholesterol 160–180 mg/dL có tần số cao nhất (≈ 2944 người), cho thấy đây là mức cholesterol phổ biến nhất trong mẫu.

Các khoảng từ 140–220 mg/dL có tần số cao và ổn định, chiếm phần lớn dân số khảo sát → phần lớn người có mức cholesterol nằm trong vùng bình thường.

Hai đầu phân bố (<130 mg/dL và >240 mg/dL) có tần số thấp hơn rõ rệt → số người có cholesterol quá thấp hoặc quá cao là thiểu số.

Kết luận: Phân bố cholesterol của người trong mẫu có dạng gần như đối xứng, hơi lệch trái nhẹ, tập trung chủ yếu ở mức 140–220 mg/dL, thể hiện đa số người có mức Cholesterol trong giới hạn an toàn, chỉ có ít người có mức quá cao hoặc quá thấp.

library(ggplot2)
ggplot(data, aes(x = factor(0), y = Cholesterol)) +
  geom_boxplot(fill = "pink", color = "black", outlier.color = "lightpink", outlier.shape = 16) +
  labs(title = "Boxplot cua Cholesterol", x = "", y = "Cholesterol") +
  theme_minimal() +
  stat_summary(fun = mean, geom = "point", shape = 20, size = 5, color = "black")

Nhận xét:

Biểu đồ Boxplot thể hiện giá trị trung vị (Median) của mức cholesterol vào khoảng 190–200 mg/dL, cho thấy phần lớn người có mức cholesterol nằm gần giá trị này.

Khoảng tứ phân vị (IQR) trải từ khoảng 150 mg/dL (Q1) đến 220 mg/dL (Q3), phản ánh rằng đa số người trong mẫu có mức cholesterol nằm trong phạm vi bình thường.

Dữ liệu phân bố cân đối và không xuất hiện giá trị ngoại lai, chứng tỏ không có cá thể nào có mức cholesterol bất thường quá cao hay quá thấp.

Độ phân tán vừa phải, cho thấy sự ổn định trong chỉ số cholesterol giữa các cá nhân được khảo sát.

Kết luận: Phân bố mức cholesterol tương đối ổn định và tập trung quanh mức trung bình, phản ánh tình trạng sức khỏe tim mạch của mẫu dữ liệu nhìn chung là bình thường, không có dấu hiệu bất thường nổi bật.

1.5.2 Phân tích biến Heart.Rate:

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

Heart.Rate_stats <- c(
  Min = min(data$Heart.Rate, na.rm = TRUE),
  Max = max(data$Heart.Rate, na.rm = TRUE),
  Mean = mean(data$Heart.Rate, na.rm = TRUE),
  Median = median(data$Heart.Rate, na.rm = TRUE),
  SD = sd(data$Heart.Rate, na.rm = TRUE),
  Var = var(data$Heart.Rate, na.rm = TRUE)
)

Heart.Rate_stats
##       Min       Max      Mean    Median        SD       Var 
##  60.00000  99.00000  79.51322  79.00000  11.54908 133.38134

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

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 = "Bieu do cot phan to Heart.Rate",
    x = "Nhip tim (lan/phut)",
    y = "Tan so"
  ) +
  theme_minimal()

Nhận xét:

Biểu đồ cột trên mô tả phân bố nhịp tim (Heart Rate) của người trong mẫu dữ liệu.

Tần số ở các nhóm nhịp tim dao động quanh mức 2.400–2.500 người, cho thấy phân bố khá đồng đều giữa các khoảng giá trị.

Không có nhóm nào chiếm ưu thế vượt trội, phản ánh rằng hầu hết người tham gia khảo sát có nhịp tim nằm trong vùng bình thường và ổn định.

Sự chênh lệch nhỏ giữa các cột (dao động khoảng ±150 người) cho thấy độ phân tán thấp, đồng nghĩa nhịp tim trung bình tương đối đồng nhất trong tập dữ liệu.

Kết luận: Phân bố nhịp tim khá cân bằng, không có giá trị bất thường, phản ánh tình trạng sức khỏe ổn định và mức độ tương đồng sinh lý giữa các cá nhân trong mẫu.

library(ggplot2)
ggplot(data, aes(x = factor(0), y = Heart.Rate)) +
  geom_boxplot(fill = "lightcoral", color = "black", outlier.color = "red", outlier.shape = 16) +
  labs(title = "Boxplot cua Heart.Rate", x = "", y = "Heart Rate (nhip/phut)") +
  theme_minimal() +
  stat_summary(fun = mean, geom = "point", shape = 20, size = 5, color = "black")

Nhận xét:

Biểu đồ Boxplot cho thấy giá trị trung vị (Median) của nhịp tim nằm khoảng 64 nhịp/phút, thể hiện mức nhịp tim trung bình ổn định trong mẫu dữ liệu.

Khoảng tứ phân vị (IQR) kéo dài từ 62 nhịp/phút (Q1) đến 67 nhịp/phút (Q3), nghĩa là 50% số người có nhịp tim nằm trong khoảng này. Khoảng biến thiên này tương đối hẹp, cho thấy sự đồng đều cao trong nhịp tim giữa các cá nhân.

Phân bố dữ liệu khá cân đối quanh trung vị, không xuất hiện giá trị ngoại lai (outliers), phản ánh rằng không có trường hợp bất thường về nhịp tim trong tập dữ liệu.

Kết luận: Biểu đồ Boxplot cho thấy phân bố nhịp tim ổn định và tập trung quanh mức trung bình 64 nhịp/phút, phù hợp với mức nhịp tim nghỉ bình thường của người trưởng thành khỏe mạnh. Điều này gợi ý rằng tình trạng sức khỏe tim mạch của nhóm đối tượng khảo sát tương đối tốt và cân bằng.

1.5.3 Phân tích biến Blood.Pressure:

Thống kê mô tả biến Blood.Pressure:

BloodPressure_stats <- c(
  Min = min(data$Blood.Pressure, na.rm = TRUE),
  Max = max(data$Blood.Pressure, na.rm = TRUE),
  Mean = mean(data$Blood.Pressure, na.rm = TRUE),
  Median = median(data$Blood.Pressure, na.rm = TRUE),
  SD = sd(data$Blood.Pressure, na.rm = TRUE),
  Var = var(data$Blood.Pressure, na.rm = TRUE)
)

BloodPressure_stats
##       Min       Max      Mean    Median        SD       Var 
##  90.00000 139.00000 114.51856 115.00000  14.40634 207.54275

Nhận xét:

Huyết áp của các cá nhân trong tập dữ liệu dao động từ 90 mmHg đến 139 mmHg, thể hiện phạm vi khá rộng giữa những người có huyết áp thấp và cao.

Giá trị trung bình (Mean) đạt 114.52 mmHg, gần bằng trung vị (Median = 115 mmHg), cho thấy phân bố dữ liệu cân đối, không bị lệch nhiều về phía nào. Điều này phản ánh rằng phần lớn cá nhân có huyết áp xung quanh mức bình thường.

Độ lệch chuẩn (SD = 14.41) và phương sai (Var = 207.54) cho thấy mức độ dao động vừa phải, nghĩa là sự khác biệt về huyết áp giữa các cá nhân không quá lớn — điều này có thể liên quan đến việc đa số bệnh nhân có tình trạng sức khỏe ổn định và lối sống tương đối đồng nhất.

Giá trị tối đa 139 mmHg tiệm cận ngưỡng tiền tăng huyết áp theo phân loại y tế (≥140 mmHg), trong khi mức tối thiểu 90 mmHg thuộc ngưỡng huyết áp thấp nhưng vẫn trong giới hạn an toàn.

Kết luận: Biến Blood Pressure cho thấy phân bố tương đối cân đối quanh mức trung bình 115 mmHg, phần lớn cá nhân duy trì huyết áp ở mức bình thường, phản ánh sức khỏe tim mạch ổn định trong tập nhân viên khảo sát. Tuy nhiên, một số ít trường hợp cận ngưỡng cao có thể cần được theo dõi để phòng ngừa nguy cơ tăng huyết áp trong tương lai.

library(ggplot2)

ggplot(data, aes(x = Blood.Pressure)) +
  geom_histogram(
    bins = 10,
    fill = "purple",
    color = "white"
  ) +
  geom_text(
    stat = "bin",
    bins = 10,
    aes(label = ..count..),
    vjust = -0.3,     
    size = 3,
    color = "black"
  ) +
  scale_x_continuous(
    breaks = seq(floor(min(data$Blood.Pressure, na.rm = TRUE)),
                 ceiling(max(data$Blood.Pressure, na.rm = TRUE)),
                 by = 10)
  ) +
  labs(
    title = "Bieu do cot phan to Blood.Pressure",
    x = "Blood Pressure (mmHg)",
    y = "Tan so"
  ) +
  theme_minimal()

Nhận xét:

Biểu đồ thể hiện phân bố huyết áp của các bệnh nhân, với giá trị dao động chủ yếu trong khoảng 90 – 139 mmHg. Ta có thể quan sát thấy phân bố khá đều giữa các nhóm huyết áp, tuy có sự chênh lệch nhẹ giữa các khoảng.

Cụ thể, các nhóm từ 100 – 110 mmHg và 130 mmHg có tần số cao nhất, lần lượt đạt 3059 và 3048 cá nhân, cho thấy phần lớn nhân viên có huyết áp trong mức bình thường hoặc hơi cao nhẹ. Trong khi đó, nhóm dưới 90 mmHg chỉ có 1522 cá nhân, chiếm tỷ lệ thấp hơn, phản ánh ít trường hợp huyết áp thấp.

Nhìn chung, phân bố không bị lệch rõ rệt mà có xu hướng gần đối xứng quanh trung tâm, tương ứng với giá trị trung bình khoảng 115 mmHg. Điều này gợi ý rằng phần lớn bệnh nhân có huyết áp ổn định, nằm trong ngưỡng an toàn theo tiêu chuẩn y tế.

Kết luận: Dữ liệu huyết áp của bệnh nhân có phân bố tương đối đồng đều và cân đối, cho thấy sức khỏe tim mạch nhìn chung tốt, song một số ít trường hợp cận ngưỡng cao (130–139 mmHg) vẫn cần được theo dõi thường xuyên để tránh nguy cơ tăng huyết áp trong tương lai.

library(ggplot2)

ggplot(data, aes(x = factor(0), y = Blood.Pressure)) +
  geom_boxplot(fill = "purple", color = "black", outlier.color = "red", outlier.shape = 16) +
  labs(title = "Boxplot cua Blood Pressure", x = "", y = "Blood Pressure (mmHg)") +
  theme_minimal() +
  stat_summary(fun = mean, geom = "point", shape = 20, size = 5, color = "black")

1.6 Phân tổ kết hợp:

1.6.1 Phân tổ 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 x 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
## # i 70 more rows
library(ggplot2)
library(dplyr)
library(viridis)
## Warning: package 'viridis' was built under R version 4.5.1
## 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 (nhip/phut)") +
  labs(title = "Phan bo Heart Rate theo Gender",
       x = "Gender",
       y = "So luong") +
  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)
  )

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 = "Ty le 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"
  )

1.6.2 Phân tổ nhóm máu theo giới tính:

tanso6 <- table(data$Gender, data$Blood.Type)
bang_tanso6 <- as.data.frame(tanso6)
colnames(bang_tanso6) <- c("Gender", "Blood.Type", "Count")
print(bang_tanso6)
##   Gender Blood.Type Count
## 1 Female          A  7529
## 2   Male          A  9102
## 3 Female         AB  7377
## 4   Male         AB  9250
## 5 Female          B  7727
## 6   Male          B  9363
## 7 Female          O 11096
## 8   Male          O 13446
library(ggplot2)
library(dplyr)
library(viridis)

long_BT <- data %>%
  filter(!is.na(Blood.Type)) %>%
  group_by(Gender, Blood.Type) %>%
  summarise(Count = n(), .groups = "drop")

ggplot(long_BT, aes(x = Gender, y = Count, fill = Blood.Type)) +
  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 = "D", name = "Nhóm máu") +
  labs(title = "Phan bo nhom mau theo Gender",
       x = "Gender",
       y = "So luong") +
  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)
  )

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

df_pie_BT <- data %>%
  filter(!is.na(Blood.Type)) %>%
  group_by(Gender, Blood.Type) %>%
  summarise(Count = n(), .groups = "drop") %>%
  group_by(Gender) %>%
  mutate(Percent = round(Count / sum(Count) * 100, 1))

ggplot(df_pie_BT, aes(x = "", y = Percent, fill = Blood.Type)) +
  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 = "F", name = "Nhom mau") +
  labs(title = "Ty le nhom mau 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"
  )

1.6.3 Phân tổ Cholesterol theo giới tính:

data <- data %>%
  filter(!is.na(Cholesterol)) %>%
  mutate(Chol.Group = cut(
    Cholesterol,
    breaks = seq(floor(min(Cholesterol)), ceiling(max(Cholesterol)), by = 10),
    right = FALSE
  ))

tanso_chol <- table(data$Gender, data$Chol.Group)
bang_tanso_chol <- as.data.frame(tanso_chol)
colnames(bang_tanso_chol) <- c("Gender", "Chol.Group", "Count")
print(bang_tanso_chol)
##    Gender Chol.Group Count
## 1  Female  [120,130)  2574
## 2    Male  [120,130)  3153
## 3  Female  [130,140)  2487
## 4    Male  [130,140)  3238
## 5  Female  [140,150)  2593
## 6    Male  [140,150)  3219
## 7  Female  [150,160)  2696
## 8    Male  [150,160)  3208
## 9  Female  [160,170)  2608
## 10   Male  [160,170)  3129
## 11 Female  [170,180)  2640
## 12   Male  [170,180)  3185
## 13 Female  [180,190)  2522
## 14   Male  [180,190)  3099
## 15 Female  [190,200)  2612
## 16   Male  [190,200)  3044
## 17 Female  [200,210)  2495
## 18   Male  [200,210)  3170
## 19 Female  [210,220)  2624
## 20   Male  [210,220)  3170
## 21 Female  [220,230)  2574
## 22   Male  [220,230)  3190
## 23 Female  [230,240)  2669
## 24   Male  [230,240)  3154
library(ggplot2)
library(dplyr)
library(viridis)

long_Chol <- data %>%
  filter(!is.na(Cholesterol)) %>%
  mutate(Chol.Group = cut(Cholesterol, breaks = 10, right = FALSE)) %>%
  group_by(Gender, Chol.Group) %>%
  summarise(Count = n(), .groups = "drop")

ggplot(long_Chol, aes(x = Gender, y = Count, fill = Chol.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 = "H", name = "Cholesterol (mg/dL)") +
  labs(title = "Phan bo Cholesterol theo Gender",
       x = "Gender",
       y = "So luong") +
  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)
  )

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

df_pie_Chol <- data %>%
  filter(!is.na(Cholesterol)) %>%
  mutate(Chol.Group = cut(Cholesterol, breaks = 10, right = FALSE)) %>%
  group_by(Gender, Chol.Group) %>%
  summarise(Count = n(), .groups = "drop") %>%
  group_by(Gender) %>%
  mutate(Percent = round(Count / sum(Count) * 100, 1))

ggplot(df_pie_Chol, aes(x = "", y = Percent, fill = Chol.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 = "F", name = "Cholesterol (mg/dL)") +
  labs(title = "Ty le Cholesterol 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"
  )

1.6.4 Phân tổ theo giới tính:

library(dplyr)
data %>%
group_by(Gender) %>%
summarise(SoLuong = n(), BMI_TB = mean(BMI, na.rm = TRUE), BP_TB = mean(Blood.Pressure, na.rm = TRUE), Chol_TB = mean(Cholesterol, na.rm = TRUE))
## # A tibble: 2 x 5
##   Gender SoLuong BMI_TB BP_TB Chol_TB
##   <fct>    <int>  <dbl> <dbl>   <dbl>
## 1 Female   33729   23.3  115.    185.
## 2 Male     41161   23.3  114.    184.

Nhận xét:

Cơ cấu giới tính: Gồm 54.972 nam và 45.028 nữ, cho thấy tỷ lệ nam giới chiếm ưu thế nhẹ trong bộ dữ liệu. Cơ cấu này khá cân bằng, đảm bảo độ tin cậy khi so sánh giữa hai nhóm giới tính.

Chỉ số BMI trung bình: Cả hai giới có BMI trung bình xấp xỉ nhau. Chênh lệch không đáng kể (< 0.01), cho thấy mức độ thể trọng trung bình giữa hai giới tương đối tương đồng. Theo phân loại của WHO, cả hai nhóm đều thuộc ngưỡng “bình thường (18.5-24.9). .

Chỉ số BP trung bình: Nữ giới có huyết áp trung bình cao hơn nam giới nhưng không đáng kể. Tuy nhiên, sự khác biệt rất nhỏ nên không mang ý nghĩa lâm sàng rõ rệt, cho thấy huyết áp của hai nhóm tương đối ổn định.

Mức Cholesterol trung bình: Nữ giới có mức cholesterol trung bình nhỉnh hơn nam giới. Cả hai đều ở ngưỡng bình thường (< 200 mg/dL), cho thấy tình trạng mỡ máu ổn định.

Kết luận: Các chỉ tiêu sức khỏe trung bình (BMI, BP, Chol) không có sự khác biệt đáng kể giữa hai giới. Điều này cho thấy sức khỏe tổng quát của nhóm nam và nữ trong bộ dữ liệu khá tương đồng, không có dấu hiệu chênh lệch đáng kể về thể trạng hoặc yếu tố tim mạch.

1.6.5 Phân tổ theo nhóm tuổi:

data <- data %>%
mutate(NhomTuoi = case_when(Age < 18 ~ "Duoi 18", Age <= 30 ~ "18-30", Age <= 45 ~ "31-45", Age <= 60 ~ "46-60", TRUE ~ "Tren 60",  TRUE ~ NA_character_))
library(dplyr)
data %>%
group_by(NhomTuoi) %>%
summarise(BMI_TB = mean(BMI), BP_TB = mean(Blood.Pressure), Chol_TB = mean(Cholesterol))
## # A tibble: 2 x 4
##   NhomTuoi BMI_TB BP_TB Chol_TB
##   <chr>     <dbl> <dbl>   <dbl>
## 1 18-30      23.3  115.    185.
## 2 31-45      23.3  114.    185.

Nhận xét:

Chỉ số BMI trung bình: Gần như không thay đổi giữa hai nhóm, cho thấy thể trạng trung bình tương đương, đều nằm trong ngưỡng “bình thường” (18.5–24.9).

Chỉ số BP trung bình: Nhóm 31–45 có huyết áp thấp hơn nhẹ (114.4 so với 114.6), chênh lệch rất nhỏ không đáng kể.

Mức Cholesterol trung bình: Giảm nhẹ ở nhóm 31–45 (184.50 so với 184.58), sự khác biệt không đáng kể về mặt thực tiễn.

Kết luận: Các chỉ tiêu sức khỏe trung bình (BMI, BP, Chol) giữa hai nhóm tuổi 18–30 và 31–45 gần như tương đồng. Điều này cho thấy tình trạng sức khỏe tổng quát ổn định và không thay đổi rõ rệt theo độ tuổi trong khoảng 18–45 tuổi.

1.6.6 Phân tổ theo nhóm máu theo giới tính:

table(data$Gender, data$Blood.Type)
##         
##              A    AB     B     O
##   Female  7529  7377  7727 11096
##   Male    9102  9250  9363 13446
prop.table(table(data$Gender, data$Blood.Type), 1) * 100
##         
##                 A       AB        B        O
##   Female 22.32204 21.87139 22.90907 32.89751
##   Male   22.11317 22.47273 22.74726 32.66684
prop.table(table(data$Gender, data$Blood.Type), 2) * 100
##         
##                 A       AB        B        O
##   Female 45.27088 44.36759 45.21358 45.21229
##   Male   54.72912 55.63241 54.78642 54.78771
library(dplyr)
data %>%
group_by(Gender, Blood.Type) %>%
summarise(SoLuong = n()) %>%
mutate(TyLe = round(100 * SoLuong / sum(SoLuong), 2))
## `summarise()` has grouped output by 'Gender'. You can override using the
## `.groups` argument.
## # A tibble: 8 x 4
## # Groups:   Gender [2]
##   Gender Blood.Type SoLuong  TyLe
##   <fct>  <fct>        <int> <dbl>
## 1 Female A             7529  22.3
## 2 Female AB            7377  21.9
## 3 Female B             7727  22.9
## 4 Female O            11096  32.9
## 5 Male   A             9102  22.1
## 6 Male   AB            9250  22.5
## 7 Male   B             9363  22.8
## 8 Male   O            13446  32.7
library(ggplot2)
ggplot(data, aes(x = Blood.Type, fill = Gender)) + geom_bar(position = "dodge") + labs(title = "Phan to nhom mau theo gioi tinh", x = "Nhom mau", y = "So luong") + theme_minimal()

Nhận xét: Ở cả hai giới, nhóm máu O chiếm tỷ lệ cao nhất (khoảng 33%), trong khi nhóm máu AB chiếm tỷ lệ thấp nhất (khoảng 22%). Phân bố nhóm máu giữa nam và nữ khá tương đồng, không có sự khác biệt lớn về tỷ lệ. Số lượng mẫu ở nam nhìn chung cao hơn nữ ở tất cả các nhóm máu.

Kết luận: Cơ cấu nhóm máu giữa nam và nữ tương đối giống nhau, với nhóm O phổ biến nhất và nhóm AB ít gặp nhất, cho thấy không có sự khác biệt đáng kể về phân bố nhóm máu theo giới tính.

1.6.7 Phân tổ nhóm máu theo nhóm tuổi:

table(data$NhomTuoi, data$Blood.Type)
##        
##             A    AB     B     O
##   18-30 12682 12731 13110 18826
##   31-45  3949  3896  3980  5716
prop.table(table(data$NhomTuoi, data$Blood.Type), 1) * 100
##        
##                A       AB        B        O
##   18-30 22.11372 22.19917 22.86003 32.82708
##   31-45 22.51297 22.21082 22.68970 32.58651
prop.table(table(data$NhomTuoi, data$Blood.Type), 2) * 100
##        
##                A       AB        B        O
##   18-30 76.25519 76.56823 76.71153 76.70931
##   31-45 23.74481 23.43177 23.28847 23.29069
library(dplyr)
data %>%
group_by(NhomTuoi, Blood.Type) %>%
summarise(SoLuong = n()) %>%
mutate(TyLe = round(100 * SoLuong / sum(SoLuong), 2))
## `summarise()` has grouped output by 'NhomTuoi'. You can override using the
## `.groups` argument.
## # A tibble: 8 x 4
## # Groups:   NhomTuoi [2]
##   NhomTuoi Blood.Type SoLuong  TyLe
##   <chr>    <fct>        <int> <dbl>
## 1 18-30    A            12682  22.1
## 2 18-30    AB           12731  22.2
## 3 18-30    B            13110  22.9
## 4 18-30    O            18826  32.8
## 5 31-45    A             3949  22.5
## 6 31-45    AB            3896  22.2
## 7 31-45    B             3980  22.7
## 8 31-45    O             5716  32.6
library(ggplot2)
ggplot(data, aes(x = Blood.Type, fill = NhomTuoi)) + geom_bar(position = "dodge") + labs(title = "Phan to nhom mau theo nhom tuoi", x = "Nhom mau", y = "So luong") + theme_minimal()

Nhận xét: Trong cả hai nhóm tuổi 18–30 và 31–45, nhóm máu O chiếm tỷ lệ cao nhất (khoảng 33%), trong khi nhóm máu AB có tỷ lệ thấp nhất (khoảng 22%). Tỷ trọng các nhóm máu A, B, AB khá cân bằng, chỉ dao động quanh mức 22–23%. Cấu trúc phân bố nhóm máu gần như tương đồng giữa hai nhóm tuổi, không có sự khác biệt đáng kể. Nhìn chung, các nhóm tuổi trẻ hơn (18–30) có số lượng mẫu lớn hơn, nhưng tỷ lệ giữa các nhóm máu vẫn ổn định.

Kết luận: Phân bố nhóm máu không có mối liên hệ rõ ràng với nhóm tuổi, xu hướng chung là nhóm máu O phổ biến nhất, tiếp theo là B và A, còn AB ít gặp nhất. Điều này cho thấy tính ổn định trong cơ cấu nhóm máu giữa các nhóm tuổi.