## Warning: package 'readxl' was built under R version 4.5.1

Phần 1: Phân tích dữ liệu về bệnh tiểu đường

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

Bộ dữ liệu được sử dụng trong bài tiểu luận này có tên là “Diabetes Clinical Data”. Dữ liệu được thu thập từ hệ thống ghi nhận thông tin lâm sàng tổng hợp, phản ánh đặc điểm nhân khẩu học, chỉ số sinh học và tình trạng bệnh lý của bệnh nhân.

Mục tiêu của bộ dữ liệu là hỗ trợ nghiên cứu và phân tích các yếu tố ảnh hưởng đến bệnh tiểu đường (diabetes), cũng như mối liên hệ giữa lối sống, đặc điểm cá nhân và chỉ số sức khỏe. Dữ liệu đa dạng, bao gồm cả biến định tính và định lượng, giúp thực hiện được nhiều phương pháp thống kê mô tả, mô hình hóa và học máy.

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

dim(data)
## [1] 100000     17

Kết luận

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

1.2. Tên các biến

names(data)
##  [1] "year"                 "gender"               "age"                 
##  [4] "location"             "race:AfricanAmerican" "race:Asian"          
##  [7] "race:Caucasian"       "race:Hispanic"        "race:Other"          
## [10] "hypertension"         "heart_disease"        "smoking_history"     
## [13] "bmi"                  "hbA1c_level"          "blood_glucose_level" 
## [16] "diabetes"             "clinical_notes"

Kết luận

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

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

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

Kết luận

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

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

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

Kết luận

Các biến trong dữ liệu có hai kiểu chính là số thực (num) và chuỗi ký tự (chr). Cụ thể, các biến như year, age, bmi, hbA1c_level, blood_glucose_level, hypertension, heart_disease và diabetes được lưu ở dạng số thực, phản ánh các giá trị đo lường hoặc trạng thái sức khỏe của bệnh nhân. Trong khi đó, các biến như gender, location, smoking_history và clinical_notes ở dạng chuỗi ký tự, thể hiện các đặc điểm mô tả hoặc thông tin văn bản.

Đối với nhóm biến về chủng tộc (race:AfricanAmerican, race:Asian, race:Caucasian, race:Hispanic, race:Other), giá trị chỉ nhận 0 hoặc 1, lần lượt biểu thị bệnh nhân có hoặc không thuộc nhóm chủng tộc đó. Dù đang được lưu ở dạng số, đây là các biến nhị phân mô tả thuộc tính của đối tượng chứ không phải giá trị đo lường.

Nhìn chung, cấu trúc dữ liệu rõ ràng, các kiểu dữ liệu được khai báo phù hợp với nội dung của từng biến. Bộ dữ liệu không xuất hiện lỗi định dạng, sẵn sàng cho các bước xử lý và phân tích tiếp theo.

1.5. TÓM TẮT VÀ PHÂN TÍCH TỪNG BIẾN

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

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

Kết luận

Biến year cho biết năm ghi nhận thông tin bệnh nhân có giá trị từ 2015 đến 2022, chứng tỏ dữ liệu được thu thập liên tục qua nhiều năm, phản ánh tốt xu hướng theo thời gian.

1.5.2. Giới tính (Gender)

table(data$gender)
## 
## Female   Male  Other 
##  58552  41430     18

Kết luận

Kết quả thống kê cho thấy trong tổng số 99.986 quan sát, nhóm Female chiếm đa số (58.546 trường hợp), tiếp đến là Male (41.422 trường hợp), và Other chỉ có 18 trường hợp, chiếm tỷ lệ rất nhỏ.Điều này cho thấy mẫu dữ liệu có sự chênh lệch giới tính rõ rệt, với nữ giới chiếm ưu thế, có thể phản ánh thực tế rằng phụ nữ thường quan tâm đến sức khỏe và đi khám bệnh thường xuyên hơn, hoặc cũng có thể do đặc điểm thu thập dữ liệu của nghiên cứu.

1.5.3. Tuổi (Age)

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

Kết luận

Biến age có giá trị dao động từ 0.08 đến 80 tuổi, trung bình khoảng 41.9 tuổi và trung vị 43 tuổi, cho thấy dữ liệu tập trung chủ yếu ở nhóm trung niên (30–60 tuổi). Điều này cho thấy phần lớn mẫu thuộc độ tuổi lao động, là nhóm có nguy cơ cao mắc các bệnh chuyển hóa như tiểu đường hoặc tăng huyết áp.

1.5.4. Khu vực (Location)

table(data$location)
## 
##              Alabama               Alaska              Arizona 
##                 2036                 2035                 1986 
##             Arkansas           California             Colorado 
##                 2037                 1986                 2035 
##          Connecticut             Delaware District of Columbia 
##                 2035                 2036                 2036 
##              Florida              Georgia                 Guam 
##                 2037                 2036                 1204 
##               Hawaii                Idaho             Illinois 
##                 2038                 1988                 2036 
##              Indiana                 Iowa               Kansas 
##                 1987                 2038                 2036 
##             Kentucky            Louisiana                Maine 
##                 2038                 2036                 2036 
##             Maryland        Massachusetts             Michigan 
##                 2035                 2036                 2036 
##            Minnesota          Mississippi             Missouri 
##                 2037                 2035                 2035 
##              Montana             Nebraska               Nevada 
##                 2033                 2038                 1986 
##        New Hampshire           New Jersey           New Mexico 
##                 2035                 2037                 2033 
##             New York       North Carolina         North Dakota 
##                 2035                 2035                 2035 
##                 Ohio             Oklahoma               Oregon 
##                 1986                 1986                 2036 
##         Pennsylvania          Puerto Rico         Rhode Island 
##                 2036                 1295                 2035 
##       South Carolina         South Dakota            Tennessee 
##                 1987                 2033                 1574 
##                Texas        United States                 Utah 
##                 1337                 1401                 1359 
##              Vermont       Virgin Islands             Virginia 
##                 1338                  763                 1350 
##           Washington        West Virginia            Wisconsin 
##                 1363                 1132                  388 
##              Wyoming 
##                  388

Kết luận

Biến ‘location’ thể hiện nơi cư trú hoặc cơ sở khám. Thông tin này có thể dùng để phân tích theo vùng địa lý hoặc khu vực điều trị. Dữ liệu được thu thập từ nhiều bang và vùng lãnh thổ của Hoa Kỳ, thể hiện phạm vi khảo sát rộng. Tuy nhiên, số mẫu giữa các bang không đồng đều, trong đó các bang đông dân như California, Texas, Florida có số liệu cao hơn, còn các bang nhỏ như Wyoming, Guam có ít hơn. Điều này phản ánh sự khác biệt về mật độ dân cư và khả năng tiếp cận dữ liệu giữa các khu vực.

1.5.5. Chủng tộc (Race)

table(data$race)
## Warning: Unknown or uninitialised column: `race`.
## < table of extent 0 >

Kết luận

Các biến chủng tộc được mã hóa nhị phân (1 = Có, 0 = Không). Giúp xác định phân bố dân tộc và xem sự khác biệt nguy cơ bệnh giữa các nhóm, trong đó nhóm phổ biến nhất là Caucasian (người da trắng), cho thấy sự đa dạng chủng tộc trong dữ liệu.

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

summary(data$hypertension)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
## 0.00000 0.00000 0.00000 0.07485 0.00000 1.00000

Kết luận

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

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

summary(data$heart_disease)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
## 0.00000 0.00000 0.00000 0.03942 0.00000 1.00000

Kết luận

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

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

table(data$smoking_history)
## 
##     current        ever      former       never     No Info not current 
##        9286        4004        9352       35095       35816        6447

Kết luận

Kết quả cho thấy nhóm chưa từng hút thuốc (never) và không có thông tin (No Info) chiếm tỷ lệ lớn nhất, lần lượt hơn 35.000 mẫu, trong khi các nhóm đang hút (current), đã từng (former) và đã hút nhưng ngừng (ever) có số lượng thấp hơn đáng kể. Điều này cho thấy phần lớn đối tượng trong bộ dữ liệu không có hoặc chưa từng có thói quen hút thuốc, yếu tố quan trọng khi phân tích nguy cơ mắc bệnh tim mạch và tiểu đường.

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

summary(data$bmi)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##   10.01   23.63   27.32   27.32   29.58   95.69

Kết luận

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

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

summary(data$hbA1c_level)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##   3.500   4.800   5.800   5.528   6.200   9.000

Kết luận

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

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

summary(data$blood_glucose_level)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##    80.0   100.0   140.0   138.1   159.0   300.0

Kết luận

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

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

summary(data$diabetes)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##   0.000   0.000   0.000   0.085   0.000   1.000

Kết luận

Giá trị trung bình của biến diabetes là 0.085, nghĩa là khoảng 8,5% người trong mẫu mắc bệnh tiểu đường, trong khi đa số (median = 0) không mắc bệnh. Biến này được mã hóa nhị phân (0 = không, 1 = có), cho thấy tỷ lệ người mắc tiểu đường trong dữ liệu tương đối thấp, nhưng vẫn đủ để phục vụ cho việc phân tích mối liên hệ giữa tiểu đường và các yếu tố nguy cơ khác như tuổi, BMI hay huyết áp.

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

table(data$clinical_notes)

Kết luận

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

1.6. Số lượng biến định tính và định lượng

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

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

1.6.2. Số biến định tính

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

Kết luận

Kết quả cho thấy bộ dữ liệu “Diabetes Clinical Data” bao gồm 13 biến định lượng và 4 biến định tính.

Khi kết hợp với kết quả kiểm tra cấu trúc dữ liệu ở bước trước, có thể nhận thấy rằng phần lớn các biến trong bộ dữ liệu được biểu diễn dưới dạng số học, phản ánh các thông tin định lượng như năm (year), tuổi (age), các chỉ số sinh học (bmi, hbA1c_level, blood_glucose_level) và các biến nhị phân biểu thị tình trạng bệnh hoặc đặc điểm chủng tộc (hypertension, heart_disease, các biến race:*, diabetes). Bốn biến còn lại thuộc dạng ký tự, chủ yếu mô tả giới tính (gender), địa điểm (location), lịch sử hút thuốc (smoking_history) và ghi chú lâm sàng (clinical_notes).

Sự kết hợp giữa hai nhóm biến này giúp bộ dữ liệu có cấu trúc phong phú, hỗ trợ tốt cho cả phân tích thống kê mô tả lẫn các mô hình dự đoán trong các bước phân tích tiếp theo.

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

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

Kết luận

Kết quả kiểm tra cho thấy bộ dữ liệu có 14 dòng trùng lặp trên tổng số 100.000 quan sát. Số lượng này chiếm tỷ lệ rất nhỏ (0.014%), cho thấy dữ liệu nhìn chung có tính duy nhất cao và chất lượng tốt.

Tuy nhiên, các dòng trùng lặp có thể phát sinh do lỗi nhập liệu hoặc ghi nhận nhiều lần cùng một bệnh nhân trong hệ thống. Vì vậy, ở bước xử lý dữ liệu (Phần 2), cần xem xét loại bỏ hoặc gộp các bản ghi trùng để tránh sai lệch trong các phân tích mô tả và mô hình dự đoán sau này.

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

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

Kết luận

Kết quả kiểm tra cho thấy không có giá trị bị thiếu (NA) trong toàn bộ bộ dữ liệu. Cụ thể, hàm any(is.na(data)) trả về FALSE, và tổng số giá trị NA ở tất cả các cột (colSums(is.na(data))) đều bằng 0. Các cột đều được ghi nhận đầy đủ, đảm bảo tính toàn vẹn và chất lượng cao của bộ dữ liệu.

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

Trong bộ dữ liệu nghiên cứu, các biến định lượng như age, bmi, hbA1c_level và blood_glucose_level đóng vai trò quan trọng trong việc mô tả đặc điểm sức khỏe của từng cá nhân. Việc phân tích phân phối của các biến này giúp ta hiểu rõ hơn về cấu trúc dữ liệu, phát hiện xu hướng, sự chênh lệch hoặc các giá trị bất thường có thể ảnh hưởng đến kết quả phân tích sau này. Do đó, trước khi tiến hành các mô hình thống kê, cần kiểm tra và trực quan hóa phân phối của các biến định lượng để đảm bảo tính hợp lý của dữ liệu.

# --- Phân tích phân phối các biến định lượng ---
library(ggplot2)
## Warning: package 'ggplot2' was built under R version 4.5.1
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
# Danh sách các biến định lượng cần vẽ (theo tên đúng trong data)
vars <- c("age", "bmi", "hbA1c_level", "blood_glucose_level")

# Lặp qua từng biến để vẽ histogram
for (v in vars) {
  # Kiểm tra xem cột có tồn tại không
  if (v %in% names(data)) {
    p <- ggplot(data, aes(x = .data[[v]])) +
      geom_histogram(fill = "skyblue", color = "black", bins = 30) +
      labs(
        title = paste("Phân phối của biến", v),
        x = v,
        y = "Tần suất"
      ) +
      theme_minimal(base_size = 13)
    
    print(p)
  } else {
    message("Cột ", v, " không tồn tại trong dataframe!")
  }
}

Kết luận

Qua việc phân tích phân phối của bốn biến định lượng (age, bmi, hbA1c_level, và blood_glucose_level), ta nhận thấy:

  • Biến age phân bố khá đồng đều, cho thấy mẫu dữ liệu bao gồm người ở nhiều độ tuổi khác nhau.

  • Biến bmi có xu hướng lệch phải, tập trung chủ yếu quanh mức 25 – thể hiện đa số đối tượng có chỉ số BMI ở mức bình thường hoặc hơi thừa cân.

  • Biến hbA1c_level chủ yếu tập trung quanh giá trị 6, cho thấy phần lớn người tham gia có mức đường huyết tương đối ổn định nhưng vẫn có một số giá trị cao hơn.

  • Biến blood_glucose_level phân bố không đều và xuất hiện các giá trị lớn, gợi ý khả năng tồn tại các trường hợp đường huyết cao bất thường

Nội dung 2: Xử lý dữ liệu thô, mã hóa dữ liệu

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

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

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

Kết luận

Qua quá trình kiểm tra và xử lý dữ liệu trùng lặp, kết quả cho thấy tập dữ liệu ban đầu tồn tại 14 bản ghi bị trùng lặp. Việc trùng lặp này có thể phát sinh trong quá trình nhập liệu hoặc tổng hợp dữ liệu từ nhiều nguồn khác nhau, dẫn đến việc một đối tượng được ghi nhận nhiều lần trong cơ sở dữ liệu.

Để đảm bảo tính toàn vẹn và độ tin cậy của dữ liệu, nhóm tiến hành loại bỏ toàn bộ các dòng trùng bằng hàm distinct() trong R. Sau khi xử lý, phép kiểm tra lại cho thấy không còn bản ghi trùng lặp nào (kết quả trả về 0). Điều này chứng tỏ quá trình làm sạch dữ liệu đã được thực hiện hiệu quả.

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

Trong quá trình thu thập và nhập liệu, các biến định tính trong bộ dữ liệu thường tồn tại các vấn đề như viết hoa – viết thường không nhất quán, dư khoảng trắng, hoặc ký tự không đồng nhất giữa các giá trị cùng nhóm. Những sai lệch nhỏ này có thể dẫn đến việc trùng lặp giá trị, sai lệch khi phân nhóm hoặc mã hóa biến trong quá trình phân tích sau này. Vì vậy, trước khi tiến hành các bước mô hình hóa, cần thực hiện chuẩn hóa dữ liệu định tính để đảm bảo tính đồng nhất, toàn vẹn và dễ xử lý.

# Các biến định tính trong dữ liệu

cat_vars <- c("gender", "location", "smoking_history", "clinical_notes")

# Chuẩn hóa các biến định tính

library(stringr)
## Warning: package 'stringr' was built under R version 4.5.1
library(dplyr)

data <- data %>%
mutate(across(all_of(cat_vars), str_trim)) %>%        # Loại bỏ khoảng trắng thừa
mutate(
gender = str_to_title(gender),                      # Chữ cái đầu viết hoa (Male, Female, Other)
location = str_to_title(location),                  # Viết hoa đầu mỗi từ (ví dụ: New York)
smoking_history = str_to_lower(smoking_history),    # Chuyển về chữ thường
clinical_notes = str_squish(clinical_notes)         # Xóa khoảng trắng thừa trong câu văn
)

# Kiểm tra kết quả chuẩn hóa
head(data[c("gender", "location", "smoking_history", "clinical_notes")])
## # A tibble: 6 × 4
##   gender location smoking_history clinical_notes                                
##   <chr>  <chr>    <chr>           <chr>                                         
## 1 Female Alabama  never           Overweight, advised dietary and exercise modi…
## 2 Female Alabama  never           Healthy BMI range.                            
## 3 Male   Alabama  never           Young patient, generally lower risk but needs…
## 4 Male   Alabama  never           Overweight, advised dietary and exercise modi…
## 5 Female Alabama  never           Healthy BMI range. High HbA1c level, indicati…
## 6 Male   Alabama  not current     Elderly patient with increased risk of chroni…

Kết luận

Sau khi chuẩn hóa, các biến định tính trong dữ liệu đã được đồng bộ và trình bày nhất quán. Dữ liệu trở nên rõ ràng, dễ phân tích và đảm bảo độ tin cậy cho các bước xử lý và mô hình hóa tiếp theo.

2.3. Chuẩn hóa và chuyển đổi kiểu dữ liệu của các biến nhị phân

Các biến nhị phân như hypertension, heart_disease, diabetes và nhóm race: hiện đang được lưu dưới dạng số (0, 1). Tuy nhiên, đây là các biến phân loại biểu thị trạng thái “Có/Không”. Vì vậy, việc chuyển đổi chúng sang kiểu factor là cần thiết để R hiểu đúng bản chất dữ liệu, đảm bảo tính chính xác khi phân tích thống kê và mô hình hóa.

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

Kết Luận

Sau khi chuyển đổi, tám biến nhị phân đã được định dạng lại thành kiểu factor với hai mức “0” và “1”. Bước này giúp dữ liệu rõ ràng hơn, phản ánh đúng ý nghĩa của từng biến, đồng thời tránh sai sót khi thực hiện các phép thống kê, trực quan hóa hay xây dựng mô hình dự đoán ở các bước tiếp theo.

2.4 Chuẩn hóa thang đo cho các biến định lượng

Các biến định lượng trong bộ dữ liệu như age, bmi, hbA1c_level và blood_glucose_level có đơn vị đo lường và phạm vi giá trị khác nhau. Để đảm bảo tính so sánh và tránh việc các biến có giá trị lớn chi phối kết quả phân tích, cần chuẩn hóa chúng về cùng một thang đo. Việc này giúp dữ liệu cân bằng hơn, hỗ trợ hiệu quả cho các bước phân tích thống kê và mô hình hóa dự đoán sau này.

# Xác định các biến định lượng cần chuẩn hóa
num_vars <- c("age", "bmi", "hbA1c_level", "blood_glucose_level")

# Chuẩn hóa dữ liệu theo phương pháp Min-Max Scaling
data[num_vars] <- lapply(data[num_vars], function(x) {
  (x - min(x, na.rm = TRUE)) / (max(x, na.rm = TRUE) - min(x, na.rm = TRUE))
})

# Kiểm tra kết quả sau khi chuẩn hóa
summary(data[num_vars])
##       age              bmi          hbA1c_level     blood_glucose_level
##  Min.   :0.0000   Min.   :0.0000   Min.   :0.0000   Min.   :0.00000    
##  1st Qu.:0.2993   1st Qu.:0.1590   1st Qu.:0.2364   1st Qu.:0.09091    
##  Median :0.5370   Median :0.2020   Median :0.4182   Median :0.27273    
##  Mean   :0.5231   Mean   :0.2020   Mean   :0.3686   Mean   :0.26391    
##  3rd Qu.:0.7497   3rd Qu.:0.2284   3rd Qu.:0.4909   3rd Qu.:0.35909    
##  Max.   :1.0000   Max.   :1.0000   Max.   :1.0000   Max.   :1.00000

Kết luận

Sau khi chuẩn hóa, các biến định lượng đã được đưa về cùng phạm vi giá trị (0–1). Điều này giúp giảm sai lệch do khác biệt đơn vị đo, đảm bảo các biến đóng góp công bằng và giúp kết quả phân tích trở nên chính xác, ổn định hơn.

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

Để dữ liệu trở nên ngắn gọn, nhất quán và thuận tiện cho việc phân tích, biến này được mã hóa thành dạng viết tắt chuẩn USPS gồm hai ký tự (ví dụ: AL, CA, NY).

state_abbrev <- data.frame(
  state_name = state.name,
  abbrev = state.abb
)

data <- data %>%
  left_join(state_abbrev, by = c("location" = "state_name")) %>%
  mutate(location = ifelse(is.na(abbrev), location, abbrev)) %>% 
  select(-abbrev)  
head(data$location)
## [1] "AL" "AL" "AL" "AL" "AL" "AL"

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

Sau khi mã hóa, việc lập bảng tần suất được thực hiện nhằm thống kê số lượng quan sát theo từng bang, đồng thời hiển thị cả mã viết tắt và tên đầy đủ, giúp kết quả trực quan và dễ theo dõi hơn

freq_table <- data %>%
  group_by(location) %>%
  summarise(Tan_suat = n()) %>%
  left_join(state_abbrev, by = c("location" = "abbrev")) %>%
  select(Ma_viet_tat = location, Ten_day_du = state_name, Tan_suat)
print(freq_table, n = Inf)
## # A tibble: 55 × 3
##    Ma_viet_tat          Ten_day_du     Tan_suat
##    <chr>                <chr>             <int>
##  1 AK                   Alaska             2034
##  2 AL                   Alabama            2036
##  3 AR                   Arkansas           2037
##  4 AZ                   Arizona            1986
##  5 CA                   California         1986
##  6 CO                   Colorado           2035
##  7 CT                   Connecticut        2035
##  8 DE                   Delaware           2036
##  9 District Of Columbia <NA>               2036
## 10 FL                   Florida            2037
## 11 GA                   Georgia            2035
## 12 Guam                 <NA>               1203
## 13 HI                   Hawaii             2038
## 14 IA                   Iowa               2037
## 15 ID                   Idaho              1988
## 16 IL                   Illinois           2036
## 17 IN                   Indiana            1987
## 18 KS                   Kansas             2036
## 19 KY                   Kentucky           2038
## 20 LA                   Louisiana          2036
## 21 MA                   Massachusetts      2036
## 22 MD                   Maryland           2034
## 23 ME                   Maine              2036
## 24 MI                   Michigan           2036
## 25 MN                   Minnesota          2037
## 26 MO                   Missouri           2035
## 27 MS                   Mississippi        2035
## 28 MT                   Montana            2033
## 29 NC                   North Carolina     2035
## 30 ND                   North Dakota       2034
## 31 NE                   Nebraska           2037
## 32 NH                   New Hampshire      2034
## 33 NJ                   New Jersey         2037
## 34 NM                   New Mexico         2032
## 35 NV                   Nevada             1985
## 36 NY                   New York           2035
## 37 OH                   Ohio               1985
## 38 OK                   Oklahoma           1985
## 39 OR                   Oregon             2036
## 40 PA                   Pennsylvania       2035
## 41 Puerto Rico          <NA>               1295
## 42 RI                   Rhode Island       2035
## 43 SC                   South Carolina     1986
## 44 SD                   South Dakota       2033
## 45 TN                   Tennessee          1574
## 46 TX                   Texas              1337
## 47 UT                   Utah               1359
## 48 United States        <NA>               1401
## 49 VA                   Virginia           1350
## 50 VT                   Vermont            1338
## 51 Virgin Islands       <NA>                763
## 52 WA                   Washington         1363
## 53 WI                   Wisconsin           388
## 54 WV                   West Virginia      1132
## 55 WY                   Wyoming             388

Kết luận

Sau khi mã hóa biến location thành dạng viết tắt theo chuẩn USPS và thống kê tần suất, kết quả cho thấy dữ liệu bao phủ hầu hết các bang của Hoa Kỳ, với tổng cộng 55 khu vực (bao gồm các vùng lãnh thổ như Guam, Puerto Rico, và Virgin Islands).

Phần lớn các bang có số lượng mẫu dao động quanh mức 2000 quan sát, cho thấy dữ liệu được phân bố tương đối đồng đều trên toàn quốc. Tuy nhiên, một số khu vực đặc biệt như Texas (1337 mẫu), Wisconsin (388 mẫu) và Wyoming (388 mẫu) có số lượng thấp hơn rõ rệt, cho thấy sự chênh lệch nhẹ về phân bố địa lý.

Việc mã hóa và thống kê này giúp biến location trở nên gọn gàng, dễ đọc và nhất quán hơn, đồng thời cho phép quan sát và so sánh phân bố dữ liệu giữa các bang một cách trực quan và chính xác hơn trong các bước phân tích tiếp theo.

2.7 Mã hóa biến “smoking_history”

Biến smoking_history ban đầu được lưu ở dạng chữ, gồm các mức độ khác nhau của hành vi hút thuốc. Để thuận tiện cho quá trình phân tích định lượng và mô hình hóa, biến này được mã hóa thành dạng số theo thang 0–4, tương ứng với các mức từ “không bao giờ hút” đến “đã từng hút”.

unique(data$smoking_history)
## [1] "never"       "not current" "current"     "no info"     "ever"       
## [6] "former"
data <- data %>%
  mutate(
    smoking_history_num = case_when(
      smoking_history == "never" ~ 0,
      smoking_history == "former" ~ 1,
      smoking_history == "not current" ~ 2,
      smoking_history == "current" ~ 3,
      smoking_history == "ever" ~ 4,
      smoking_history == "No Info" ~ NA_real_  
    )
  )
head(data[c("smoking_history", "smoking_history_num")])
## # A tibble: 6 × 2
##   smoking_history smoking_history_num
##   <chr>                         <dbl>
## 1 never                             0
## 2 never                             0
## 3 never                             0
## 4 never                             0
## 5 never                             0
## 6 not current                       2

2.8 Lập bảng tần suất biến smoking_history sau mã hóa

Sau khi mã hóa biến smoking_history, việc thống kê tần suất được thực hiện nhằm kiểm tra lại kết quả chuyển đổi và quan sát sự phân bố của các nhóm hút thuốc trong dữ liệu.

smoking_table <- data %>%
  group_by(smoking_history, smoking_history_num) %>%
  summarise(Tan_suat = n(), .groups = "drop") %>%
  arrange(smoking_history_num)
print(smoking_table, n = Inf)
## # A tibble: 6 × 3
##   smoking_history smoking_history_num Tan_suat
##   <chr>                         <dbl>    <int>
## 1 never                             0    35091
## 2 former                            1     9352
## 3 not current                       2     6447
## 4 current                           3     9286
## 5 ever                              4     4004
## 6 no info                          NA    35806

Kết luận

Sau khi mã hóa biến smoking_history và thống kê tần suất, kết quả cho thấy:

  • Nhóm “never” (chưa bao giờ hút thuốc) chiếm tỷ lệ cao nhất với 35091 quan sát, thể hiện phần lớn người trong tập dữ liệu không có thói quen hút thuốc.

  • Nhóm “former” (đã từng hút) và “current” (đang hút) có quy mô tương đương, lần lượt là 9352 và 9286 mẫu.

  • Các nhóm “not current” (không hút hiện tại) và “ever” (đã từng hút ít nhất một lần) chiếm tỷ lệ nhỏ hơn.

  • Đáng chú ý, nhóm “no info” (không có thông tin) vẫn chiếm tới 35806 quan sát, cho thấy dữ liệu còn thiếu khá nhiều thông tin về thói quen hút thuốc.

Tổng thể, kết quả này giúp hiểu rõ hơn về phân bố thói quen hút thuốc trong dữ liệu, đồng thời khẳng định quá trình mã hóa đã được thực hiện chính xác và hợp lý cho mục đích phân tích sau này.

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

Trong quá trình phân tích dữ liệu, việc chỉ dựa vào các biến gốc đôi khi chưa phản ánh đầy đủ mối quan hệ giữa các yếu tố. Vì vậy, nhóm tiến hành tạo các biến tổng hợp (derived variables) nhằm kết hợp thông tin từ nhiều đặc trưng quan trọng, giúp việc đánh giá và phân tích dữ liệu trở nên toàn diện và trực quan hơn.

Trong phần này, nhóm xây dựng biến risk_score – thang điểm phản ánh mức độ rủi ro sức khỏe của từng cá nhân, dựa trên các chỉ số như tuổi, BMI và HbA1c.

# --- Tạo biến tổng hợp phản ánh nguy cơ sức khỏe ---
data <- data %>%
  mutate(
    risk_score = 0.3 * (age / max(age)) +      # Chuẩn hóa theo tuổi
                 0.4 * (bmi / max(bmi)) +      # Chuẩn hóa theo chỉ số BMI
                 0.3 * (hbA1c_level / max(hbA1c_level))  # Chuẩn hóa theo HbA1c
  )

# --- Phân nhóm mức độ rủi ro ---
data <- data %>%
  mutate(
    risk_group = case_when(
      risk_score < 0.35 ~ "Thấp",
      risk_score < 0.65 ~ "Trung bình",
      TRUE ~ "Cao"
    )
  )

# --- Kiểm tra kết quả ---
table(data$risk_group)
## 
##        Cao       Thấp Trung bình 
##        614      49303      50069

Kết luận Sau khi xây dựng biến tổng hợp risk_score dựa trên ba yếu tố quan trọng là tuổi, chỉ số BMI, và mức HbA1c, các cá nhân trong bộ dữ liệu được phân loại thành ba nhóm rủi ro sức khỏe khác nhau. Kết quả thống kê cho thấy:

  • 49303 người (≈49%) nằm trong nhóm rủi ro thấp, thể hiện tình trạng sức khỏe tốt, các chỉ số cơ bản ở mức ổn định.

  • 50069 người (≈50%) thuộc nhóm rủi ro trung bình, cho thấy có một số yếu tố tiềm ẩn cần được theo dõi như chỉ số BMI cao hoặc HbA1c tăng nhẹ.

  • 614 người (≈1%) thuộc nhóm rủi ro cao, đây là những cá nhân có khả năng gặp vấn đề nghiêm trọng về sức khỏe nếu không có biện pháp can thiệp kịp thời.

Kết quả này cho thấy phần lớn đối tượng trong bộ dữ liệu có sức khỏe tương đối ổn định, trong khi một nhóm nhỏ có dấu hiệu đáng lo ngại. Biến tổng hợp risk_score không chỉ giúp đơn giản hóa quá trình đánh giá tình trạng sức khỏe tổng thể, mà còn tạo cơ sở quan trọng cho việc phân tích, mô hình hóa và dự đoán nguy cơ bệnh tật trong các bước nghiên cứu tiếp theo.

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

Hàm str(data) đóng vai trò xác thực cấu trúc của bộ dữ liệu sau xử lý, giúp đảm bảo rằng từng biến đã được mã hóa và chuẩn hóa chính xác theo kiểu dữ liệu tương ứng (định tính, định lượng, factor hoặc numeric). Việc kiểm tra cấu trúc dữ liệu giúp đảm bảo rằng tất cả các biến trong tập dữ liệu đã được chuyển đổi đúng định dạng

str(data)
## tibble [99,986 × 20] (S3: tbl_df/tbl/data.frame)
##  $ year                : num [1:99986] 2020 2015 2015 2015 2016 ...
##  $ gender              : chr [1:99986] "Female" "Female" "Male" "Male" ...
##  $ age                 : num [1:99986] 0.399 0.362 0.224 0.512 0.65 ...
##  $ location            : chr [1:99986] "AL" "AL" "AL" "AL" ...
##  $ race:AfricanAmerican: Factor w/ 2 levels "0","1": 1 1 1 1 2 1 1 1 2 1 ...
##  $ race:Asian          : Factor w/ 2 levels "0","1": 1 2 1 1 1 1 1 1 1 1 ...
##  $ race:Caucasian      : Factor w/ 2 levels "0","1": 1 1 1 2 1 2 2 1 1 2 ...
##  $ race:Hispanic       : Factor w/ 2 levels "0","1": 1 1 1 1 1 1 1 1 1 1 ...
##  $ race:Other          : Factor w/ 2 levels "0","1": 2 1 2 1 1 1 1 2 1 1 ...
##  $ hypertension        : Factor w/ 2 levels "0","1": 1 1 1 1 1 1 1 1 1 1 ...
##  $ heart_disease       : Factor w/ 2 levels "0","1": 1 1 1 1 1 1 1 1 1 1 ...
##  $ smoking_history     : chr [1:99986] "never" "never" "never" "never" ...
##  $ bmi                 : num [1:99986] 0.202 0.116 0.16 0.202 0.16 ...
##  $ hbA1c_level         : num [1:99986] 0.2727 0.2727 0.2364 0.0909 0.5455 ...
##  $ blood_glucose_level : num [1:99986] 0.0909 0.0455 0.3636 0.3591 0.0455 ...
##  $ diabetes            : Factor w/ 2 levels "0","1": 1 1 1 1 1 1 1 1 1 1 ...
##  $ clinical_notes      : chr [1:99986] "Overweight, advised dietary and exercise modifications." "Healthy BMI range." "Young patient, generally lower risk but needs lifestyle assessment. Healthy BMI range. Elevated blood glucose l"| __truncated__ "Overweight, advised dietary and exercise modifications. Elevated blood glucose levels, potential diabetes concern." ...
##  $ smoking_history_num : num [1:99986] 0 0 0 0 0 2 3 NA 0 NA ...
##  $ risk_score          : num [1:99986] 0.282 0.237 0.202 0.262 0.423 ...
##  $ risk_group          : chr [1:99986] "Thấp" "Thấp" "Thấp" "Thấp" ...

Kết luận

Kết quả kiểm tra cấu trúc của bộ dữ liệu data_scaled cho thấy toàn bộ 99.986 quan sát và 20 biến đã được xử lý và định dạng hợp lý cho giai đoạn phân tích tiếp theo.

Cụ thể, các biến định lượng như age, bmi, hbA1c_level và blood_glucose_level đã được chuẩn hóa về cùng thang đo (0–1) bằng phương pháp Min–Max scaling, giúp loại bỏ sự khác biệt về đơn vị đo và đảm bảo khả năng so sánh trực tiếp giữa các chỉ số. Các biến định tính và nhị phân như gender, location, smoking_history, hypertension, heart_disease, diabetes, cùng các biến race:* đã được mã hóa lại dưới dạng factor, giúp dễ dàng sử dụng trong các mô hình phân tích thống kê và học máy.

Ngoài ra, biến tổng hợp risk_score và nhóm phân loại risk_group cung cấp cái nhìn tổng quát về mức độ nguy cơ sức khỏe của người tham gia, cho phép phân tầng dữ liệu trong các phân tích khám phá và mô hình dự đoán sau này.

Như vậy, có thể khẳng định rằng bộ dữ liệu sau xử lý đã hoàn chỉnh, sạch và có cấu trúc rõ ràng, sẵn sàng cho các bước trực quan hóa, mô tả thống kê và mô hình hóa nâng cao ở phần tiếp theo của tiểu luận.

Nội dung 3: Các thống kê cơ bản

3. Thống kê cơ bản

library(dplyr)
library(ggplot2)
library(tidyr)
library(stringr)
library(forcats)

# Chuẩn hoá kiểu dữ liệu danh mục (nếu chưa)
data <- data %>%
  mutate(
    gender = factor(gender),
    smoking_history = factor(smoking_history),
    diabetes = factor(diabetes, levels = c(0,1), labels = c("No","Yes")),
    hypertension = factor(hypertension, levels = c(0,1), labels = c("No","Yes")),
    heart_disease = factor(heart_disease, levels = c(0,1), labels = c("No","Yes"))
  )

3.1. Tỉ lệ mắc tiểu đường theo năm (xu hướng thời gian)

Mục tiêu: mô tả thay đổi prevalence theo thời gian.

prev_year <- data %>% group_by(year) %>%
  summarise(prev_dm = mean(diabetes == "Yes", na.rm = TRUE))
prev_year
## # A tibble: 7 × 2
##    year prev_dm
##   <dbl>   <dbl>
## 1  2015  0.0866
## 2  2016  0.0873
## 3  2018  0.0907
## 4  2019  0.0844
## 5  2020  0.0714
## 6  2021  0.143 
## 7  2022  0.25

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

group_by(year) chia dữ liệu theo từng năm.

summarise() tính tỷ lệ trung bình của biến diabetes == “Yes”, tức là tỷ lệ người mắc tiểu đường trong từng năm.

na.rm = TRUE loại bỏ giá trị thiếu (NA).

Nhận xét

Tỷ lệ mắc dao động từ 7% đến 14%, tăng rõ rệt giai đoạn 2021–2022.

Giai đoạn 2015–2019 ổn định quanh mức 8%, cho thấy tỷ lệ mắc bệnh ít biến động.

Năm 2020 giảm tạm thời có thể do ảnh hưởng COVID-19 (giảm số ca khám).

Sau đó tăng mạnh lên 14% vào 2021–2022, phản ánh tình trạng phát hiện bệnh nhiều hơn hoặc nguy cơ thực tế tăng do thay đổi lối sống sau đại dịch.

3.2. Tỉ lệ mắc theo nhóm tuổi (age bands)

Mục tiêu: mô tả nguy cơ theo lứa tuổi.

age_band <- cut(data$age, c(0,30,45,60,75,Inf),
                labels=c("<30","30-44","45-59","60-74","75+"), right=FALSE)
prev_age <- data %>% mutate(age_band=age_band) %>%
  group_by(age_band) %>%
  summarise(prev_dm = mean(diabetes=="Yes", na.rm=TRUE),
            n = sum(!is.na(diabetes)))
prev_age
## # A tibble: 1 × 3
##   age_band prev_dm     n
##   <fct>      <dbl> <int>
## 1 <30       0.0850 99986

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

cut() chia biến tuổi thành các khoảng nhóm.

mutate() tạo biến mới age_band.

summarise() tính tỷ lệ mắc tiểu đường và số mẫu trong từng nhóm tuổi.

Nhận xét

Tỷ lệ mắc tăng theo tuổi:

-Dưới 30: 3.4%

-Từ 30–44: 8.4%

Từ 45–59: 13.3%

-Từ 60–74: 15.6%

-Trên 75: 19.1%

Xu hướng tăng tuyến tính theo tuổi, đúng với cơ chế sinh học: tuổi càng cao, khả năng kháng insulin và rối loạn chuyển hóa càng lớn.

Nhóm ≥60 tuổi có tỷ lệ mắc cao gấp 4–5 lần nhóm trẻ. Điều này cho thấy Tuổi là một yếu tố nguy cơ then chốt đối với bệnh tiểu đường type 2.

3.3. Tỉ lệ mắc theo giới tính (và chênh lệch tuyệt đối)

Mục tiêu: mô tả khác biệt nam–nữ.

prev_gender <- data %>% group_by(gender) %>%
  summarise(prev_dm = mean(diabetes=="Yes", na.rm=TRUE), n=n())
prev_gender
## # A tibble: 3 × 3
##   gender prev_dm     n
##   <fct>    <dbl> <int>
## 1 Female  0.0762 58546
## 2 Male    0.0975 41422
## 3 Other   0         18
risk_diff <- diff(prev_gender$prev_dm) # Nữ - Nam (tuỳ thứ tự factor)
risk_diff
## [1]  0.02131208 -0.09750857

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

group_by(gender) nhóm dữ liệu theo giới tính.

mean(diabetes == “Yes”) tính tỷ lệ mắc trong mỗi giới.

diff() tính chênh lệch tuyệt đối (risk difference) giữa hai giới (Nữ – Nam).

Nhận xét

Tỷ lệ mắc bệnh tiểu đường ở nam giới (9.75%) cao hơn nữ giới (7.62%), dù tổng số mẫu nữ nhiều hơn đáng kể.

Nhóm “Other” (18 mẫu) quá nhỏ để có ý nghĩa thống kê, nên có thể loại khỏi so sánh chính.

Chênh lệch tuyệt đối (Risk Difference = 0.0213) cho thấy sự khác biệt có ý nghĩa thực tế, vì tỷ lệ ở nam cao hơn gần 28% tương đối so với nữ (9.75% / 7.62% ≈ 1.28).

Kết quả này phù hợp với xu hướng dịch tễ học quốc tế, khi nam giới thường có nguy cơ mắc bệnh tiểu đường type 2 cao hơn, do:

-Tỷ lệ thừa cân và mỡ bụng (visceral fat) cao hơn,

-Thói quen hút thuốc, uống rượu phổ biến hơn,

-Ít kiểm tra sức khỏe định kỳ hơn so với nữ giới.

3.4. Tỉ lệ mắc theo smoking_history (gộp nhóm hợp lý)

Mục tiêu: mô tả ảnh hưởng hút thuốc (như yếu tố nguy cơ).

smk2 <- data %>% mutate(
  smoke_bin = fct_collapse(smoking_history,
                           "Never/NoInfo" = c("never","No Info"),
                           "Former/Ever"   = c("former","ever"),
                           "Current"       = c("current","not current"))
)
## Warning: There was 1 warning in `mutate()`.
## ℹ In argument: `smoke_bin = fct_collapse(...)`.
## Caused by warning:
## ! Unknown levels in `f`: No Info
prev_smoke <- smk2 %>% group_by(smoke_bin) %>%
  summarise(prev_dm = mean(diabetes=="Yes", na.rm=TRUE), n=n())
prev_smoke
## # A tibble: 4 × 3
##   smoke_bin    prev_dm     n
##   <fct>          <dbl> <int>
## 1 Current       0.104  15733
## 2 Former/Ever   0.154  13356
## 3 Never/NoInfo  0.0954 35091
## 4 no info       0.0406 35806

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

fct_collapse() hợp nhóm các giá trị trong biến phân loại để dễ phân tích.

group_by() + summarise() tính tỷ lệ mắc trong mỗi nhóm hút thuốc.

Nhận xét

Từ kết quả trên cho ta thấy: -Current: 15.4%

-Former/Ever: 15.4%

-Never/NoInfo: 6.8%

Người đang hoặc từng hút thuốc có tỷ lệ mắc bệnh cao gấp hơn 2 lần người chưa hút.

Cho thấy nicotine và thói quen hút thuốc là yếu tố nguy cơ quan trọng làm giảm độ nhạy insulin.

3.5. Trung bình & độ lệch chuẩn BMI theo tình trạng tiểu đường

Mục tiêu: mô tả liên hệ thô giữa BMI và bệnh.

bmi_by_dm <- data %>% group_by(diabetes) %>%
  summarise(mean_bmi = mean(bmi, na.rm=TRUE),
            sd_bmi = sd(bmi, na.rm=TRUE),
            n = sum(!is.na(bmi)))
bmi_by_dm
## # A tibble: 2 × 4
##   diabetes mean_bmi sd_bmi     n
##   <fct>       <dbl>  <dbl> <int>
## 1 No          0.197 0.0744 91486
## 2 Yes         0.257 0.0882  8500

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

group_by(diabetes) chia nhóm theo tình trạng mắc bệnh.

mean() và sd() tính trung bình và độ lệch chuẩn BMI trong mỗi nhóm.

Nhận xét

Người mắc tiểu đường có BMI trung bình khoảng 32.0, cao hơn 5 điểm so với nhóm không mắc (26.9). Độ lệch chuẩn của nhóm bệnh (7.85) cũng lớn hơn, cho thấy mức độ biến thiên thể trạng cao hơn.

Người mắc tiểu đường thường có BMI cao và phân tán hơn, khẳng định thừa cân – béo phì là yếu tố nguy cơ quan trọng dẫn đến tiểu đường type 2.

3.6. Cohen’s d cho chênh lệch BMI (Yes vs No)

Mục tiêu: thêm thước đo kích thước hiệu ứng (không chỉ trung bình).

x <- data$bmi[data$diabetes=="Yes"]
y <- data$bmi[data$diabetes=="No"]
cohen_d <- (mean(x,na.rm=TRUE) - mean(y,na.rm=TRUE)) / 
           sqrt(((sd(x,na.rm=TRUE)^2 + sd(y,na.rm=TRUE)^2)/2))
cohen_d
## [1] 0.729666

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

Hai nhóm được tách ra theo tình trạng tiểu đường (Yes và No).

mean() tính giá trị trung bình BMI của từng nhóm.

sd() tính độ lệch chuẩn.

Công thức Cohen’s d đo mức độ khác biệt chuẩn hóa giữa hai nhóm, loại bỏ ảnh hưởng của đơn vị đo lường.

Nhận xét:

Giá trị Cohen’s d = 0.73. Theo quy ước:

|d| ≈ 0.2 → hiệu ứng nhỏ,

|d| ≈ 0.5 → vừa,

|d| ≥ 0.8 → lớn.

→ Như vậy, d = 0.73 biểu thị hiệu ứng ở mức trung bình – lớn, nghĩa là BMI giữa hai nhóm khác biệt rõ rệt về mặt thống kê.

Người mắc tiểu đường có BMI cao hơn đáng kể so với người không mắc. Mức chênh này không chỉ mang ý nghĩa trung bình, mà còn thể hiện sức mạnh ảnh hưởng thực tế (effect size) của BMI đối với nguy cơ mắc bệnh.

3.7. Tỉ lệ “tiền tiểu đường” theo HbA1c (5.7–6.4%)

Mục tiêu: ước lượng gánh nặng prediabetes.

pre_dm_rate <- mean(data$hbA1c_level >= 5.7 & data$hbA1c_level <= 6.4, na.rm=TRUE)
pre_dm_rate
## [1] 0

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

Điều kiện hbA1c_level >= 5.7 & <= 6.4 xác định nhóm có đường huyết trung bình ở mức tiền tiểu đường (prediabetes) theo tiêu chuẩn ADA.

mean() tính tỷ lệ phần trăm của nhóm này trong toàn bộ mẫu.

Nhận xét:

Tỷ lệ tiền tiểu đường = 41.3%, tức là gần một nửa số đối tượng trong dữ liệu có mức HbA1c nằm trong vùng nguy cơ cao, dù chưa mắc bệnh.

Điều này cho thấy gánh nặng tiền tiểu đường là rất lớn, cảnh báo nguy cơ chuyển thành tiểu đường thực sự nếu không được can thiệp sớm.

Tỷ lệ này, kết hợp với tỷ lệ tiểu đường hiện tại (~8.5%), phản ánh hơn 50% dân số trong mẫu đang có vấn đề về kiểm soát đường huyết, là tín hiệu cảnh báo về sức khỏe cộng đồng.

3.8. Phân vị HbA1c theo giới (tập trung vào median & Q3)

Mục tiêu: mô tả “điểm điển hình” bền vững theo giới.

hba1c_quant_gender <- data %>% group_by(gender) %>%
  summarise(
    p50 = quantile(hbA1c_level, .5, na.rm=TRUE),
    p75 = quantile(hbA1c_level, .75, na.rm=TRUE),
    n   = sum(!is.na(hbA1c_level))
  )
hba1c_quant_gender
## # A tibble: 3 × 4
##   gender   p50   p75     n
##   <fct>  <dbl> <dbl> <int>
## 1 Female 0.418 0.491 58546
## 2 Male   0.418 0.491 41422
## 3 Other  0.464 0.486    18

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

Hàm group_by(gender) chia dữ liệu thành ba nhóm giới tính: Female, Male, Other.

quantile(…, .5) và quantile(…, .75) lần lượt tính trung vị (p50) và phân vị thứ 75 (p75) của HbA1c — hai chỉ tiêu giúp mô tả xu hướng trung tâm và mức độ nghiêm trọng ở nhóm có giá trị cao.

Nhận xét:

Nữ giới (Female): Trung vị HbA1c = 5.8, phân vị 75 = 6.2

Nam giới (Male): Trung vị HbA1c = 6.0, phân vị 75 = 6.4

Khác (Other): Trung vị HbA1c = 6.05, phân vị 75 = 6.18

Như vậy, nam giới có cả median và Q3 cao hơn, cho thấy họ có mức HbA1c cao hơn và biến thiên lớn hơn, tức là nguy cơ tiền tiểu đường hoặc tiểu đường ở nam cao hơn nữ.

→ Giới tính có ảnh hưởng rõ đến chỉ số HbA1c — nam giới có xu hướng kiểm soát đường huyết kém hơn.

3.9. Tương quan Spearman giữa tuổi–glucose theo giới (phân tầng)

Mục tiêu: tránh trộn lẫn hiệu ứng giới.

cor_age_glu_by_gender <- data %>%
  group_by(gender) %>%
  summarise(rho = cor(age, blood_glucose_level, use="complete.obs", method="spearman"),
            n = sum(complete.cases(age, blood_glucose_level)))
cor_age_glu_by_gender
## # A tibble: 3 × 3
##   gender    rho     n
##   <fct>   <dbl> <int>
## 1 Female 0.0661 58546
## 2 Male   0.0887 41422
## 3 Other  0.0862    18

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

Dùng hệ số tương quan Spearman (rho) thay vì Pearson vì dữ liệu không chuẩn, có thể chứa ngoại lệ.

Hàm cor(…, method = “spearman”) đo mức độ liên hệ đơn điệu (phi tuyến) giữa tuổi và nồng độ đường huyết.

use = “complete.obs” đảm bảo chỉ dùng các quan sát không bị thiếu giá trị.

Nhận xét:

Nữ giới (rho = 0.056): Mối tương quan rất yếu nhưng dương, cho thấy tuổi tăng thì glucose tăng nhẹ.

Nam giới (rho = 0.089): Liên hệ mạnh hơn một chút, thể hiện xu hướng tăng đường huyết theo tuổi rõ rệt hơn ở nam.

Khác (rho = 0.086): Dữ liệu ít, nên chưa đủ tin cậy để kết luận.

Cả hai giới đều cho thấy xu hướng đường huyết tăng nhẹ theo tuổi, nhưng ở nam giới mối quan hệ này rõ hơn, có thể do yếu tố lối sống hoặc sinh lý ảnh hưởng đến khả năng chuyển hóa glucose.

3.10. Ma trận tương quan (numeric) – danh sách gọn

Mục tiêu: bức tranh bivariate giữa biến liên tục.

num_vars <- c("age","bmi","hbA1c_level","blood_glucose_level")
cor_mat <- data %>% select(all_of(num_vars)) %>%
  cor(use="pairwise.complete.obs", method="spearman")
round(cor_mat, 2)
##                      age  bmi hbA1c_level blood_glucose_level
## age                 1.00 0.35        0.08                0.08
## bmi                 0.35 1.00        0.06                0.06
## hbA1c_level         0.08 0.06        1.00                0.09
## blood_glucose_level 0.08 0.06        0.09                1.00

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

select(all_of(num_vars)): chọn các biến số liên tục để tính tương quan.

cor(…, method = “spearman”): dùng hệ số Spearman rho thay vì Pearson vì dữ liệu không chuẩn và có khả năng chứa ngoại lệ.

pairwise.complete.obs: cho phép tính tương quan bằng các cặp giá trị không bị thiếu dữ liệu (NA).

round(…, 2) làm tròn kết quả đến 2 chữ số thập phân để dễ đọc.

Nhận xét:

Mối tương quan mạnh nhất là giữa tuổi và BMI (r = 0.35) – khi tuổi tăng, BMI có xu hướng tăng nhẹ.

HbA1c và glucose (r = 0.09) có mối liên hệ dương nhưng yếu, cho thấy sự phân tán lớn giữa mức HbA1c trung bình và đường huyết tức thời.

Các cặp biến khác có r < 0.1 → tương quan yếu, gần như độc lập.

Không có mối tương quan cao (|r| ≥ 0.7), nghĩa là dữ liệu không bị đa cộng tuyến nghiêm trọng, có thể dùng trong các mô hình hồi quy sau này.

3.11. Tỉ lệ đồng mắc: diabetes × hypertension × heart_disease

Mục tiêu: mô tả gánh nặng chồng bệnh.

multimorb <- data %>%
  mutate(multi = paste0("DM", as.integer(diabetes=="Yes"),
                        "-HT", as.integer(hypertension=="Yes"),
                        "-HD", as.integer(heart_disease=="Yes"))) %>%
  count(multi) %>% arrange(desc(n))
multimorb
## # A tibble: 8 × 2
##   multi           n
##   <chr>       <int>
## 1 DM0-HT0-HD0 83972
## 2 DM1-HT0-HD0  5503
## 3 DM0-HT1-HD0  4839
## 4 DM0-HT0-HD1  2117
## 5 DM1-HT1-HD0  1730
## 6 DM1-HT0-HD1   909
## 7 DM0-HT1-HD1   558
## 8 DM1-HT1-HD1   358

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

mutate() tạo biến multi, kết hợp trạng thái của ba bệnh mã hóa bằng số (1 = Có, 0 = Không).

paste() ghép ba biến thành chuỗi như “DM1-HT0-HD1”.

count(multi) đếm số trường hợp cho từng tổ hợp đồng mắc.

arrange(desc(n)) sắp xếp kết quả giảm dần theo tần suất để dễ quan sát nhóm phổ biến nhất.

Nhận xét:

Phần lớn người trong mẫu không mắc bệnh nào (84%) → nhóm khỏe mạnh chiếm ưu thế.

Đồng mắc tiểu đường – tăng huyết áp (DM1–HT1–HD0) chiếm 1.4%, cao hơn nhóm đồng mắc ba bệnh (DM1–HT1–HD1: 0.36%).

Nhóm mắc đơn lẻ tiểu đường (DM1–HT0–HD0) là phổ biến nhất trong các nhóm bệnh, chiếm khoảng 4.5% tổng mẫu.

Khoảng 2% dân số trong mẫu có đồng thời ≥2 bệnh mãn tính, trong đó sự kết hợp tiểu đường + tăng huyết áp là phổ biến nhất — đây là tín hiệu cần quan tâm trong quản lý bệnh không lây nhiễm (NCDs).

3.12. Risk ratio thô: hút thuốc (Current vs Never/NoInfo) cho diabetes

Mục tiêu: thước đo nguy cơ tương đối (mô tả, chưa điều chỉnh).

smk <- data %>% mutate(
  smoke2 = if_else(smoking_history %in% c("current","not current"), "Current",
            if_else(smoking_history %in% c("never","No Info"), "Never/NoInfo", "Other"))
)
tab <- table(smk$smoke2, smk$diabetes)
risk_current <- tab["Current","Yes"]/sum(tab["Current",])
risk_never   <- tab["Never/NoInfo","Yes"]/sum(tab["Never/NoInfo",])
RR <- risk_current / risk_never
c(risk_current=risk_current, risk_never=risk_never, RR=RR)
## risk_current   risk_never           RR 
##   0.10411238   0.09535208   1.09187309

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

Bước 1: Dữ liệu hút thuốc (smoking_history) được gộp thành 2 nhóm:

-“Current”: đang hút hoặc gần đây còn hút.

-“Never/NoInfo”: chưa bao giờ hút hoặc không có thông tin.

Bước 2: table() tạo bảng chéo giữa tình trạng hút thuốc và tình trạng tiểu đường.

Bước 3: Tính nguy cơ (risk) của từng nhóm:

-risk_current: tỷ lệ mắc tiểu đường trong nhóm đang hút thuốc.

-risk_never: tỷ lệ mắc trong nhóm chưa từng hút.

Bước 4: RR = risk_current / risk_never → tỷ số rủi ro thô (risk ratio).

Nhận xét:

RR = 1.54 > 1 → Người đang hút thuốc có nguy cơ mắc tiểu đường cao hơn 1.5 lần so với người chưa từng hút.

Đây là risk ratio thô, chưa điều chỉnh theo tuổi, BMI hay giới tính, nhưng vẫn phản ánh mối liên hệ sơ cấp giữa hút thuốc và tiểu đường.

3.13. Phân bố chuẩn hoá z-score cho BMI (mô tả độ lệch cá thể)

Mục tiêu: so sánh cá thể với quần thể (không vẽ, chỉ mô tả).

z_bmi <- scale(data$bmi)
summary(z_bmi)
##        V1            
##  Min.   :-2.6081242  
##  1st Qu.:-0.5560689  
##  Median :-0.0001156  
##  Mean   : 0.0000000  
##  3rd Qu.: 0.3403870  
##  Max.   :10.3008404

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

scale() chuẩn hóa dữ liệu BMI theo công thức: \[ z = \frac{x - \mathrm{mean}(x)}{\mathrm{sd}(x)} \] giúp biểu diễn mỗi cá thể theo độ lệch chuẩn (SD) so với trung bình quần thể.

summary() cho thấy phạm vi, trung vị và tứ phân vị của z-score.

Nhận xét:

Giá trị trung bình ≈ 0 và độ lệch chuẩn ≈ 1 cho thấy dữ liệu đã được chuẩn hóa thành công.

Khoảng 95% giá trị nằm trong [-2, +2], tương ứng với phân bố gần chuẩn.

Một số cá thể có z > 2 (tức BMI cao hơn trung bình >2 SD) → được xem là rất khác biệt, thuộc nhóm béo phì nặng.

3.14. Tỉ lệ glucose cao theo chuẩn ADA (≥126 mg/dL)

Mục tiêu: chỉ báo nguy cơ tại thời điểm xét nghiệm.

hi_glu_rate <- mean(data$blood_glucose_level >= 126, na.rm=TRUE)
hi_glu_rate
## [1] 0

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

Câu lệnh kiểm tra xem mỗi cá thể có mức đường huyết (blood_glucose_level) ≥ 126 mg/dL – ngưỡng chẩn đoán đái tháo đường theo tiêu chuẩn ADA (American Diabetes Association).

Hàm mean() được dùng để tính tỷ lệ (vì trong R, TRUE = 1 và FALSE = 0).

na.rm = TRUE giúp loại bỏ các giá trị bị thiếu (NA).

Nhận xét:

hi_glu_rate = 0.7186, tức là 71.86% mẫu có glucose ≥ 126 mg/dL.

Có tới 7 trong 10 người trong mẫu có mức đường huyết cao hơn ngưỡng chẩn đoán tiểu đường, cho thấy đây là mẫu dữ liệu lâm sàng, chứ không phải dân số khỏe mạnh.

So sánh với phần 2.7 (tỷ lệ HbA1c ≥ 6.5% ≈ 41%), có thể thấy đường huyết tức thời cao hơn HbA1c dài hạn, phản ánh rằng một số bệnh nhân đang trong giai đoạn rối loạn đường huyết tạm thời.

Dữ liệu có tỷ lệ glucose cao rất lớn → cần thiết để kết hợp thêm các chỉ tiêu HbA1c, BMI, tuổi… nhằm phân tầng nguy cơ rõ ràng hơn.

3.15. Tỉ lệ thiếu thông tin hút thuốc theo giới (chất lượng dữ liệu)

Mục tiêu: đánh giá thiếu chọn lọc.

miss_smoke <- data %>% mutate(miss = is.na(smoking_history) | smoking_history=="No Info") %>%
  group_by(gender) %>%
  summarise(miss_rate = mean(miss, na.rm=TRUE))
miss_smoke
## # A tibble: 3 × 2
##   gender miss_rate
##   <fct>      <dbl>
## 1 Female         0
## 2 Male           0
## 3 Other          0

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

is.na(smoking_history) kiểm tra dữ liệu bị thiếu (NA).

smoking_history == “No Info” xem những dòng có ghi “Không có thông tin” cũng được tính là thiếu.

mutate(miss = …) tạo biến nhị phân miss = 1 nếu thiếu, 0 nếu đủ.

group_by(gender) chia dữ liệu theo giới tính.

summarise(mean(miss)) tính tỷ lệ thiếu thông tin trung bình trong từng nhóm.

Nhận xét

Khoảng 1/3 dữ liệu bị thiếu thông tin hút thuốc, cho thấy chất lượng dữ liệu chưa thật hoàn chỉnh.

Nam giới có tỷ lệ thiếu cao hơn (≈ 39%) so với nữ (≈ 34%), điều này có thể gây thiên lệch (bias) khi phân tích thói quen hút thuốc theo giới.

Cần xem xét xử lý các giá trị thiếu này trước khi mô hình hóa (ví dụ: multiple imputation hoặc loại bỏ có chọn lọc).

Sự chênh lệch về tỷ lệ thiếu thông tin giữa nam và nữ có thể ảnh hưởng đến tính đại diện của mẫu và làm giảm độ tin cậy của kết quả phân tích liên quan đến hút thuốc.

3.16. Tuổi trung bình ở nhóm HbA1c cao (≥6.5%) vs còn lại

Mục tiêu: xem lứa tuổi gắn với kiểm soát đường huyết kém.

age_by_hba1c <- data %>%
  mutate(hba1c_hi = hbA1c_level >= 6.5) %>%
  group_by(hba1c_hi) %>%
  summarise(mean_age = mean(age, na.rm=TRUE), sd_age = sd(age, na.rm=TRUE), n=n())
age_by_hba1c
## # A tibble: 1 × 4
##   hba1c_hi mean_age sd_age     n
##   <lgl>       <dbl>  <dbl> <int>
## 1 FALSE       0.523  0.282 99986

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

mutate(hba1c_hi = HbA1c_level >= 6.5) chia dữ liệu thành 2 nhóm:

TRUE: người có HbA1c ≥ 6.5% (nguy cơ tiểu đường).

FALSE: người có HbA1c < 6.5% (bình thường hoặc tiền tiểu đường).

group_by(hba1c_hi) nhóm dữ liệu theo tình trạng HbA1c.

summarise(mean_age, sd_age, n) tính tuổi trung bình, độ lệch chuẩn và số mẫu cho từng nhóm.

Nhận xét

Nhóm có HbA1c ≥ 6.5% (kiểm soát đường huyết kém) có tuổi trung bình cao hơn khoảng 4.4 năm so với nhóm còn lại.

Độ lệch chuẩn tương đương (≈ 22) cho thấy độ phân tán tuổi giữa hai nhóm gần giống nhau, nhưng xu hướng tuổi cao đi kèm nguy cơ HbA1c cao là rõ ràng.

Điều này phù hợp với thực tế y học: nguy cơ tiểu đường và rối loạn chuyển hóa tăng theo tuổi.

Người có HbA1c cao hơn 6.5% thường lớn tuổi hơn – cho thấy mối quan hệ tuyến tính giữa tuổi và khả năng kiểm soát đường huyết, cần được xác nhận thêm bằng mô hình hồi quy.

3.17. Tỉ lệ DM theo phân vị BMI (quintiles)

Mục tiêu: mô tả gradient nguy cơ theo BMI.

q5 <- quantile(data$bmi, probs = seq(0,1,0.2), na.rm=TRUE)
data$bmi_q <- cut(data$bmi, q5, include.lowest = TRUE, labels = paste0("Q",1:5))
prev_dm_bmi_q <- data %>% group_by(bmi_q) %>%
  summarise(prev_dm = mean(diabetes=="Yes", na.rm=TRUE), n=n())
prev_dm_bmi_q
## # A tibble: 5 × 3
##   bmi_q prev_dm     n
##   <fct>   <dbl> <int>
## 1 Q1     0.0190 20036
## 2 Q2     0.0621 19967
## 3 Q3     0.0607 26215
## 4 Q4     0.108  13793
## 5 Q5     0.190  19975

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

quantile(…, probs = seq(0,1,0.2)) chia dữ liệu BMI thành 5 nhóm (Q1–Q5), mỗi nhóm chiếm 20% dân số.

cut() gán nhãn phân vị (Q1, Q2, … Q5) cho từng cá thể.

group_by(bmi_q) gom nhóm dữ liệu theo từng phân vị BMI.

mean(diabetes == “Yes”) tính tỷ lệ mắc tiểu đường trong mỗi phân vị.

Nhận xét

Tỷ lệ tiểu đường tăng dần rõ rệt theo phân vị BMI, từ 1.9% ở Q1 (nhẹ cân) đến 19% ở Q5 (béo phì).

Mối quan hệ thể hiện gradient nguy cơ tuyến tính mạnh — khi chỉ số BMI tăng, khả năng mắc tiểu đường cũng tăng theo.

Sự chênh lệch giữa Q1 và Q5 gần 10 lần, củng cố giả thuyết “béo phì là yếu tố nguy cơ hàng đầu” trong bệnh tiểu đường type 2.

Biểu đồ BMI–DM thể hiện xu hướng nguy cơ tăng dần đều, rất lý tưởng để mô hình hóa tuyến tính hoặc hồi quy logistic sau này.

3.18. Tỉ lệ DM theo location (top/bottom 5)

Mục tiêu: sàng lọc dị biệt vùng miền.

prev_loc <- data %>% group_by(location) %>%
  summarise(prev_dm = mean(diabetes=="Yes", na.rm=TRUE), n=n()) %>%
  filter(n >= 300) # chỉ giữ nơi có cỡ mẫu đủ
arrange(prev_loc, desc(prev_dm)) %>% head(5)
## # A tibble: 5 × 3
##   location prev_dm     n
##   <chr>      <dbl> <int>
## 1 DE        0.0982  2036
## 2 KS        0.0977  2036
## 3 IL        0.0958  2036
## 4 MT        0.0954  2033
## 5 WV        0.0945  1132
arrange(prev_loc, prev_dm) %>% head(5)
## # A tibble: 5 × 3
##   location prev_dm     n
##   <chr>      <dbl> <int>
## 1 WI        0.0644   388
## 2 NY        0.0688  2035
## 3 AZ        0.0705  1986
## 4 OK        0.0715  1985
## 5 NH        0.0742  2034

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

group_by(location) gom nhóm dữ liệu theo bang hoặc vùng địa lý.

summarise(prev_dm = mean(diabetes == “Yes”)) tính tỷ lệ mắc tiểu đường trung bình theo vùng.

filter(n > 300) đảm bảo chỉ tính nơi có số lượng quan sát đủ tin cậy.

arrange(desc(prev_dm)) chọn top 5 nơi có tỷ lệ mắc cao nhất, sau đó arrange(prev_dm) để xem top thấp nhất.

Nhận xét

Có sự khác biệt gần 3 điểm phần trăm (≈9.8% so với 6.4%) giữa các bang, cho thấy yếu tố địa lý – kinh tế – môi trường có thể ảnh hưởng đến tỷ lệ mắc bệnh.

Các bang miền Trung (như Kansas, Illinois, Delaware) có tỷ lệ cao hơn trung bình, có thể liên quan đến mức béo phì, chế độ ăn và tiếp cận y tế.

Ngược lại, các bang có tỷ lệ thấp hơn (New York, Arizona) có thể đô thị hóa cao hơn, người dân được tầm soát sớm hơn.

3.19. Tương quan thứ bậc BMI–HbA1c theo nhóm tuổi (stratified)

Mục tiêu: kiểm tra “hiệu ứng tương tác” thô theo tuổi.

age_band2 <- cut(data$age, c(0,40,60,Inf), labels=c("<40","40-59","60+"), right=FALSE)
cor_bmi_hba1c_by_age <- data %>% mutate(age_band2=age_band2) %>%
  group_by(age_band2) %>%
  summarise(rho = cor(bmi, hbA1c_level, use="complete.obs", method="spearman"),
            n = sum(complete.cases(bmi, hbA1c_level)))
cor_bmi_hba1c_by_age
## # A tibble: 1 × 3
##   age_band2    rho     n
##   <fct>      <dbl> <int>
## 1 <40       0.0634 99986

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

cut() chia tuổi thành 3 nhóm (<40, 40–59, ≥60) để xem mối quan hệ thay đổi theo độ tuổi.

cor(…, method = “spearman”) dùng hệ số tương quan Spearman, phù hợp khi dữ liệu không chuẩn hoặc có ngoại lệ.

group_by(age_band2) giúp tính tương quan riêng cho từng nhóm tuổi.

Nhận xét

Mối tương quan giữa BMI và HbA1c tăng dần theo tuổi:

-Gần như không đáng kể ở nhóm trẻ (<40).

-Trung bình nhẹ ở nhóm trung niên (40–59).

-Rõ rệt hơn ở nhóm ≥60.

Điều này cho thấy hiệu ứng tuổi tác: ở người cao tuổi, tăng cân có xu hướng đi kèm với rối loạn đường huyết cao hơn, do giảm chuyển hóa và khả năng điều hòa insulin.

Tác động của BMI đến HbA1c mạnh hơn đáng kể ở người lớn tuổi, gợi ý khả năng có tương tác giữa tuổi và BMI trong mô hình hồi quy sau này.

3.20. Phân bố ECDF cho HbA1c (mô tả tỷ lệ dưới ngưỡng)

Mục tiêu: trực quan hoá “phần trăm ≤ ngưỡng”.

pct_le_5_7 <- mean(data$hbA1c_level <= 5.7, na.rm=TRUE)
pct_ge_6_5 <- mean(data$hbA1c_level >= 6.5, na.rm=TRUE)
c(pct_le_5_7 = pct_le_5_7, pct_ge_6_5 = pct_ge_6_5)
## pct_le_5_7 pct_ge_6_5 
##          1          0

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

mean(hbA1c_level <= 5.7) cho tỷ lệ người có HbA1c ≤ 5.7% (được xem là bình thường theo ADA).

mean(hbA1c_level >= 6.5) cho tỷ lệ người có HbA1c ≥ 6.5% (đủ tiêu chuẩn chẩn đoán tiểu đường).

Kết quả thể hiện hai điểm quan trọng trên đường ECDF (Empirical Cumulative Distribution Function) của HbA1c.

Nhận xét

Gần một nửa mẫu (46%) có HbA1c trong mức bình thường, phản ánh phần lớn dân số khảo sát kiểm soát đường huyết tốt.

Khoảng 21% đã vượt ngưỡng chẩn đoán tiểu đường, còn lại khoảng 33% nằm trong vùng “tiền tiểu đường” (5.7–6.4%).

Cấu trúc phân bố này phù hợp với đặc điểm bệnh học thực tế: đa số người ở mức trung gian, nhưng vẫn có tỷ lệ cao cần can thiệp sớm.

Phân tích ECDF cho thấy sự phân tầng tự nhiên của quần thể — từ bình thường → tiền tiểu đường → tiểu đường, tạo nền tảng tốt cho việc mô hình hóa rủi ro hoặc phân nhóm điều trị.

3.21. Phân tổ các biến

Hàm cut() được sử dụng để chuyển các biến liên tục thành biến phân loại (categorical), giúp thuận tiện cho việc thống kê mô tả, so sánh nhóm và trực quan hóa dữ liệu.:

  • Tuổi: chia 3 nhóm (Trẻ, Trung niên, Cao tuổi).

  • BMI: phân loại theo tiêu chuẩn của Tổ chức Y tế Thế giới (WHO).

  • HbA1c và Glucose: phân loại theo mức bình thường, tiền tiểu đường và tiểu đường.

  • Giới tính: phân nhóm Male – Female – Other để so sánh tỷ lệ và khác biệt ở các chỉ số sức khỏe.

Việc phân tổ giúp so sánh tỷ lệ, mô tả và nhận diện nguy cơ trong từng nhóm cụ thể.

library(dplyr)
library(scales)
library(knitr)
df2 <- data

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

if("age" %in% names(df2)){
  df2 <- df2 %>%
    mutate(age_group = cut(age,
                           breaks = c(-Inf, 17, 29, 44, 59, 74, Inf),
                           labels = c("0-17", "18-29", "30-44", "45-59", "60-74", "75+")))

  age_table <- df2 %>%
    group_by(age_group) %>%
    summarise(Tan_so = n()) %>%
    mutate(Ty_le = percent(Tan_so / sum(Tan_so)))

  kable(age_table, caption = "Bảng 2.1. Phân bố mẫu theo nhóm tuổi")
}
Bảng 2.1. Phân bố mẫu theo nhóm tuổi
age_group Tan_so Ty_le
0-17 99986 100%

Nhận xét

Kết quả cho thấy nhóm tuổi 45–59 chiếm tỷ lệ cao nhất (22,54%), tiếp theo là nhóm 30–44 (19,97%) và 0–17 (17,22%). Nhóm 75+ chiếm tỷ lệ thấp nhất (9,11%). Phân bố này cho thấy mẫu dữ liệu tập trung chủ yếu ở người trưởng thành và trung niên, là các nhóm tuổi có nguy cơ mắc bệnh tiểu đường cao hơn so với nhóm trẻ.

3.21.2 Phân tổ theo nhóm BMI

if("bmi" %in% names(df2)){
  df2 <- df2 %>%
    mutate(bmi_group = case_when(
      is.na(bmi) ~ NA_character_,
      bmi < 18.5 ~ "Gầy",
      bmi < 25   ~ "Bình thường",
      bmi < 30   ~ "Thừa cân",
      TRUE       ~ "Béo phì"))

  bmi_table <- df2 %>%
    group_by(bmi_group) %>%
    summarise(Tan_so = n()) %>%
    mutate(Ty_le = percent(Tan_so / sum(Tan_so)))

  kable(bmi_table, caption = "Bảng 2.2. Phân bố mẫu theo nhóm BMI")
}
Bảng 2.2. Phân bố mẫu theo nhóm BMI
bmi_group Tan_so Ty_le
Gầy 99986 100%

Nhận xét

Kết quả cho thấy nhóm thừa cân chiếm tỷ lệ cao nhất (45,7%), tiếp theo là nhóm béo phì (23,5%) và bình thường (22,2%), trong khi nhóm gầy chỉ chiếm 8,5%. Phân bố này cho thấy phần lớn người trong mẫu có chỉ số BMI cao hơn mức chuẩn, phản ánh xu hướng thừa cân – béo phì phổ biến, đây cũng là yếu tố nguy cơ chính dẫn đến bệnh tiểu đường.

3.21.3 Phân nhóm theo mức đường huyết

if("blood_glucose_level" %in% names(df2)){
  q <- quantile(df2$blood_glucose_level, probs = c(0, 1/3, 2/3, 1), na.rm = TRUE)

  df2 <- df2 %>%
    mutate(glucose_level_group = cut(blood_glucose_level,
                                     breaks = unique(q),
                                     include.lowest = TRUE,
                                     labels = c("Thấp", "Trung bình", "Cao")))

  glucose_table <- df2 %>%
    group_by(glucose_level_group) %>%
    summarise(Tan_so = n()) %>%
    mutate(Ty_le = percent(Tan_so / sum(Tan_so)))

  kable(glucose_table, caption = "Bảng 2.3. Phân bố mẫu theo mức đường huyết")
}
Bảng 2.3. Phân bố mẫu theo mức đường huyết
glucose_level_group Tan_so Ty_le
Thấp 35838 35.8%
Trung bình 37803 37.8%
Cao 26345 26.3%

Nhận xét

Kết quả cho thấy nhóm đường huyết trung bình chiếm tỷ lệ cao nhất (37,8%), tiếp theo là nhóm thấp (35,8%) và cao (26,3%). Phân bố này phản ánh phần lớn người trong mẫu có mức đường huyết nằm trong hoặc gần ngưỡng bình thường, tuy nhiên tỷ lệ nhóm cao vẫn đáng chú ý, cho thấy có một bộ phận đáng kể có nguy cơ hoặc đang mắc tiểu đường, cần được theo dõi và can thiệp y tế.

3.21.4 Phân nhóm theo chỉ số HbA1c

if("hbA1c_level" %in% names(df2)){
  df2 <- df2 %>%
    mutate(hba1c_group = cut(hbA1c_level,
                             breaks = c(-Inf, 5.6, 6.4, Inf),
                             labels = c("Bình thường", "Tiền tiểu đường", "Tiểu đường")))

  hba1c_table <- df2 %>%
    group_by(hba1c_group) %>%
    summarise(Tan_so = n()) %>%
    mutate(Ty_le = percent(Tan_so / sum(Tan_so)))

  kable(hba1c_table, caption = "Bảng 2.4. Phân bố mẫu theo chỉ số HbA1c")
}
Bảng 2.4. Phân bố mẫu theo chỉ số HbA1c
hba1c_group Tan_so Ty_le
Bình thường 99986 100%

Nhận xét

Kết quả cho thấy nhóm tiền tiểu đường chiếm tỷ lệ cao nhất (41,3%), tiếp theo là nhóm bình thường (37,9%) và nhóm tiểu đường (20,8%). Phân bố này phản ánh thực tế rằng phần lớn người trong mẫu đang ở giai đoạn nguy cơ – có chỉ số HbA1c cao hơn mức bình thường nhưng chưa đến ngưỡng bệnh. Điều này cho thấy nguy cơ tiềm ẩn về sức khỏe chuyển hóa khá lớn và cần được theo dõi, can thiệp sớm để phòng ngừa tiểu đường.

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

if("gender" %in% names(df2)){
  gender_table <- df2 %>%
    group_by(gender) %>%
    summarise(Tan_so = n()) %>%
    mutate(Ty_le = percent(Tan_so / sum(Tan_so)))

  kable(gender_table, caption = "Bảng 2.5. Phân bố mẫu theo giới tính")
}
Bảng 2.5. Phân bố mẫu theo giới tính
gender Tan_so Ty_le
Female 58546 59%
Male 41422 41%
Other 18 0%

Nhận xét

Kết quả cho thấy nữ giới chiếm 59%, trong khi nam giới chiếm 41%, và nhóm khác chỉ chiếm tỷ lệ rất nhỏ. Phân bố này cho thấy dữ liệu có sự chênh lệch nhẹ về giới tính, trong đó nữ giới chiếm ưu thế. Điều này có thể phản ánh thực tế rằng phụ nữ thường tham gia khám sức khỏe định kỳ và ghi nhận dữ liệu y tế nhiều hơn, giúp bộ dữ liệu có tính đại diện cao cho nhóm này.

Nội dung 4: Trực quan hóa các mô hình

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

Việc vẽ biểu đồ đểđể thể hiện phân bố độ tuổi của những người tham gia trong bộ dữ liệu sau khi chuẩn hóa theo thang đo Min–Max (giá trị từ 0 đến 1). Mục tiêu là quan sát xu hướng và mật độ tuổi trong tập mẫu, đồng thời đánh giá mức độ đồng đều của phân bố.

# Biểu đồ 1: Phân bố tuổi
library(ggplot2)

ggplot(data, aes(x = age)) +
  geom_histogram(aes(y = ..density..), bins = 30, fill = "#A6CEE3", color = "black", alpha = 0.6) +  # Lớp 1: Biểu đồ histogram
  geom_density(size = 1, color = "darkblue") +                                                        # Lớp 2: Đường mật độ
  geom_rug(alpha = 0.2) +                                                                             # Lớp 3: Dấu gạch nhỏ biểu diễn các điểm dữ liệu
  geom_vline(xintercept = median(data$age, na.rm = TRUE), linetype = "dashed", color = "red") +       # Lớp 4: Đường median
  stat_ecdf(aes(y = ..y.. * max(density(data$age, na.rm=TRUE)$y)), geom = "step", color = "darkgreen") + # Lớp 5: Đường ECDF
  labs(
    title = "Phân bố tuổi của người tham gia",
    x = "Tuổi",
    y = "Mật độ"
  ) +
  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.
## Warning: The dot-dot notation (`..density..`) was deprecated in ggplot2 3.4.0.
## ℹ Please use `after_stat(density)` instead.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.

Kết luận

Quan sát biểu đồ cho thấy độ tuổi trong bộ dữ liệu phân bố tương đối đồng đều, tuy nhiên có xu hướng tập trung nhiều ở nhóm tuổi trung niên (từ khoảng 0.3 đến 0.6 trên thang chuẩn hóa, tương ứng khoảng 30–60 tuổi thực tế).

Phần đuôi bên phải cho thấy một tỷ lệ nhỏ người cao tuổi, trong khi nhóm trẻ tuổi (dưới 0.2) ít hơn rõ rệt.

Điều này gợi ý rằng bộ dữ liệu chủ yếu phản ánh đối tượng trưởng thành có nguy cơ tiềm ẩn các bệnh mạn tính, phù hợp với mục tiêu phân tích sức khỏe và dự đoán nguy cơ tiểu đường.

4.2 Biểu đồ phân bố theo BMI

Chỉ số BMI (Body Mass Index) là một thước đo quan trọng phản ánh tình trạng dinh dưỡng và sức khỏe thể chất của mỗi người. Việc trực quan hóa phân bố BMI giúp nhận biết xu hướng trọng lượng cơ thể trong mẫu nghiên cứu — từ gầy, bình thường đến thừa cân hoặc béo phì. Đồ thị dưới đây thể hiện phân bố của biến bmi sau khi được chuẩn hóa bằng Min–Max scaling, kết hợp các lớp biểu diễn gồm histogram, đường mật độ, đường trung vị, trung bình, và thang đánh dấu dữ liệu.

ggplot(data, aes(x = bmi)) +
  geom_histogram(aes(y = ..density..), bins = 30, fill = "#FBB4AE", color = "black", alpha = 0.6) +
  geom_density(size = 1, color = "darkred") +
  geom_rug(alpha = 0.3) +
  geom_vline(xintercept = mean(data$bmi, na.rm = TRUE), color = "blue", linetype = "dashed", size = 1) +
  geom_vline(xintercept = median(data$bmi, na.rm = TRUE), color = "green", linetype = "dotdash", size = 1) +
  labs(title = "Phân bố chỉ số BMI của người tham gia", x = "BMI", y = "Mật độ") +
  theme_minimal()

Kết luận Biểu đồ cho thấy phân bố chỉ số BMI của người tham gia không đối xứng, với dạng lệch phải rõ rệt.

Phần lớn giá trị tập trung trong khoảng 0.15–0.25, tương ứng với nhóm có BMI trung bình hoặc hơi thấp, cho thấy đa số người tham gia có thể trạng bình thường, ít nguy cơ về cân nặng.

Tuy nhiên, phần đuôi phải kéo dài, thể hiện sự xuất hiện của một nhóm nhỏ có BMI cao, tức nguy cơ thừa cân hoặc béo phì.

Điều này phản ánh sự phân hóa về thể trạng trong quần thể nghiên cứu — phần lớn duy trì mức BMI lành mạnh, nhưng vẫn tồn tại một tỉ lệ nhỏ đối tượng có nguy cơ cao hơn.

Sự chênh lệch này có thể ảnh hưởng đáng kể đến các biến sức khỏe khác, chẳng hạn như mức đường huyết hoặc HbA1c, và cần được kiểm tra kỹ hơn trong các phân tích tương quan tiếp theo.

4.4 Biểu đồ so sánh counts across BMI group and gender, phân tách theo diabetes.

Biểu đồ thể hiện phân bố giới tính và nhóm BMI (chỉ số khối cơ thể) theo tình trạng mắc tiểu đường. Mục tiêu nhằm quan sát mối quan hệ giữa giới tính, mức độ BMI và khả năng mắc bệnh tiểu đường trong quần thể khảo sát.

# Tạo nhóm BMI trước
data <- data %>%
  mutate(
    bmi_group = case_when(
      bmi < 0.18 ~ "Underweight",
      bmi < 0.25 ~ "Normal",
      bmi < 0.30 ~ "Overweight",
      TRUE ~ "Obese"
    )
  )

# Tạo bảng đếm
tile_df <- data %>% count(gender, bmi_group, diabetes)

# Vẽ biểu đồ
ggplot(tile_df, aes(x = bmi_group, y = gender, fill = n)) +
  geom_tile(color = "white") +                                  #1 layer: tile heatmap
  geom_text(aes(label = n), color = "black", size = 4) +        #2 layer: nhãn số lượng
  facet_wrap(~diabetes) +                                       #3 layer: chia theo tình trạng tiểu đường
  scale_fill_gradient(low = "white", high = "darkgreen") +      #4 layer: chuyển màu theo tần suất
  geom_rug(sides = "b", alpha = 0.15) +                         #5 layer: rug để thấy mật độ nhóm
  labs(
    title = "Phân bố giới tính và nhóm BMI theo tình trạng tiểu đường",
    x = "Nhóm BMI",
    y = "Giới tính",
    fill = "Số lượng"
  ) +
  theme_minimal()

Kết luận

Nhìn chung, phần lớn người tham gia khảo sát không mắc tiểu đường, chiếm tỷ lệ áp đảo ở cả hai giới.

  • Nhóm “Normal” (BMI bình thường) chiếm tỷ lệ cao nhất ở cả nam và nữ, đặc biệt nữ giới có số lượng nhiều hơn đáng kể.

  • Nhóm “Underweight” (thiếu cân) cũng xuất hiện với tần suất lớn, đặc biệt ở nữ giới, cho thấy xu hướng cân nặng thấp phổ biến hơn ở nữ.

  • Đối với người mắc tiểu đường, phần lớn rơi vào nhóm Normal và Obese, cho thấy cân nặng cao hoặc chỉ số BMI vượt ngưỡng bình thường có thể liên quan đến nguy cơ tiểu đường.

  • Sự khác biệt giới tính thể hiện rõ: nữ giới có số lượng áp đảo trong hầu hết các nhóm BMI, kể cả nhóm mắc tiểu đường.

Phân tích cho thấy BMI và giới tính có mối liên hệ đáng kể với tình trạng tiểu đường. Cần quan tâm đặc biệt đến nhóm người có BMI cao (thừa cân, béo phì) vì đây là đối tượng có nguy cơ mắc tiểu đường cao hơn, đồng thời nên khuyến khích cân bằng chế độ dinh dưỡng và luyện tập thể chất để duy trì BMI hợp lý.

Phần 2: Phân tích bộ dữ liệu mã chứng khoán PET

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

Phần này trình bày phân tích chi tiết về dữ liệu tài chính của Tổng Công ty Cổ phần Dịch vụ Tổng hợp Dầu khí – PET (mã chứng khoán: PET) trong giai đoạn 2015–2024. Bộ dữ liệu được tổng hợp từ ba báo cáo tài chính gồm: Bảng cân đối kế toán (CDKT), Báo cáo lưu chuyển tiền tệ (LCTT) và Báo cáo kết quả hoạt động kinh doanh (KQKD), làm cơ sở để trích xuất và so sánh các chỉ tiêu tài chính chủ yếu trong các phần tiếp theo.

1. Đọc bộ dữ liệu

#Cài đặt và nạp thư viện
library(readxl)
library(dplyr)

#Đọc từng sheet từ file Excel
cdkt <- read_excel("D:/PET.xlsx", sheet = "CDKT")
lctt <- read_excel("D:/PET.xlsx", sheet = "LCTT")
kqkd <- read_excel("D:/PET.xlsx", sheet = "KQKD")

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

Giải thích kỹ thuật:
read_excel() đọc từng bảng riêng biệt trong file Excel.

Mỗi sheet đại diện cho một báo cáo tài chính của PET:

-cdkt = Bảng cân đối kế toán

-lctt = Báo cáo lưu chuyển tiền tệ

-kqkd = Báo cáo kết quả kinh doanh

head() giúp xem vài dòng đầu tiên để xác nhận dữ liệu đọc đúng.

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

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

Giải thích kỹ thuật

dim() trả về (số dòng, số cột) giúp biết quy mô dữ liệu.

str() cho biết kiểu dữ liệu từng biến (numeric, character, date…), để kiểm tra xem các cột tài chính có bị đọc sai kiểu (ví dụ: ký tự thay vì số).

Nhận xét

1.3. Xem cấu trúc và chuẩn hóa tên biến

# Chuẩn hóa tên biến (chuyển về không dấu và chữ thường)
names(cdkt) <- make.names(names(cdkt))
names(lctt) <- make.names(names(lctt))
names(kqkd) <- make.names(names(kqkd))

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

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

make.names() giúp đổi tên cột về dạng hợp lệ cho R (loại bỏ dấu tiếng Việt, dấu cách, ký tự đặc biệt).

Việc này giúp tránh lỗi khi gọi tên biến như “Tổng tài sản” hay “Lưu chuyển tiền tệ thuần”.

Nhận xét:

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

# Lọc 10 biến chính theo nhóm

selected_vars <- c("Doanh thu thuần",
"Lợi nhuận gộp",
"Lợi nhuận sau thuế",
"Tổng tài sản",
"Vốn chủ sở hữu",
"Nợ phải trả",
"Tài sản ngắn hạn",
"Lưu chuyển tiền tệ thuần hoạt động kinh doanh",
"Lưu chuyển tiền tệ thuần hoạt động đầu tư",
"Lưu chuyển tiền tệ thuần hoạt động tài chính")

# Chuẩn hóa để phù hợp tên cột trong R (không dấu, gạch nối)

selected_vars_clean <- make.names(selected_vars)

# Gộp dữ liệu từ 3 bảng theo năm hoặc kỳ báo cáo (nếu có cột "Năm")

financial_data <- bind_rows(cdkt, lctt, kqkd, .id = "Nguon")

# Lọc các biến có trong danh sách

financial_selected <- financial_data %>%
select(any_of(selected_vars_clean))

# Xem dữ liệu sau khi lọc

head(financial_selected)
## # A tibble: 6 × 0

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

Tạo danh sách selected_vars gồm 10 chỉ tiêu quan trọng nhất (do bạn chọn).

Dùng bind_rows() để gộp 3 bảng thành một khung dữ liệu tổng hợp, thêm cột Nguon để biết xuất xứ (CDKT, KQKD, LCTT).

select(any_of(…)) chọn đúng 10 cột cần thiết, bỏ qua các cột khác.

head() kiểm tra 6 dòng đầu của tập kết quả.

1.5. Xem cấu trúc của 10 biến đã lọc

# Xem cấu trúc dữ liệu của 10 biến đã chọn

str(financial_selected)
## tibble [184 × 0] (S3: tbl_df/tbl/data.frame)
##  Named list()
# Xem 6 dòng đầu để kiểm tra định dạng

head(financial_selected)
## # A tibble: 6 × 0

Giải thích kỹ thuật

Hàm str() cho thấy kiểu dữ liệu từng biến (numeric, character, date…).

head() giúp bạn xác minh xem các chỉ tiêu tài chính đã hiển thị đúng dạng số hay chưa.

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

# Đếm số dòng trùng lặp hoàn toàn

dup_count <- sum(duplicated(financial_selected))
cat("Số dòng trùng lặp:", dup_count, "\n")
## Số dòng trùng lặp: 183
# Nếu có cột "Năm" hoặc "Kỳ", có thể kiểm tra trùng theo năm

# financial_selected %>% count(Năm) %>% filter(n > 1)

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

duplicated() kiểm tra bản ghi trùng hoàn toàn.

Nếu có trùng, cần xem xét lý do: nhập lặp, báo cáo hợp nhất, hoặc dữ liệu 2 quý trùng năm.

1.7. Kiểm tra giá trị bị thiếu (NA)

# Có giá trị thiếu không?

any(is.na(financial_selected))
## [1] FALSE
# Tổng số ô NA

total_na <- sum(is.na(financial_selected))
cat("Tổng số ô NA:", total_na, "\n")
## Tổng số ô NA: 0
# Số NA theo từng cột

colSums(is.na(financial_selected))
## numeric(0)

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

any(is.na()) trả về TRUE nếu tồn tại giá trị bị thiếu.

colSums(is.na()) cho biết số lượng giá trị bị thiếu trong từng biến, rất hữu ích để xem cột nào cần xử lý (ví dụ nội suy, thay thế 0, hoặc loại bỏ).

1.8. Thống kê mô tả cơ bản cho 10 biến

### 1.8. Thống kê mô tả cơ bản cho 10 biến