1 Chương 1: Phân tích dữ liệu tai nạn giao thông Hoa Kỳ năm 2022

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

Bộ dữ liệu US Accidents (2022) được tổng hợp và công bố công khai trên nền tảng Kaggle bởi Sobhan Moosavi, thuộc nhóm nghiên cứu của Đại học Ohio State. Đây là một trong những bộ dữ liệu quy mô lớn và toàn diện nhất về các vụ tai nạn giao thông xảy ra tại Hoa Kỳ, được xây dựng với mục tiêu phục vụ cho việc phân tích, mô hình hóa và dự báo rủi ro giao thông. Dữ liệu được thu thập tự động và liên tục từ nhiều nguồn giám sát giao thông khác nhau, bao gồm cả nguồn dữ liệu công cộng và cảm biến thực tế. Việc tổng hợp dữ liệu được thực hiện thông qua hai kênh chính:

API và mạng lưới cảm biến (Sensor Networks): Trong đó dữ liệu được thu thập từ các cảm biến giao thông, camera giám sát, thiết bị đo đạc, và ứng dụng bản đồ giao thông trực tuyến. Các nền tảng chính bao gồm Bing Maps Traffic API, MapQuest, TomTom, INRIX, và Here Traffic. Các nguồn này cung cấp dữ liệu theo thời gian thực về tình trạng giao thông, sự cố, mật độ xe và điều kiện đường xá tại 49 bang của Hoa Kỳ.

US Department of Transportation & State DOTs: Các nguồn dữ liệu chính phủ như Bộ Giao thông vận tải Hoa Kỳ (US Department of Transportation – USDOT), Sở Giao thông vận tải các bang (State DOTs), cùng với Cục Quản lý Đại dương và Khí quyển Quốc gia (NOAA Weather API). Các cơ quan này cung cấp thông tin công khai về điều kiện thời tiết, vị trí địa lý, tình trạng đường xá, và mức độ nghiêm trọng của các vụ tai nạn giao thông.

1.1.1 Cấu trúc tổng quan bộ dữ liệu

Đọc dữ liệu:

library(readr)
Accidents <- read_csv("Accidents.csv")

Hàm read_csv() (thuộc gói readr) được dùng để đọc tệp dữ liệu định dạng CSV (Comma-Separated Values

Nạp gói cần thiết:

library(tidyverse)

Trong nghiên cứu này, nhóm sử dụng hệ sinh thái tidyverse – một tập hợp các gói R được thiết kế nhất quán cho quy trình phân tích dữ liệu hiện đại. Lệnh library(tidyverse) tự động nạp các gói cốt lõi gồm dplyr (tiền xử lý và thao tác dữ liệu dạng bảng), ggplot2 (trực quan hóa theo ngữ pháp đồ thị), readr (nhập dữ liệu văn bản/CSV), tidyr (chuẩn hóa cấu trúc dữ liệu “gọn” – tidy data), tibble (khung dữ liệu nâng cao), stringr (xử lý chuỗi), forcats (quản lý biến phân loại) và purrr (lập trình hàm). Việc nạp tidyverse giúp chuẩn hóa cú pháp, giảm sai sót thao tác, và đảm bảo tái lập (reproducibility) cho toàn bộ pipeline: nhập liệu → làm sạch/mã hóa → phân tích mô tả → trực quan hóa.

Tổng quan bộ dữ liệu:

glimpse(Accidents )
## Rows: 1,762,452
## Columns: 17
## $ ID                <chr> "A-512230", "A-512231", "A-512232", "A-512233", "A-5…
## $ Source            <chr> "Source2", "Source2", "Source2", "Source2", "Source2…
## $ Severity          <dbl> 1, 1, 1, 1, 2, 1, 2, 2, 1, 1, 1, 3, 1, 1, 2, 2, 1, 1…
## $ Start_Time        <dttm> 2022-09-08 05:49:30, 2022-09-08 02:02:05, 2022-09-0…
## $ End_Time          <dttm> 2022-09-08 06:34:53, 2022-09-08 04:31:32, 2022-09-0…
## $ Start_Lat         <dbl> 41.94680, 34.52117, 37.54284, 40.89663, 41.40936, 39…
## $ Start_Lng         <dbl> -88.20809, -117.95808, -77.44178, -81.17845, -81.644…
## $ `Distance(mi)`    <dbl> 0.00, 0.00, 0.00, 0.00, 1.91, 1.59, 0.00, 0.00, 0.00…
## $ Street            <chr> "Army Trail Rd", "Pearblossom Hwy", "N 2nd St", "Atl…
## $ City              <chr> "Bartlett", "Littlerock", "Richmond", "Alliance", "I…
## $ State             <chr> "IL", "CA", "VA", "OH", "OH", "PA", "OH", "OH", "SC"…
## $ Weather_Timestamp <dttm> 2022-09-08 05:52:00, 2022-09-08 01:53:00, 2022-09-0…
## $ `Temperature(F)`  <dbl> 58, 86, 68, 62, 63, 65, 53, 58, 70, 70, 70, 65, 66, …
## $ `Humidity(%)`     <dbl> 90, 28, 96, 86, 87, 93, 99, 97, 100, 97, 100, 90, 87…
## $ `Pressure(in)`    <dbl> 29.24, 27.35, 29.71, 28.71, 29.37, 29.45, 29.01, 29.…
## $ `Visibility(mi)`  <dbl> 10, 10, 10, 7, 7, 9, 7, 10, 10, 10, 10, 10, 10, 7, 1…
## $ Weather_Condition <chr> "Fair", "Fair", "Mostly Cloudy", "Mostly Cloudy", "P…

Glimpse(Accidents) thực hiện kiểm toán nhanh cấu trúc: in số dòng/cột, kiểu dữ liệu của từng biến và một số giá trị mẫu. Đây là bước kiểm soát chất lượng bắt buộc trước khi phân tích, giúp xác nhận dữ liệu đã được đọc đúng định dạng và phát hiện sớm các bất thường Sau khi nạp xong bộ dữ liệu US_new.csv, chúng ta tiếp tục đi phân tích sau vào dữ liệu của bộ dữ liệu dùng hàm glimpse().

Kết quả cho thấy bộ dữ liệu bao gồm 1.762.452 bản ghi về các vụ tai nạn giao thông tại Hoa Kỳ, bản ghi này lưu trữ 17 biến phản ánh đầy đủ ba trục thông tin cốt lõi: thời gian, không gian và thời tiết.

Về kiểu dữ liệu, các biến định tính như ID, Source, Street, City, StateWeather_Condition giúp nhận diện bản ghi, nguồn thu thập và bối cảnh địa lý – khí tượng. Các biến định lượng gồm Severity, Start_Lat, Start_Lng, Distance(mi), Temperature(F), Humidity(%), Pressure(in)Visibility(mi) phục vụ mô tả và mô hình hóa. Ba biến thời gian Start_Time, End_TimeWeather_Timestamp được nhận diện ở dạng POSIXct (kiểu dữ liệu thời gian–ngày giờ trong R (thuộc họ “POSIXt”) dùng để lưu số giây kể từ mốc 1970-01-01 00:00:00 UTC), cho phép tách năm, tháng, thứ và giờ để nghiên cứu các mẫu hình theo chu kỳ.

Bộ dữ liệu: Tai nạn giao thông tại Hoa Kỳ (US Accidents 2022) bao gồm:

Tên: US Accidents – A Countrywide Traffic Accident Dataset

Mục đích: Phân tích các yếu tố ảnh hưởng đến mức độ nghiêm trọng của tai nạn giao thông trên toàn nước Mỹ năm 2022

Quy mô: Khoảng 1. triệu bản ghi, bao phủ 49 tiểu bang

Tần suất thu thập: Liên tục từ các nguồn API giao thông và thời tiết công cộng

Địa điểm: Gồm 49 tiểu bang

1.1.2 Xem nhanh 5 dòng đầu bộ dữ liệu

head(Accidents, 5)
## # A tibble: 5 × 17
##   ID       Source  Severity Start_Time          End_Time            Start_Lat
##   <chr>    <chr>      <dbl> <dttm>              <dttm>                  <dbl>
## 1 A-512230 Source2        1 2022-09-08 05:49:30 2022-09-08 06:34:53      41.9
## 2 A-512231 Source2        1 2022-09-08 02:02:05 2022-09-08 04:31:32      34.5
## 3 A-512232 Source2        1 2022-09-08 05:14:12 2022-09-08 07:38:17      37.5
## 4 A-512233 Source2        1 2022-09-08 06:22:57 2022-09-08 06:52:42      40.9
## 5 A-512234 Source2        2 2022-09-08 06:36:20 2022-09-08 07:05:58      41.4
## # ℹ 11 more variables: Start_Lng <dbl>, `Distance(mi)` <dbl>, Street <chr>,
## #   City <chr>, State <chr>, Weather_Timestamp <dttm>, `Temperature(F)` <dbl>,
## #   `Humidity(%)` <dbl>, `Pressure(in)` <dbl>, `Visibility(mi)` <dbl>,
## #   Weather_Condition <chr>

Hàm head() trong R được dùng để hiển thị một số dòng đầu tiên của bảng dữ liệu (data frame hoặc tibble).

Khi chạy head(Accidents, 5), R hiển thị 5 dòng đầu tiên của bộ dữ liệu để bạn kiểm tra cấu trúc, định dạng và nội dung của các biến.

Cho thấy dữ liệu được đọc đúng: đủ 17 cột, thời gian hiển thị chuẩn POSIXct, tọa độ là số thập phân, các biến có đơn vị giữ đúng nhãn (Distance(mi), Temperature(F), Pressure(in), Visibility(mi)). Mỗi dòng tương ứng một vụ tai nạn với vị trí, thời gian, Severity và bối cảnh thời tiết. Đây là ảnh “soi nhanh” để xác nhận cấu trúc trước khi phân tích sâu hơn.

Dữ liệu bao quát nhiều khía cạnh: mức độ tai nạn, vị trí địa lý, và điều kiện thời tiết, cho phép phân tích mối quan hệ giữa yếu tố môi trường và mức độ nghiêm trọng của tai nạn.

Nhìn vào 5 dòng đầu, ta thấy đa số vụ tai nạn có Severity = 1–2 (mức nhẹ), xảy ra vào buổi sáng (khoảng 5–7 giờ), và thời tiết nhìn chung “Fair” hoặc “Mostly Cloudy” — đây là dấu hiệu cho thấy tai nạn không chỉ xảy ra trong điều kiện xấu, mà còn có thể do yếu tố con người hoặc mật độ giao thông.

1.1.3 Xem nhanh 5 dòng cuối bộ dữ liệu

tail(Accidents, 5)
## # A tibble: 5 × 17
##   ID        Source  Severity Start_Time          End_Time            Start_Lat
##   <chr>     <chr>      <dbl> <dttm>              <dttm>                  <dbl>
## 1 A-5464714 Source1        2 2022-10-08 13:29:50 2022-10-08 14:44:50      32.9
## 2 A-5464715 Source1        2 2022-10-11 05:30:00 2022-10-11 06:47:57      33.9
## 3 A-5464716 Source1        2 2022-08-25 09:41:00 2022-08-25 11:00:54      33.9
## 4 A-5464717 Source1        2 2022-06-28 14:18:09 2022-06-28 19:29:33      40.7
## 5 A-5464718 Source1        2 2022-09-18 08:48:00 2022-09-18 10:30:00      30.3
## # ℹ 11 more variables: Start_Lng <dbl>, `Distance(mi)` <dbl>, Street <chr>,
## #   City <chr>, State <chr>, Weather_Timestamp <dttm>, `Temperature(F)` <dbl>,
## #   `Humidity(%)` <dbl>, `Pressure(in)` <dbl>, `Visibility(mi)` <dbl>,
## #   Weather_Condition <chr>

Hàm tail() trong R được dùng để hiển thị một số dòng cuối cùng của bảng dữ liệu (data frame hoặc tibble).

Khi chạy tail(Accidents, 5), R sẽ hiển thị 5 dòng cuối cùng của bộ dữ liệu — giúp xem các bản ghi mới nhất hoặc kết thúc của tập dữ liệu.

5 dòng cuối thể hiện các vụ tai nạn gần đây nhất trong bộ dữ liệu, xảy ra từ tháng 6 đến tháng 10 năm 2022.

Các vụ này đều có mức độ nghiêm trọng (Severity) = 2, tức là tai nạn mức trung bình, không quá nhẹ cũng không quá nặng.

Các vụ xảy ra ở nhiều bang khác nhau như Texas (TX), California (CA), New York (NY) → cho thấy phạm vi dữ liệu bao phủ nhiều khu vực tại Hoa Kỳ.

Điều kiện thời tiết trong 5 vụ cuối phần lớn là “Cloudy”, “Partly Cloudy”, “Mostly Cloudy”, có một số vụ thời tiết tốt (“Fair”).

Nhiệt độ dao động từ 64°F đến 83°F, độ ẩm từ 36% đến 96%, và tầm nhìn (Visibility) từ 5 đến 10 dặm — điều này cho thấy các vụ tai nạn không nhất thiết xảy ra trong điều kiện thời tiết xấu.

1.1.4 Kích thước bảng dữ liệu

nrow(Accidents); ncol(Accidents); dim(Accidents)
## [1] 1762452
## [1] 17
## [1] 1762452      17

nrow(Accidents); ncol(Accidents); dim(Accidents) lần lượt trả về số dòng, số cột, và kích thước đầy đủ (dòng × cột) của bảng Accidents. Đây là thao tác cơ bản để nắm quy mô dữ liệu trước khi phân tích.

Kết quả cho thấy tập dữ liệu có 1.762.452 bản ghi và 17 biến. Quy mô này rất lớn, đủ mạnh cho phân tích mô tả chi tiết theo không gian–thời gian và điều kiện thời tiết.

1.1.5 Tên các biến (liệt kê cột)

names(Accidents) 
##  [1] "ID"                "Source"            "Severity"         
##  [4] "Start_Time"        "End_Time"          "Start_Lat"        
##  [7] "Start_Lng"         "Distance(mi)"      "Street"           
## [10] "City"              "State"             "Weather_Timestamp"
## [13] "Temperature(F)"    "Humidity(%)"       "Pressure(in)"     
## [16] "Visibility(mi)"    "Weather_Condition"

names(Accidents) trả về một vector ký tự chứa tên tất cả các cột (biến) của bảng dữ liệu Accidents. Đây là cách nhanh để kiểm tra có bao nhiêu biến và đang đặt tên thế nào

Kết quả hiển thị 17 tên biến, phản ánh đúng cấu trúc bộ US Accidents: Nhận diện & định vị: ID, Source, Street, City, State, Start_Lat, Start_Lng. Thời gian: Start_Time, End_Time, Weather_Timestamp (mốc quan trắc thời tiết). Mức độ sự kiện: Severity (thang 1–4). Bối cảnh & đo lường: Distance(mi), Temperature(F), Humidity(%), Pressure(in), Visibility(mi), Weather_Condition

1.1.6 Kiểu dữ liệu từng biến

sapply(Accidents, class)
## $ID
## [1] "character"
## 
## $Source
## [1] "character"
## 
## $Severity
## [1] "numeric"
## 
## $Start_Time
## [1] "POSIXct" "POSIXt" 
## 
## $End_Time
## [1] "POSIXct" "POSIXt" 
## 
## $Start_Lat
## [1] "numeric"
## 
## $Start_Lng
## [1] "numeric"
## 
## $`Distance(mi)`
## [1] "numeric"
## 
## $Street
## [1] "character"
## 
## $City
## [1] "character"
## 
## $State
## [1] "character"
## 
## $Weather_Timestamp
## [1] "POSIXct" "POSIXt" 
## 
## $`Temperature(F)`
## [1] "numeric"
## 
## $`Humidity(%)`
## [1] "numeric"
## 
## $`Pressure(in)`
## [1] "numeric"
## 
## $`Visibility(mi)`
## [1] "numeric"
## 
## $Weather_Condition
## [1] "character"

sapply(Accidents, class) duyệt qua từng cột của bảng Accidents và trả về lớp kiểu dữ liệu (class) của mỗi cột

Chuỗi/ký tự (character): ID, Source, Street, City, State, Weather_Condition. Đây là các biến định danh/nhãn văn bản. Khi thao tác với dplyr/ggplot, nên để nguyên hoặc chuyển sang factor khi cần đếm/tính tỷ lệ. Số (numeric): Severity, Start_Lat, Start_Lng, Distance(mi), Temperature(F), Humidity(%), Pressure(in), Visibility(mi). Đây là các biến định lượng. Lưu ý Severity về mặt bản chất là thang thứ bậc (1–4). Ngày-giờ (POSIXct/POSIXt): Start_Time, End_Time, Weather_Timestamp. Kiểu này cho phép trích xuất giờ/thứ/tháng, sắp xếp theo thời gian, tính thời lượng, v.v.

1.1.7 Giới thiệu các biến và giải thích ý nghĩa từng biến

variable_meaning <- data.frame(
  Variable = c(
    "ID","Source","Severity","Start_Time","End_Time","Start_Lat","Start_Lng",
    "Distance(mi)","Street","City","State",
    "Weather_Timestamp",                      
    "Temperature(F)","Humidity(%)","Pressure(in)","Visibility(mi)","Weather_Condition"
  ),
  Meaning = c(
    "Mã định danh duy nhất cho từng vụ tai nạn",
    "Nguồn thu thập dữ liệu",
    "Mức độ nghiêm trọng (1 = nhẹ nhất, 4 = nghiêm trọng nhất)",
    "Thời điểm bắt đầu vụ tai nạn",
    "Thời điểm kết thúc vụ tai nạn",
    "Vĩ độ nơi bắt đầu vụ tai nạn",
    "Kinh độ nơi bắt đầu vụ tai nạn",
    "Chiều dài đoạn đường bị ảnh hưởng (dặm)",
    "Tên đường hoặc tuyến",
    "Thành phố nơi tai nạn ghi nhận",
    "Bang/khu vực",
    "Thời điểm thời tiết (timestamp quan trắc/thời điểm sự kiện)",
    "Nhiệt độ (°F) tại thời điểm tai nạn",
    "Độ ẩm (%) tại thời điểm tai nạn",
    "Áp suất khí quyển (inch Hg)",
    "Tầm nhìn (mile)",
    "Điều kiện thời tiết quan sát"
  ),
  stringsAsFactors = FALSE,
  check.names = FALSE
)

data_types <- data.frame(
  Variable = names(Accidents),
  DataType = vapply(Accidents, function(x) paste(class(x), collapse = "|"), character(1L)),
  stringsAsFactors = FALSE
)

library(dplyr)
variable_summary <- left_join(variable_meaning, data_types, by = "Variable")
knitr::kable(variable_summary, caption = "Tên biến, ý nghĩa và kiểu dữ liệu")
Tên biến, ý nghĩa và kiểu dữ liệu
Variable Meaning DataType
ID Mã định danh duy nhất cho từng vụ tai nạn character
Source Nguồn thu thập dữ liệu character
Severity Mức độ nghiêm trọng (1 = nhẹ nhất, 4 = nghiêm trọng nhất) numeric
Start_Time Thời điểm bắt đầu vụ tai nạn POSIXct|POSIXt
End_Time Thời điểm kết thúc vụ tai nạn POSIXct|POSIXt
Start_Lat Vĩ độ nơi bắt đầu vụ tai nạn numeric
Start_Lng Kinh độ nơi bắt đầu vụ tai nạn numeric
Distance(mi) Chiều dài đoạn đường bị ảnh hưởng (dặm) numeric
Street Tên đường hoặc tuyến character
City Thành phố nơi tai nạn ghi nhận character
State Bang/khu vực character
Weather_Timestamp Thời điểm thời tiết (timestamp quan trắc/thời điểm sự kiện) POSIXct|POSIXt
Temperature(F) Nhiệt độ (°F) tại thời điểm tai nạn numeric
Humidity(%) Độ ẩm (%) tại thời điểm tai nạn numeric
Pressure(in) Áp suất khí quyển (inch Hg) numeric
Visibility(mi) Tầm nhìn (mile) numeric
Weather_Condition Điều kiện thời tiết quan sát character

variable_meaning <- data.frame(): R khởi tạo một khung dữ liệu với hai cột là Variable và Meaning.

Variable = c() cung cấp vector ký tự chứa chính xác tên các cột đang có trong bảng dữ liệu gốc. Việc trùng khớp từng ký tự là quan trọng vì ở bước nối sau đó, phép nối sẽ dựa hoàn toàn vào khóa chữ đúng 100%.

Meaning = c() là vector chú giải tiếng Việt, thứ tự phần tử của nó phải tương ứng một-một với vector Variable; R khớp các phần tử theo vị trí để tạo từng dòng của khung dữ liệu.

stringsAsFactors = FALSE yêu cầu lưu các chuỗi dưới dạng kiểu character thay vì factor (hành vi mặc định của các phiên bản R cũ trước 4.0), giúp tránh phát sinh các mức (levels) không cần thiết và thuận tiện hơn cho việc ghép nối, in ấn.

check.names = FALSE yêu cầu R không tự “sửa tên” để hợp lệ cú pháp; nếu bỏ qua tham số này, R có thể tự đổi các tên chứa ký tự đặc biệt như “Humidity(%)” hay “Distance(mi)” thành biến thể hợp lệ (chẳng hạn thay dấu ngoặc)

Đoạn mã thứ hai xây dựng một bảng ghi nhận kiểu dữ liệu thực tế mà R đang hiểu cho từng cột trong Accidents. Cột Variable được điền bằng names(Accidents), phản ánh đúng thứ tự và chính tả tên cột hiện có.

Cột DataType được tạo bằng vapply(). Cơ chế ở đây là duyệt qua từng cột, lấy lớp đối tượng bằng class(x), rồi ghép các lớp lại thành một chuỗi duy nhất; điều này cần thiết vì các biến thời gian thường có đồng thời hai lớp POSIXct và POSIXt. Việc chọn vapply thay cho sapply giúp ràng buộc kiểu trả về là một chuỗi ký tự cho mỗi cột, nhờ vậy phát hiện lỗi sớm nếu gặp cột có cấu trúc bất thường.

left_join(A, B, by = “Variable”) thực hiện phép nối trái: giữ toàn bộ hàng của A (ở đây là variable_meaning) và ghép thêm cột từ B (data_types) khi khóa Variable trùng khớp.

knitr::kable(…) được sử dụng để hiển thị bảng dưới dạng trực quan, có chú thích (caption), giúp phần báo cáo trở nên chuyên nghiệp và dễ hiểu hơn.

Bảng trên trình bày 17 biến trong bộ dữ liệu cùng ý nghĩa và kiểu dữ liệu tương ứng.

  • ID. Đây là mã định danh duy nhất cho mỗi vụ tai nạn trong tập dữ liệu. Giá trị ở dạng chuỗi ký tự (character) do hệ thống nguồn sinh ra, dùng để tham chiếu chéo, phát hiện trùng lặp và đảm bảo rằng mỗi dòng dữ liệu biểu diễn một sự kiện riêng biệt.

  • Source. Biến này cho biết nguồn thu thập dữ liệu ban đầu (ví dụ các API giao thông hoặc hệ thống cảm biến). Dạng ký tự, không có đơn vị. Ý nghĩa chính là giúp đánh giá sai lệch theo nguồn, so sánh độ phủ và chất lượng dữ liệu giữa các nhà cung cấp.

  • Severity. Mức độ nghiêm trọng của vụ tai nạn theo thang thứ bậc từ 1 đến 4, trong đó 1 là nhẹ nhất và 4 là nghiêm trọng nhất. Biến được lưu dạng số (numeric) nhưng về bản chất là thang đo ordinal; khi mô hình hóa nên chuyển sang ordered factor hoặc phân nhóm theo ngưỡng để đảm bảo diễn giải đúng. Không có đơn vị đo, nhưng có phạm vi giá trị hợp lệ {1,2,3,4}.

  • Start_Time. Thời điểm bắt đầu của sự kiện tai nạn, được lưu theo định dạng ngày–giờ POSIXct (UTC hoặc theo múi giờ hệ thống nếu không được gắn rõ). Không có đơn vị; về kỹ thuật đây là số giây kể từ mốc epoch 1970-01-01 khi lưu trữ nội bộ. Biến này là nền tảng để tách năm, tháng, thứ, giờ và phân tích theo chu kỳ thời gian.

  • End_Time. Thời điểm kết thúc của sự kiện hoặc khoảng thời gian hệ thống ước tính còn ảnh hưởng. Cũng là POSIXct, không có đơn vị. Nên kiểm tra ràng buộc logic End_Time ≥ Start_Time để phát hiện lỗi nhập liệu; khoảng chênh End_Time − Start_Time có thể được dùng để ước lượng độ dài ảnh hưởng.

  • Start_Lat. Vĩ độ nơi sự kiện được ghi nhận, đơn vị là độ thập phân (decimal degrees), phạm vi hợp lệ từ −90 đến 90. Biến số này, cùng với kinh độ, cho phép định vị không gian, ghép bản đồ hoặc quy chiếu tới bang/thành phố khi cần.

  • Start_Lng. Kinh độ nơi sự kiện được ghi nhận, đơn vị là độ thập phân, phạm vi hợp lệ từ −180 đến 180. Giá trị âm tương ứng tọa độ ở Tây bán cầu (Hoa Kỳ phần lớn là giá trị âm).

  • Distance(mi). Chiều dài đoạn đường bị ảnh hưởng bởi sự cố, đơn vị là dặm (mile). Đây thường là khoảng cách mà hệ thống ước lượng liên quan đến sự kiện (ví dụ phong tỏa hay ùn tắc).

  • Street. Tên tuyến đường hoặc địa chỉ mô tả vị trí tai nạn. Dạng ký tự, không có đơn vị. Biến này hữu ích cho phân tích định tính hoặc ghép quy hoạch hạ tầng nhưng thường có nhiều mức độ khác nhau nên cần xử lý chuẩn hóa nếu dùng làm biến giải thích.

  • City. Thành phố nơi ghi nhận tai nạn, dạng ký tự. Không có đơn vị. Có thể dùng để tổng hợp theo địa bàn hoặc tạo bản đồ rủi ro theo đô thị; cần lưu ý các biến thể tên và lỗi chính tả nếu so sánh xuyên nguồn.

  • State. Mã bang hoặc khu vực hành chính của Hoa Kỳ (thường là mã hai ký tự như CA, TX, NY). Dạng ký tự, không có đơn vị. Đây là cấp không gian quan trọng cho phân tích tổng hợp theo vùng và so sánh chính sách.

  • Weather_Timestamp. Thời điểm quan trắc thời tiết gắn với sự kiện, cũng ở định dạng POSIXct. Không có đơn vị. Biến này cho phép đối chiếu xem dữ liệu thời tiết có đồng thời với tai nạn hay không; chênh lệch lớn giữa Weather_Timestamp và Start_Time có thể gây sai lệch khi suy luận mối quan hệ thời tiết–tai nạn.

  • Temperature(F). Nhiệt độ không khí tại thời điểm/địa điểm sự kiện, đơn vị là độ Fahrenheit (°F). Biến định lượng liên tục; để quy đổi sang độ C dùng công thức °C = (°F − 32) × 5/9. Cần kiểm tra ngoại lai (ví dụ < −50 °F hoặc > 130 °F) vì có thể do lỗi cảm biến.

  • Humidity(%). Độ ẩm tương đối của không khí, đơn vị phần trăm (%), phạm vi lý thuyết từ 0 đến 100. Biến định lượng; giá trị ngoài khoảng có thể là lỗi đo hoặc nhập liệu và cần được rà soát.

  • Pressure(in). Áp suất khí quyển, đơn vị inch thủy ngân (inch Hg, viết tắt inHg). Đây là đơn vị phổ biến ở Hoa Kỳ; nếu cần chuẩn SI có thể quy đổi 1 inHg ≈ 33,8639 hPa. Giá trị áp suất bất thường thường báo lỗi thiết bị.

  • Visibility(mi). Tầm nhìn xa ước lượng, đơn vị dặm (mile). Biến định lượng thể hiện điều kiện quan sát của người lái; tầm nhìn thấp thường liên quan đến sương mù, mưa lớn hoặc bão cát. Quy đổi sang km bằng cách nhân với 1,609.

  • Weather_Condition. Mô tả điều kiện thời tiết định tính tại thời điểm/địa điểm sự kiện, ví dụ “Fair”, “Rain”, “Snow”, “Fog”, “Thunderstorm”. Dạng ký tự không đơn vị; thường nên gộp nhóm các mức hiếm để phân tích tần suất hoặc mô hình hóa.

Bảng kết quả cho ta thấy có 3 nhóm biến là biến định tính, biến định lượng và biến thời gian.

Nhóm biến định lượng bao gồm các biến như Severity, Distance(mi), Temperature(F), Humidity(%), Pressure(in)Visibility(mi).

=> Đây là những biến mang giá trị số, cho phép đo lường, tính toán và sử dụng trong các phân tích thống kê hoặc mô hình hồi quy. Các biến này phản ánh mức độ nghiêm trọng, điều kiện môi trường và các yếu tố vật lý ảnh hưởng trực tiếp đến khả năng xảy ra tai nạn, từ đó giúp đánh giá mối quan hệ giữa thời tiết và rủi ro giao thông.

Nhóm biến định tính gồm các biến ID, Source, Street, City, StateWeather_Condition.

=> Đây là những biến dạng chuỗi ký tự (character), dùng để mô tả thông tin phân loại như vị trí địa lý, tên đường, nguồn dữ liệu hay tình trạng thời tiết. Các biến này có vai trò quan trọng trong việc phân nhóm, so sánh và xác định các khu vực hoặc điều kiện đặc trưng có tần suất tai nạn cao, phục vụ cho công tác quy hoạch và quản lý giao thông.

Nhóm biến thời gian gồm Start_Time, End_TimeWeather_Timestamp => Các biến dạng ngày–giờ (POSIXct). Chúng thể hiện thời điểm bắt đầu, kết thúc của mỗi vụ tai nạn và thời điểm ghi nhận điều kiện thời tiết tương ứng. Nhóm biến này giúp phân tích xu hướng, chu kỳ hoặc thời điểm dễ xảy ra tai nạn, hỗ trợ các cơ quan chức năng trong việc dự báo, cảnh báo và điều phối giao thông theo thời gian thực.

1.1.8 Thống kê mô tả sơ lược

summary(Accidents)
##       ID               Source             Severity    
##  Length:1762452     Length:1762452     Min.   :1.000  
##  Class :character   Class :character   1st Qu.:2.000  
##  Mode  :character   Mode  :character   Median :2.000  
##                                        Mean   :2.072  
##                                        3rd Qu.:2.000  
##                                        Max.   :4.000  
##                                                       
##    Start_Time                        End_Time                     
##  Min.   :2022-01-01 00:02:00.00   Min.   :2022-01-01 00:31:30.00  
##  1st Qu.:2022-04-01 06:33:48.75   1st Qu.:2022-04-01 09:29:51.75  
##  Median :2022-06-21 13:00:25.00   Median :2022-06-21 17:10:05.00  
##  Mean   :2022-06-27 17:29:30.31   Mean   :2022-06-27 23:10:00.88  
##  3rd Qu.:2022-09-23 08:06:33.25   3rd Qu.:2022-09-23 13:46:07.50  
##  Max.   :2022-12-31 23:59:03.00   Max.   :2023-03-31 23:59:00.00  
##                                                                   
##    Start_Lat       Start_Lng        Distance(mi)         Street         
##  Min.   :24.55   Min.   :-124.54   Min.   :  0.0000   Length:1762452    
##  1st Qu.:33.01   1st Qu.:-112.30   1st Qu.:  0.0570   Class :character  
##  Median :35.91   Median : -84.50   Median :  0.2600   Mode  :character  
##  Mean   :35.95   Mean   : -93.09   Mean   :  0.9264                     
##  3rd Qu.:39.99   3rd Qu.: -80.09   3rd Qu.:  0.9780                     
##  Max.   :49.00   Max.   : -68.28   Max.   :336.5700                     
##                                                                         
##      City              State           Weather_Timestamp               
##  Length:1762452     Length:1762452     Min.   :2022-01-01 00:15:00.00  
##  Class :character   Class :character   1st Qu.:2022-04-01 00:53:00.00  
##  Mode  :character   Mode  :character   Median :2022-06-21 06:53:00.00  
##                                        Mean   :2022-06-27 13:04:38.47  
##                                        3rd Qu.:2022-09-23 02:53:00.00  
##                                        Max.   :2022-12-31 23:56:00.00  
##                                        NA's   :29595                   
##  Temperature(F)    Humidity(%)      Pressure(in)   Visibility(mi)  
##  Min.   :-45.00   Min.   :  1.00   Min.   : 0.00   Min.   :  0.00  
##  1st Qu.: 51.00   1st Qu.: 45.00   1st Qu.:29.20   1st Qu.: 10.00  
##  Median : 67.00   Median : 63.00   Median :29.72   Median : 10.00  
##  Mean   : 63.17   Mean   : 62.04   Mean   :29.37   Mean   :  9.16  
##  3rd Qu.: 78.00   3rd Qu.: 81.00   3rd Qu.:29.96   3rd Qu.: 10.00  
##  Max.   :172.00   Max.   :100.00   Max.   :58.63   Max.   :100.00  
##  NA's   :38718    NA's   :41107    NA's   :33134   NA's   :41953   
##  Weather_Condition 
##  Length:1762452    
##  Class :character  
##  Mode  :character  
##                    
##                    
##                    
## 

summary(Accidents) tạo bảng tóm tắt cho từng biến: với biến số (numeric) trả về Min, 1st Qu. (Q1), Median, Mean, 3rd Qu. (Q3), Max và số NA (nếu có); với biến thời gian (POSIXct) trả về các mốc tứ phân vị theo định dạng ngày-giờ; với biến ký tự (character) chỉ hiển thị Length/Class/Mode (không đếm NA hay số mức khác nhau).

  • Nhận xét:

Severity có miền 1–4 và cả Q1, Median, Q3 đều bằng 2, trong khi trung bình 2,07—chứng tỏ phần lớn vụ việc thuộc mức 2 (lệch mạnh về mức trung bình-nhẹ).

Start_Time phủ đều cả năm 2022 (Median ~ 2022-06-21), còn End_Time có giá trị tối đa sang 2023-03-31, gợi ý một số sự cố kéo dài qua năm hoặc đuôi dữ liệu cập nhật muộn—điểm này đáng ghi chú khi giới hạn khung thời gian.

Miền tọa độ Start_Lat (24.55–49.00) và Start_Lng (−124.54 đến −68.28) khớp phạm vi địa lý lục địa Hoa Kỳ.

Distance(mi) có Median 0.26 nhưng Mean 0.93 và Max 336.57 → lệch phải rất mạnh với đuôi dài; nhiều sự kiện gần như không kéo dài không gian.

Nhóm thời tiết cho thấy đặc trưng kỹ thuật rõ rệt:Temperature(F) có Q1–Q3 khoảng 51–78°F (hợp lý theo mùa) nhưng Max 172°F là ngoại lai; Humidity(%) trong [1;100]; Pressure(in) tập trung quanh 29–30 inHg nhưng xuất hiện 0 và 58.63—cả hai đều phi thực tế, cần coi là giá trị bất thường; Visibility(mi) có Q1=Median=Q3 đều 10, Mean 9.16 → khối lớn giá trị bị “trần” ở 10 miles do giới hạn đo; Max 100 cũng là điểm cần xem lại. Các biến ký tự như Street, City, State, Weather_Condition chỉ hiện lớp character; muốn biết NA hay số mức khác nhau cần lệnh chuyên biệt (ví dụ colSums(is.na(.)), n_distinct(.)).

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

Trong bộ US Accidents, mỗi dòng được hiểu là một vụ tai nạn. Nếu tồn tại dòng trùng, số liệu tần suất theo bang/tháng, ước lượng tỷ lệ theo mức độ (Severity), hay tương quan với thời tiết sẽ bị đếm lố, dẫn tới kết luận thiên lệch. Kiểm tra trùng lặp là bước kiểm soát chất lượng dữ liệu bắt buộc trước khi mô tả hoặc mô hình hoá.

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

Hàm duplicated() sẽ kiểm tra các dòng trùng lặp trong toàn bộ bảng dữ liệu Accidents.

Kết quả trả về 0, nghĩa là không có hàng trùng hoàn toàn trong bảng—mỗi dòng là một vụ tai nạn duy nhất theo toàn bộ 17 biến. Đây là tín hiệu tốt cho độ tin cậy của phần mô tả.

1.1.10 Đếm số biến theo loại (numeric / character / thời gian)

table(
  numeric   = sapply(Accidents, is.numeric),
  character = sapply(Accidents, function(x) is.character(x) || is.factor(x)),
  time      = sapply(Accidents, function(x) inherits(x, "POSIXct"))
)
## , , time = FALSE
## 
##        character
## numeric FALSE TRUE
##   FALSE     0    6
##   TRUE      8    0
## 
## , , time = TRUE
## 
##        character
## numeric FALSE TRUE
##   FALSE     3    0
##   TRUE      0    0

sapply(Accidents, is.numeric) tạo một vector TRUE/FALSE cho biết mỗi cột có phải kiểu số không. Dòng thứ hai gộp cả character và factor vào nhóm “character”. Dòng thứ ba dùng inherits(x, “POSIXct”) để nhận diện biến thời gian (ngày-giờ). table(…) ghép 3 vector logic này thành bảng đếm tổ hợp (TRUE/FALSE cho từng nhóm), từ đó suy ra bao nhiêu cột thuộc mỗi loại.

Khối time = FALSE cho thấy có 6 cột character (numeric = FALSE, character = TRUE) và 8 cột numeric (numeric = TRUE, character = FALSE). Khối time = TRUE có 3 cột thời gian (numeric = FALSE, character = FALSE, time = TRUE).

Cộng lại đúng 17 cột của bộ dữ liệu:

8 numeric: Severity, Start_Lat, Start_Lng, Distance(mi), Temperature(F), Humidity(%), Pressure(in), Visibility(mi).

3 thời gian (POSIXct): Start_Time, End_Time, Weather_Timestamp.

6 character: ID, Source, Street, City, State, Weather_Condition.

1.1.11 Kiểm tra cột thời gian và múi giờ

Trước khi phân tích theo ngày–giờ, ta phải chắc chắn cột thời gian được parse đúng kiểu và múi giờ thống nhất. Nếu kiểu không phải thời gian, hoặc múi giờ lẫn lộn (UTC vs địa phương), các phép lọc/so sánh sẽ sai lệch vài giờ hoặc lệch ngày—đặc biệt nhạy cảm với DST (đổi giờ mùa hè).

class(Accidents$Start_Time); attr(Accidents$Start_Time, "tzone")
## [1] "POSIXct" "POSIXt"
## [1] "UTC"
class(Accidents$End_Time);   attr(Accidents$End_Time, "tzone")
## [1] "POSIXct" "POSIXt"
## [1] "UTC"

class(…) trả về lớp dữ liệu của biến. Với thời gian đúng chuẩn sẽ là “POSIXct” “POSIXt”. attr(…, “tzone”) đọc thuộc tính múi giờ gắn với biến thời gian (ví dụ “UTC”, “America/New_York”).

Kết quả cho thấy cả Start_Time và End_Time đều là POSIXct/POSIXt và có múi giờ “UTC”. Điều này chứng tỏ dữ liệu đã được parse đúng và nhất quán về múi giờ.

1.1.12 Kiểm tra phạm vi thời gian ghi nhận

Kiểm tra các mốc sớm–muộn nhất được ghi, xem có phủ đủ năm 2022 không, và phát hiện giá trị biên bất thường (ví dụ thời gian kết thúc vượt quá năm nghiên cứu).

range(Accidents$Start_Time, na.rm = TRUE)
## [1] "2022-01-01 00:02:00 UTC" "2022-12-31 23:59:03 UTC"
range(Accidents$End_Time,   na.rm = TRUE)
## [1] "2022-01-01 00:31:30 UTC" "2023-03-31 23:59:00 UTC"

range() trả về cặp (min, max) của vector thời gian. Tham số na.rm = TRUE bỏ qua giá trị thiếu nếu có. Vì hai cột là POSIXct, kết quả hiển thị mốc ngày–giờ UTC nhỏ nhất và lớn nhất.

Start_Time trải từ 2022-01-01 00:02:00 UTC đến 2022-12-31 23:59:03 UTC ⇒ dữ liệu khởi phát sự kiện phủ trọn năm 2022.

End_Time trải từ 2022-01-01 00:31:30 UTC đến 2023-03-31 23:59:00 UTC ⇒ có một số vụ kết thúc sau 2022, có thể do sự cố kéo dài/ghi nhận muộn.

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

1.2.1 Loại bỏ biến không cần thiết

Trong bộ dữ liệu, việc loại bỏ biến không cần thiết giúp phân tích tập trung đúng câu hỏi nghiên cứu và tránh hao hụt mẫu do NA. Cụ thể với bài này, chúng ta giới hạn phạm vi năm 2022 và dùng Start_Time làm mốc thời gian chính của sự kiện. Biến Weather_Timestamp chỉ ghi lại thời điểm quan trắc khí tượng gần đó để ghép các biến thời tiết (nhiệt độ, độ ẩm, áp suất, tầm nhìn). Nó không bổ sung thông tin cho các phân tích theo ngày/giờ/thứ vì những phân tích này đã dựa trực tiếp vào Start_Time, còn các biến thời tiết đã được ghép sẵn thành các cột định lượng riêng.

Accidents <- Accidents %>% select(-Weather_Timestamp)

Câu lệnh Accidents <- Accidents %>% select(-Weather_Timestamp) dùng dplyr để cập nhật lại bảng dữ liệu, loại bỏ hoàn toàn cột Weather_Timestamp và giữ nguyên các cột còn lại. Toán tử ống %>% chuyển Accidents vào hàm select(), còn dấu trừ - trước tên biến là cú pháp “bỏ cột”. Sau lệnh này, mọi thao tác tiếp theo sẽ làm việc với phiên bản Accidents không còn cột thời gian của trạm thời tiết.

Biến Weather_Timestamp thể hiện thời điểm ghi nhận điều kiện thời tiết gần thời gian xảy ra tai nạn. Tuy nhiên, biến này trùng lặp thông tin với Start_Time, vì thời gian đo thời tiết thường diễn ra gần hoặc trùng với thời điểm tai nạn. Do đó, việc giữ lại cả hai biến sẽ không làm tăng giá trị thông tin và nó thiếu trị thiếu (NA) đáng kể, trong khi các đặc trưng thời gian quan trọng như giờ, thứ trong tuần, hoặc thời điểm trong ngày đã được trích xuất trực tiếp từ Start_Time.

=> Vì vậy, biến Weather_Timestamp được loại bỏ khỏi tập dữ liệu để giúp mô hình gọn hơn và tránh trùng lặp về mặt thông tin thời gian.

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

Trước khi phân tích, việc kiểm tra dữ liệu thiếu (NA) là bước bắt buộc để bảo đảm chất lượng và độ tin cậy của thống kê. Nếu không biết cột nào thiếu nhiều, các bảng thống kê và biểu đồ sau đó có thể tự động bỏ bớt hàng (vì gặp NA) làm số mẫu hữu dụng bị giảm, thậm chí kết quả bị lệch nếu phần thiếu rơi vào những tình huống đặc biệt (ví dụ chỉ thiếu khi trời mưa to).

colSums(is.na(Accidents))
##                ID            Source          Severity        Start_Time 
##                 0                 0                 0                 0 
##          End_Time         Start_Lat         Start_Lng      Distance(mi) 
##                 0                 0                 0                 0 
##            Street              City             State    Temperature(F) 
##              7236                65                 0             38718 
##       Humidity(%)      Pressure(in)    Visibility(mi) Weather_Condition 
##             41107             33134             41953             38688

Câu lệnh colSums(is.na(Accidents)) thực hiện việc kiểm kê NA theo cột một cách gọn và hiệu quả. Cụ thể,is.na(Accidents) tạo ra một ma trận logic cùng kích thước với dữ liệu, trong đó mỗi ô mang giá trị TRUE nếu phần tử tương ứng là NA và FALSE nếu không. Hàm colSums sau đó cộng dồn theo cột, coi TRUE = 1 và FALSE = 0, nhờ đó trả về một véc-tơ cho biết tổng số NA của từng biến.

Kết quả kiểm tra giá trị khuyết cho thấy bộ dữ liệu có một số biến chứa nhiều giá trị bị thiếu (NA).

Biến City có một lượng nhỏ giá trị thiếu (65).

Tuy nhiên, các biến Street, Temperature(F), Humidity(%), Pressure(in), Visibility(mi), Weather_Condition có số lượng NA rất lớn lần lượt là 7236, 29595, 38718, 41107, 33134, 41953 và 38688 giá trị.

ID, Source, Severity, Start_Time, End_Time, Start_Lat, Start_Lng, State — không có giá trị thiếu. Điều này chứng tỏ dữ liệu nền tảng về “sự kiện xảy ra khi nào và ở đâu” được ghi nhận đầy đủ, tạo tiền đề tin cậy cho các phân tích theo trục thời gian–không gian

City chỉ thiếu 65 dòng, còn Street thiếu 7.236 dòng. Lý do thường gặp là các sự kiện xảy ra trên cao tốc, nút giao, đường liên bang hoặc các đoạn không có tên đường rõ ràng; hệ thống geocoding ưu tiên xác định tọa độ–bang và đôi khi không suy ra được tên phố. Ngoài ra, khi dữ liệu đến từ nhiều nguồn, quy ước đặt tên hoặc ký tự đặc biệt có thể khiến bước chuẩn hóa/parse thất bại, các ô này sẽ được gán NA thay vì giữ chuỗi lỗi.

Với nhóm thời tiết, các biến thiếu vớivới tổng số dòng: Temperature(F) 38.718 dòng, Humidity(%) 41.107 dòng, Pressure(in) 33.134 dòng, Visibility(mi) 41.953 dòng, Weather_Condition 38.688 dòng.

Nguyên nhân vì đặc thù của US Accidents là thời tiết được ghép (join) theo không–thời gian từ trạm quan trắc gần nhất:Ở vùng thưa trạm (ví dụ ngoại ô, nông thôn) hoặc khi thời điểm tai nạn rơi ra ngoài “cửa sổ” ghép chấp nhận (quá xa trạm, lệch quá nhiều phút), hệ thống sẽ không tìm được bản ghi phù hợp và để trống. Thứ hai, các trạm có thể gián đoạn đo do bảo trì, mất điện hay thời tiết cực đoan; những lúc mưa bão, băng tuyết hay giông sét, thiết bị dễ mất tín hiệu, khiến các cột Temperature(F), Humidity(%), Pressure(in) và Visibility(mi) hổng dữ liệu đúng lúc điều kiện khắc nghiệt—nghịch lý là khi hiện tượng thời tiết quan trọng nhất thì dữ liệu lại dễ vắng.

→ Điều này cho thấy bộ dữ liệu có thể thiếu dữ liệu trong một số ngày nhất định, có thể là do sự cố kết nối mạng, mất tính hiệu trong quá trình thu thập dữ liệu, từ hệ thống cảm biến.

1.2.3 Tính tỷ lệ giá trị bị thiếu NA

Ta tính phần trăm giá trị thiếu để so sánh mức độ thiếu giữa các biến có đơn vị và quy mô khác nhau trong bộ US Accidents.

Tỷ lệ phần trăm giúp ta quyết định chiến lược: biến thiếu 1–3% có thể xử lý nhẹ (dùng na.rm=TRUE), còn nếu một biến nào đó vượt ngưỡng (ví dụ >20%) thì cần cân nhắc điền/ước lượng hoặc loại khỏi mô hình.

colMeans(is.na(Accidents)) * 100
##                ID            Source          Severity        Start_Time 
##       0.000000000       0.000000000       0.000000000       0.000000000 
##          End_Time         Start_Lat         Start_Lng      Distance(mi) 
##       0.000000000       0.000000000       0.000000000       0.000000000 
##            Street              City             State    Temperature(F) 
##       0.410564373       0.003688044       0.000000000       2.196825786 
##       Humidity(%)      Pressure(in)    Visibility(mi) Weather_Condition 
##       2.332375577       1.879994462       2.380376884       2.195123612

is.na(Accidents) quét toàn bộ bảng và trả về một ma trận đúng/sai có cùng kích thước: ô nào trống sẽ là TRUE, ô có dữ liệu là FALSE. Hàm colMeans(…) lấy trung bình theo cột của ma trận logic đó; trong R, TRUE được coi là 1 và FALSE là 0, nên “trung bình” chính là tỷ lệ ô trống trên tổng số dòng của từng biến. Cuối cùng, nhân * 100 chuyển tỷ lệ sang phần trăm để dễ đọc trong báo cáo.

Kết quả từ hàm colMeans(is.na(US)) * 100 cho thấy phần lớn các biến trong bộ dữ liệu US Accidents đều có tỷ lệ thiếu rất thấp, thể hiện chất lượng dữ liệu khá tốt và đáng tin cậy.

Các biến ID, Source, Start_Time, End_Time, Start_Lat, Start_Lng, State, Severity không có giá trị bị thiếu (0%)

→ Thông tin cốt lõi về “khi nào – ở đâu – mức độ nghiêm trọng” được ghi nhận đầy đủ, là nền tảng tin cậy để phân tích theo thời gian và không gian mà không bị rơi mẫu do NA, có thể sử dụng trực tiếp trong phân tích mà không cần xử lý thiếu hụt.

Một số biến có tỷ lệ thiếu dưới 0.2%, như Street (0,41%)City (0.003%), mức này là rất nhỏ, có thể bỏ qua hoặc giữ nguyên vì không ảnh hưởng đáng kể đến kết quả thống kê.

Nhóm thời tiết là nơi thiếu dữ liệu nhiều hơn nhưng vẫn ở mức thấp theo tỷ lệ: Temperature(F) 2,20% (≈ 38.718), Humidity(%) 2,33% (≈ 41.107), Pressure(in) 1,88% (≈ 33.134), Visibility(mi) 2,38% (≈ 41.953), Weather_Condition 2,20% (≈ 38.688)

Nhìn chung, tỷ lệ thiếu của các biến này đều dưới 3%, do đó có thể xử lý bằng phương pháp nội suy đơn giản là thay thế bằng giá trị trung vị hoặc trung bình mà không làm sai lệch phân phối của dữ liệu.

1.2.3.1 Vẽ biểu đồ thể hiện tỷ lệ thiếu NA

Vẽ biểu đồ tỷ lệ thiếu giúp nhìn nhanh biến nào thiếu nhiều/ít để ra quyết định xử lý (giữ, điền, hay loại bỏ). So với nhìn số tuyệt đối, phần trăm cho phép so sánh công bằng giữa các biến và dễ thấy nhóm thời tiết thường thiếu hơn nhóm thời gian–không gian.

library(scales)
missing_percentage <- colSums(is.na(Accidents)) / nrow(Accidents) * 100
missing_df <- data.frame(
  Variable = names(missing_percentage),
  MissingPercent = missing_percentage)
missing_df <- missing_df %>%
  arrange(desc(MissingPercent))

ggplot(missing_df, aes(reorder(Variable, MissingPercent), MissingPercent)) +
  geom_col(fill = "steelblue") +                                 
  geom_text(aes(label = sprintf("%.2f%%", MissingPercent)),      
            hjust = -0.1, size = 3) +
  geom_hline(yintercept = 0, linetype = "dashed") +              
  coord_flip() +                                                 
  scale_y_continuous(labels = label_percent(scale = 1),          
                     expand = expansion(mult = c(0, .05))) +
  labs(title = "Tỷ lệ giá trị bị thiếu theo biến",
       x = "Biến", y = "Tỷ lệ thiếu (%)") +
  theme_minimal(base_size = 13)

Dòng library(scales) nạp gói scales để định dạng trục thành phần trăm. Tiếp đó, missing_percentage <- colSums(is.na(Accidents)) / nrow(Accidents) * 100 tính phần trăm thiếu của từng biến: is.na(Accidents) tạo ma trận TRUE/FALSE đánh dấu ô trống, colSums cộng theo cột, chia cho số dòng nrow(Accidents) để ra tỷ lệ, rồi nhân 100 để đổi sang %.

Bảng missing_df ghép tên biến với phần trăm thiếu, arrange(desc(…)) sắp giảm dần để khi vẽ, các biến thiếu nhiều (thường là Visibility(mi), Humidity(%), Temperature(F)…) lên đầu, dễ nhìn ra “điểm nóng” thiếu dữ liệu.

Dòng ggplot(missing_df, aes(reorder(Variable, MissingPercent), MissingPercent)) + mở khung đồ thị cho toàn bộ biểu diễn: dữ liệu dùng là missing_df (gồm tên biến và phần trăm thiếu),aes(…) định nghĩa ánh xạ biến → trục/thuộc tính hình học, trong đó trục hoành nhận biến Variable đã được sắp xếp lại bằng reorder(Variable, MissingPercent) để các tên cột xuất hiện theo mức thiếu có ý nghĩa thay vì thứ tự ngẫu nhiên, còn trục tung là giá trị phần trăm thiếu MissingPercent (đã nhân 100). Dấu “+” ở cuối báo rằng các lớp (layer) sẽ được bổ sung tiếp theo.

geom_col để vẽ các cột với chiều cao đúng bằng phần trăm thiếu—đây là phần hiển thị dữ liệu cốt lõi

geom_text in nhãn “xx.xx%” ngay trên mỗi cột giúp đọc con số chính xác chứ không chỉ ước lượng bằng mắt

geom_hline kẻ đường tham chiếu tại 0% để người xem có mốc so sánh khi nhìn độ cao cột

coord_flip lật trục để tên biến nằm dọc, rất hữu ích vì nhiều tên cột dài như Weather_Condition hay Temperature(F)

scale_y_continuous(labels = label_percent(scale = 1), …) định dạng trục tung thành phần trăm đúng nghĩa

labs(title = “Tỷ lệ giá trị bị thiếu theo biến”, x = “Biến”, y = “Tỷ lệ thiếu (%)”) đặt tiêu đề và nhãn trục, theme_minimal(base_size = 13) cho giao diện sạch, dễ đọc.

Biểu đồ cho thấy dữ liệu ID, Source, Severity, Start/End_Time, tọa độ, State, Distance, City hầu như không thiếu → có thể phân tích thời gian–không gian trực tiếp. Thiếu tập trung ở nhóm thời tiết khoảng 1.7–2.4%: cao nhất là Visibility(mi) (2.38%), rồi Humidity(%), Temperature(F), Weather_Condition (~2.2%), Pressure(in) (1.88%).

1.2.4 Xử lý biến NA

1.2.4.1 Vẽ biểu đồ phân phối cho từng biến định lượng NA

library(ggplot2)
library(patchwork)  

num_cols <- names(Accidents)[sapply(Accidents, is.numeric)]
plots <- lapply(num_cols, function(col) {
  ggplot(Accidents, aes(x = .data[[col]])) +
    geom_histogram(
      aes(y = after_stat(density)),
      bins = 30, fill = "skyblue", color = "white", alpha = 0.7,
      na.rm = TRUE
    ) +
    geom_density(color = "red", linewidth = 0.9, na.rm = TRUE) +
    labs(title = col, x = NULL, y = NULL) +
    theme_minimal()
})

wrap_plots(plots)

Nạp gói ggplot2 (vẽ đồ thị) và patchwork (xếp nhiều plot thành một bố cục chung);

sapply(Accidents, is.numeric) duyệt từng cột và trả về vector TRUE/FALSE cho biết cột đó có kiểu numeric không. names(Accidents)[ … ] dùng vector TRUE/FALSE này để lọc tên cột số.

lapply(num_cols, function(col) { … }) lặp qua mỗi tên cột số và tạo một ggplot cho cột đó; kết quả là danh sách plots chứa nhiều biểu đồ. ggplot(Accidents, aes(x = .data[[col]])) khởi tạo plot: data là toàn bộ bảng Accidents. aes(x = .data[[col]]) đặt trục X là cột hiện tại của vòng lặp. Dùng **.data[[col]**] để tham chiếu tên cột bằng chuỗi bên trong hàm.

geom_histogram(aes(y = after_stat(density)), …) vẽ histogram chuẩn hoá theo mật độ: after_stat(density) bảo ggplot dùng mật độ (area = 1) thay vì đếm tần suất, nhờ đó các biến có thang đo khác nhau vẫn so sánh hình dạng được. bins = 30 chia 30 “rổ” (có thể đổi tùy biến; ví dụ Distance(mi) lệch phải nhiều có thể tăng bins). fill = “skyblue”, color = “white”, alpha = 0.7 đặt màu và độ trong cho cột; na.rm = TRUE bỏ qua NA để không cảnh báo/lỗi.

geom_density(color = “red”, linewidth = 0.9, na.rm = TRUE) chồng đường mật độ kernel (KDE) màu đỏ lên histogram để nhìn xu hướng mượt. labs(title = col, x = NULL, y = NULL) đặt tiêu đề mỗi ô là tên biến; bỏ nhãn trục cho gọn (vì bạn đang xem nhiều ô một lúc). theme_minimal() dùng chủ đề tối giản giúp nền sáng, ít đường kẻ — tập trung vào phân phối.

wrap_plots(plots): Hàm của patchwork ghép tất cả các plot trong danh sách plots thành một bố cục lưới.

Sau khi vẽ biểu đồ histogram kết hợp đường mật độ cho các biến định lượng trong bộ dữ liệu, ta nhận thấy hình dạng phân phối của các biến có sự khác biệt rõ rệt.

Start_Lat: đa đỉnh rõ rệt quanh các vĩ độ ~30–40°B; điều này phản ánh cụm dân cư/đường xá (Sun Belt, Midwest) chứ không phải nhiễu. Dùng để mô tả theo bang/vùng hoặc làm bản đồ là hợp lý.

Start_Lng: cũng đa đỉnh, với ba cụm tương ứng bờ Đông (~−75 đến −85), Midwest (~−95) và bờ Tây (~−120 đến −115). Mẫu hình này xác nhận phạm vi địa lý của dữ liệu và gợi ý phân tích theo khu vực.

Distance(mi): lệch phải rất mạnh và có nhiều giá trị 0 (zero-inflation).

Temperature(F)Pressure có dạng phân phối gần chuẩn, tương đối đối xứng quanh giá trị trung tâm. Điều này cho thấy dữ liệu của hai biến này ổn định, không xuất hiện giá trị ngoại lai lớn.

=> Vì vậy, việc sử dụng giá trị trung bình (Mean) để thay thế các giá trị bị thiếu là phù hợp, giúp duy trì đặc trưng của phân phối ban đầu.

Các biến Humidity(%)Visibility(mi) đều có phân phối lệch trái, phải rõ rệt và xuất hiện nhiều giá trị ngoại lai.

=> Vì vậy, sử dụng **trung bình có thể khiến dữ liệu bị méo do ảnh hưởng của các điểm cực trị. Do đó, nhóm lựa chọn sử dụng giá trị trung vị (Median) để điền vào các ô bị thiếu, vì trung vị phản ánh vị trí trung tâm thực tế của dữ liệu và ít bị tác động bởi ngoại lai.

Riêng biến Severity là biến định tính dạng rời rạc (mức độ nghiêm trọng từ 1 đến 4). Do không mang tính liên tục, biến này được xử lý bằng giá trị mode (giá trị xuất hiện nhiều nhất) để đảm bảo tính đại diện cho nhóm dữ liệu phổ biến nhất.

Nhìn chung, việc lựa chọn Mean, Median hay Mode để thay thế giá trị bị thiếu dựa trên đặc điểm phân phối của từng biến, nhằm đảm bảo dữ liệu sau xử lý vẫn giữ được cấu trúc và xu hướng gốc, hạn chế tối đa sai lệch trong quá trình phân tích tiếp theo.

1.2.4.2 Kiểm tra độ lệch phân phối các biến NA

Định lượng hình dạng phân phối của các biến quan trọng để quyết định cách xử lý NA và chọn thống kê phù hợp. Hai chỉ số chuẩn là skewness (độ lệch trái/phải) và kurtosis (độ nặng đuôi). Nếu phân phối gần chuẩn (skew≈0, kurtosis≈0) thì có thể điền mean; nếu lệch mạnh/đuôi nặng thì nên ưu tiên median, biến đổi log, hoặc điền theo nhóm.

library(e1071) 
vars <- c("Temperature(F)", "Humidity(%)", "Pressure(in)", "Visibility(mi)", "Distance(mi)")

skew_tab <- sapply(Accidents[vars], function(x) e1071::skewness(x, na.rm = TRUE))
kurt_tab <- sapply(Accidents[vars], function(x) e1071::kurtosis(x, na.rm = TRUE))

data.frame(variable = vars, skewness = skew_tab, kurtosis = kurt_tab)
##                      variable   skewness    kurtosis
## Temperature(F) Temperature(F) -0.7144853   0.2535267
## Humidity(%)       Humidity(%) -0.2617628  -0.8195426
## Pressure(in)     Pressure(in) -3.1474550  18.4552088
## Visibility(mi) Visibility(mi) -0.6891490  45.1934911
## Distance(mi)     Distance(mi) 12.9725146 696.3156995

vars liệt kê các biến cần đo. sapply(…, skewness)sapply(…, kurtosis) tính skewness và kurtosis (excess) cho từng cột, bỏ qua NA. (Trong e1071, kurtosis=0 là phân phối chuẩn; >0: đuôi nặng/leptokurtic, <0: đuôi nhẹ/platykurtic.). Ghép kết quả vào một data.frame để đọc thuận tiện.

Temperature có skewness ≈ −0.71 và kurtosis ≈ 0.25: phân phối hơi lệch trái nhưng vẫn gần chuẩn, phần lớn quan sát tập trung quanh trung tâm. => Điền mean giữ nguyên kỳ vọng của biến và ít làm biến dạng phân phối tổng thể.

Humidity Skewness ≈ −0.26, kurtosis ≈ −0.82: gần đối xứng nhưng bị chặn trong [0;100], dễ dính cụm ở mép 100%. => Điền Median phản ánh vị trí trung tâm thực hơn và an toàn trước các giá trị biên.

Pressure tập trung rất hẹp quanh 29–30 inHg nhưng có một ít ngoại lai kỹ thuật (ví dụ 0 hoặc ~58.6 inHg), nên skewness âm lớn và kurtosis cao. Về mặt vật lý, áp suất khí quyển hiếm khi ra khỏi dải ~28.5–30.5 inHg; vì thế các điểm cực trị nhiều khả năng do lỗi cảm biến/ghép dữ liệu. Do phân phối có đuôi nặng => điền NA bằng trung vị (median) là hợp lý hơn trung bình (ít bị kéo bởi ngoại lai).

Visibility bị trần đo ở 10 miles, nên rất nhiều bản ghi dồn đúng giá trị 10; phần còn lại tập trung ở 0–3 miles (sương mù, mưa…), khiến skew âm và kurtosis rất cao. Vì “10” chỉ là giới hạn cảm biến, không phải tầm nhìn vô hạn, nếu dùng mean sẽ bị kéo cao giả tạo; nên mô tả bằng median

Theo như biểu đồ của hàm phân phối và nhận xét trên. Thì chúng ta đi xử lý các biến giá trị thiếu theo mean, median và mode.

1.2.4.3 Điền giá trị mean cho Temperature

Biến Temperature(F) được lựa chọn điền khuyết bằng giá trị trung bình (mean): bảo toàn kỳ vọng của biến, giữ nguyên cỡ mẫu cho các thống kê mô tả và trực quan hóa theo thời gian–không gian.

Accidents$`Temperature(F)`[is.na(Accidents$`Temperature(F)`)] <-
  mean(Accidents$`Temperature(F)`, na.rm = TRUE)

Phần trong ngoặc vuông [is.na(…)] xác định đúng những ô đang NA của từng biến. Dấu <- gán lại các ô NA này bằng trung bình của cả cột, tính với na.rm = TRUE để bỏ qua NA khi tính. Tên cột có ký tự đặc biệt ((), mi, in) nên được đặt trong backticks ... để tránh lỗi cú pháp.

1.2.4.4 Điền giá trị median cho các biến định lượng khác

Điền khuyết bằng trung vị cho mọi biến định lượng có NA, ngoại trừ Temperature(F)—biến đã được chọn điền trung bình. Việc dùng median cho các biến số còn lại là hợp lý với bộ dữ liệu này vì nhiều phân phối lệch/đuôi nặng hoặc bị trần (như Visibility(mi), Distance(mi), và cả Pressure(in) khi có ngoại lai kỹ thuật). Trung vị bền vững với ngoại lai và không kéo lệch trung tâm.

num_vars <- names(Accidents)[sapply(Accidents, is.numeric) & !(names(Accidents) %in% c("Temperature(F)"))]

for (v in num_vars) {
  Accidents[[v]][is.na(Accidents[[v]])] <- median(Accidents[[v]], na.rm = TRUE)
}

names(Accidents) lấy danh sách tên cột. sapply(Accidents, is.numeric) tạo vectơ TRUE/FALSE cho biết cột nào là numeric.!(names(Accidents) %in% c(“Temperature(F)”)) loại riêng Temperature(F) khỏi danh sách xử lý. Kết hợp hai điều kiện bằng & để nhận được num_vars: các cột số cần điền trung vị. Vòng for lặp qua từng tên cột v. Cú pháp Accidents[[v]] truy cập cột theo tên. is.na(Accidents[[v]]) xác định các ô NA; phần chỉ số […] dùng để gán lại chính các ô này. median(Accidents[[v]], na.rm = TRUE) tính trung vị của cột (bỏ qua NA) và gán vào các ô thiếu.

1.2.4.5 Điền giá trị mode cho biến định tính

các biến định tính dạng chuỗi (như Street, City, Weather_Condition, …) có tỉ lệ thiếu rất nhỏ. Với biến danh mục, chiến lược đơn giản và nhất quán là điền bằng giá trị xuất hiện nhiều nhất (mode): vừa giữ nguyên cỡ mẫu, vừa không giả định khoảng cách giữa các mức như khi dùng số.

cat_vars <- names(Accidents)[sapply(Accidents, is.character)]

for (v in cat_vars) {
  mode_value <- names(sort(table(Accidents[[v]]), decreasing = TRUE))[1]
  Accidents[[v]][is.na(Accidents[[v]])] <- mode_value
}

sapply(Accidents, is.character) tạo vector TRUE/FALSE cho biết cột nào là chuỗi; lấy tên cột đó vào cat_vars. Vòng for duyệt từng biến v. table(Accidents[[v]]) đếm tần suất các mức; sort(…, decreasing=TRUE)[1] chọn mức xuất hiện nhiều nhất (mode). Dòng gán cuối thay thế các ô NA của biến v bằng mode_value

1.2.4.6 Kiểm tra lại xem còn giá trị NA

colSums(is.na(Accidents))
##                ID            Source          Severity        Start_Time 
##                 0                 0                 0                 0 
##          End_Time         Start_Lat         Start_Lng      Distance(mi) 
##                 0                 0                 0                 0 
##            Street              City             State    Temperature(F) 
##                 0                 0                 0                 0 
##       Humidity(%)      Pressure(in)    Visibility(mi) Weather_Condition 
##                 0                 0                 0                 0

Sau khi xử lý giá trị thiếu NA của các biến thì kết quả kiểm tra lại là 0.

1.2.5 Đinh dạng ngày giờ

Định dạng ngày giờ bắt đầu và kết thúc xảy ra tai nạn

Để mọi thao tác theo thời gian (lọc đúng năm 2022, trích giờ/thứ/tháng, tính thời lượng, sắp xếp mốc thời gian, ghép thời tiết) chính xác và nhất quán múi giờ, cột Start_Time và End_Time phải ở kiểu ngày–giờ chứ không phải chuỗi. Việc ép về POSIXct cũng tránh lỗi sắp xếp theo chữ, lệch ngày do múi giờ

Accidents <- Accidents %>%
  mutate(
    Start_Time = as.POSIXct(Start_Time, format = "%Y-%m-%d %H:%M:%S", tz = "UTC"),
    End_Time   = as.POSIXct(End_Time,   format = "%Y-%m-%d %H:%M:%S", tz = "UTC")
  )

%>% là toán tử pipe của dplyr để truyền Accidents qua các bước. mutate(…) ghi đè cột Start_Time và End_Time. as.POSIXct(…) chuyển chuỗi sang kiểu POSIXct (thời gian Unix). format = “%Y-%m-%d %H:%M:%S” chỉ rõ khuôn dạng của chuỗi đầu vào (năm-tháng-ngày giờ:phút:giây); nếu khuôn dạng khác sẽ sinh NA. tz = “UTC” thiết lập múi giờ cho cột, đảm bảo mọi so sánh/biến đổi dùng chuẩn UTC (tránh lệch do DST/địa phương).

1.2.6 Phân loại những cột cần thiết

1.2.6.1 Phân loại ngày tháng năm xảy ra tai nạn

Nhiều phân tích chỉ cần mức ngày (đếm số vụ/ngày, vẽ xu hướng theo ngày, gộp theo tuần/tháng) chứ không cần giờ–phút–giây. Tạo thêm một biến Date giúp nhóm – tổng hợp nhanh, tránh phải xử lý trên đối tượng ngày-giờ phức tạp và giảm rủi ro sai lệch múi giờ khi so sánh.

Accidents <- Accidents %>% mutate(Date = as.Date(Start_Time))

%>% truyền bảng Accidents qua mutate. mutate(Date = …) tạo cột mới tên Date. as.Date(Start_Time) trích phần ngày từ Start_Time (đã ở POSIXct), bỏ phần giờ–phút–giây và trả về kiểu Date (YYYY-MM-DD).

1.2.6.2 Thêm cột giờ khi bắt đầu xảy ra vụ tai nạn

Mục tiêu là rút trích giờ trong ngày để phân tích nhịp tai nạn theo giờ (giờ cao điểm sáng/chiều, ban đêm…).

Accidents <- Accidents %>% mutate(Hour = as.numeric(format(Start_Time, "%H")))

format(Start_Time, “%H”) trích giờ (00–23) từ cột Start_Time (POSIXct). as.numeric(…) chuyển chuỗi “00”, “01”, … thành số 0–23 để dễ nhóm và vẽ. mutate(Hour = …) thêm cột mới Hour vào data.

1.2.6.3 Thêm cột ngày trong tuần xảy ra vụ tai nạn

Thêm ngày trong tuần của vụ tai nạn để phân tích nhịp tai nạn theo lịch làm việc/cuối tuần (so sánh weekday vs weekend, xem ngày nào cao nhất).

Accidents <- Accidents %>% mutate(Day = weekdays(Date))

weekdays(Date) lấy tên ngày trong tuần từ cột Date (kiểu Date). mutate(Day = …) tạo cột mới Day.

1.2.6.4 **Thêm cột cột nhiệt độ mới theo độ C

Chuẩn hoá về hệ SI để báo cáo/so sánh dễ hiểu (đa số độc giả quen °C).

Accidents <- Accidents %>%
  mutate(temp_c = (`Temperature(F)` - 32) * 5/9)

Công thức đổi: °C = (°F - 32) * 5/9;

mutate(temp_c = …) thêm cột temp_c, giữ nguyên cột °F để đối chiếu.

1.2.6.5 Phân loại thời điểm xảy ra tai nạn trong ngày

Rút 24 mức giờ rời rạc (0–23) thành vài khoảng thời gian có ý nghĩa để mô tả/so sánh dễ đọc (đêm, sáng, chiều, tối). Phân nhóm như vậy giúp tính tần suất, tỷ lệ mức độ nghiêm trọng theo “ca” trong ngày và trực quan hoá đơn giản hơn so với dùng 24 cột giờ.

Accidents <- Accidents %>%
  mutate(time_period = cut(Hour,
                           breaks = c(0, 6, 12, 18, 24),
                           labels = c("Đêm", "Sáng", "Chiều","Tối"),
                           include.lowest = TRUE)) %>%
  mutate(time_period = factor(time_period,
                              levels = c("Đêm","Sáng","Chiều","Tối"),
                              ordered = TRUE))

Hour là biến số nguyên 0–23 đã trích từ Start_Time. cut() chia miền giờ thành 4 khoảng liên tục: [0,6], (6,12], (12,18], (18,24];

tham số breaks xác định các mốc biên; labels gán nhãn tiếng Việt cho từng khoảng;

include.lowest=TRUE đảm bảo giờ 0 rơi vào nhóm “Đêm”.

Kết quả của cut() là một factor; bước mutate(… = factor(…, levels=…, ordered=TRUE)) cố định thứ tự hiển thị theo chuỗi tự nhiên “Đêm → Sáng → Chiều → Tối” (rất hữu ích khi vẽ biểu đồ/trình bày bảng

1.2.6.6 Phân loại điều kiện thời tiết lúc xảy ra tai nạn

Weather_Condition trong dữ liệu có rất nhiều mức chi tiết (ví dụ “Light Rain”, “Heavy Thunderstorm”, “Partly Cloudy”, “Haze”, …). Nếu giữ nguyên, bảng chéo/biểu đồ sẽ rối và khó diễn giải. Gộp chúng thành ít nhóm khí tượng có ý nghĩa (Mưa, Tuyết, Sương mù, Nhiều mây, Trời quang, Khác) giúp mô tả xu hướng mức độ nghiêm trọng theo điều kiện thời tiết rõ ràng hơn và ổn định thống kê hơn.

library(dplyr)
library(stringr)
Accidents <- Accidents %>% mutate(
weather_simple = case_when(
str_detect(Weather_Condition, regex("Rain|Thunderstorm", ignore_case=TRUE)) ~ "Mưa",
str_detect(Weather_Condition, regex("Snow|Sleet|Ice", ignore_case=TRUE)) ~ "Tuyết",
str_detect(Weather_Condition, regex("Fog|Mist|Haze", ignore_case=TRUE)) ~ "Sương mù",
str_detect(Weather_Condition, regex("Cloud|Overcast", ignore_case=TRUE)) ~ "Nhiều mây",
str_detect(Weather_Condition, regex("Clear|Fair", ignore_case=TRUE)) ~ "Trời quang",
TRUE ~ "Khác"))

str_detect(x, regex(pattern, ignore_case=TRUE)) kiểm tra chuỗi có chứa bất kỳ từ khóa nào trong mẫu regex (không phân biệt hoa/thường). Ví dụ, “Light Rain” hay “HEAVY RAIN” đều khớp “Rain”.

case_when(…) gán nhãn nhóm theo điều kiện đầu tiên thỏa mãn. Dòng cuối TRUE ~ “Khác” là mặc định cho các trường hợp không khớp (tránh NA).

Các mẫu: “Rain|Thunderstorm”(mưa/giông),“Snow|Sleet|Ice”(tuyết/băng),“Fog|Mist|Haze”(sương mù/khói mờ),“Cloud|Overcast”(nhiều mây),“Clear|Fair”(trời quang).

Kết quả là cột mới weather_simple (kiểu character)

1.3 Các thống kê cơ bản về bộ dữ liệu

1.3.1 Thống kê mô tả

1.3.1.1 Thống kê mô tả biến định tính

1.3.1.1.1 Xác định biến định tính
cat_vars <- names(Accidents)[sapply(Accidents, function(x) is.character(x) | is.factor(x))]
cat_vars
## [1] "ID"                "Source"            "Street"           
## [4] "City"              "State"             "Weather_Condition"
## [7] "Day"               "time_period"       "weather_simple"

sapply(Accidents, …) duyệt từng cột của Accidents và áp điều kiện.

is.character(x) | is.factor(x) trả về TRUE nếu cột là chuỗi hoặc factor → đây là hai kiểu dữ liệu tiêu biểu của biến định tính. Kết quả là một vector logic; đặt trong chỉ số của names(Accidents)[ … ] để lấy tên các cột thoả điều kiện.

In cat_vars cho ta danh sách biến định tính hiện có.

1.3.1.1.2 Thống kê giá trị xuất hiện nhiều nhất của mỗi dữ liệu biến định tính
freq_all <- Accidents %>%
  select(where(~ is.character(.x) || is.factor(.x)), -ID, -Source) %>%
  pivot_longer(everything(), names_to = "Biến", values_to = "Giá trị") %>%
  count(`Biến`, `Giá trị`, sort = TRUE) %>%
  group_by(`Biến`) %>%
  mutate(`Tỷ lệ (%)` = round(100 * n / sum(n), 2)) %>%
  ungroup()
mode_tbl <- freq_all %>%
  group_by(`Biến`) %>%
  slice_max(n, n = 1, with_ties = FALSE) %>%
  ungroup() %>%
  select(`Biến`, `Giá trị`, n) %>%
  rename(`Giá trị phổ biến` = `Giá trị`,
         `Số lần` = n)
mode_tbl
## # A tibble: 7 × 3
##   Biến              `Giá trị phổ biến` `Số lần`
##   <chr>             <chr>                 <int>
## 1 City              Miami                 64609
## 2 Day               Thứ Sáu              307733
## 3 State             CA                   375913
## 4 Street            I-95 S                23999
## 5 Weather_Condition Fair                 904354
## 6 time_period       Chiều                758195
## 7 weather_simple    Trời quang           917040

select(where(~ is.character(.x) || is.factor(.x)), -ID, -Source) chọn các biến định tính (kiểu character/factor) và loại hai cột không dùng cho mô tả (ID, Source).

pivot_longer(…, names_to = “Biến”, values_to = “Giá trị”) “làm gọn” dữ liệu theo chuẩn tidy: mọi biến định tính được dồn vào một cột tên Biến, còn các giá trị cụ thể nằm ở cột Giá trị.

count(,Giá trị, sort = TRUE) đếm tần suất từng giá trị trong từng biến.

group_by() %>% mutate(Tỷ lệ (%)` = 100 * n / sum(n)) tính tỷ lệ phần trăm trong phạm vi mỗi biến.

mode_tbl <- freq_all %>% group_by(`) %>% slice_max(n, n = 1, with_ties = FALSE) lấy giá trị xuất hiện nhiều nhất (mode) của từng biến; sau đó rename đổi tiêu đề cột sang tiếng Việt.

City: Miami là thành phố xuất hiện nhiều nhất (64,544 bản ghi). Điều này phản ánh mức độ bao phủ/cấu hình cảm biến–nguồn dữ liệu ở khu vực Miami rất dày, không nhất thiết là nơi “nguy hiểm nhất”.

Day: Thứ Sáu có nhiều vụ nhất (~307 nghìn), phù hợp với trực giác về lưu lượng cao cuối tuần.

State: CA (California) dẫn đầu (375,913 bản ghi), cho thấy dữ liệu thiên về các bang đông dân/được ghi nhận tốt.

Street: I-95 S là tuyến đường xuất hiện nhiều nhất (16,763), gợi ý các điểm nóng trên tuyến liên bang có lưu lượng lớn.

Weather_Condition: Fair (trời quang) chiếm phần lớn (865,666), và ở mức rút gọn weather_simple thì Trời quang cũng áp đảo (878,352). Điều này nhấn mạnh: đa số tai nạn xảy ra trong điều kiện thời tiết bình thường (yếu tố không phải chỉ thời tiết xấu).

time_period: Chiều là khung thời gian phổ biến nhất (758,195)—khớp với giờ tan tầm.

1.3.1.1.3 Thống kê tần suất, tỷ lệ % và trực quan hoá của Top 10 thành phố (City) xảy ra nhiều tai nạn nhất
top10_city <- Accidents %>%
  count(City, name = "n") %>%                        
  mutate(ty_le = round(n / sum(n) * 100, 2)) %>%    
  arrange(desc(n)) %>%                              
  slice_head(n = 10)   
top10_city
## # A tibble: 10 × 3
##    City            n ty_le
##    <chr>       <int> <dbl>
##  1 Miami       64609  3.67
##  2 Orlando     36413  2.07
##  3 Los Angeles 31072  1.76
##  4 Houston     25865  1.47
##  5 Dallas      24306  1.38
##  6 Charlotte   23308  1.32
##  7 Raleigh     16410  0.93
##  8 Nashville   14368  0.82
##  9 Baton Rouge 13683  0.78
## 10 Sacramento  13543  0.77

count(City, name = “n”) gom theo City và đếm số bản ghi, đặt tên cột đếm là n. 

Sau đó mutate(ty_le = round(n / sum(n) * 100, 2)) tính tỷ lệ % của từng thành phố trên toàn bộ dữ liệu (n chia tổng, nhân 100, làm tròn 2 số).

Tiếp đến arrange(desc(n)) sắp xếp giảm dần theo số vụ để xếp hạng, và slice_head(n = 10) chỉ lấy 10 thành phố đứng đầu.

ggplot(top10_city, aes(x = reorder(City, n), y = n)) +
  geom_bar(stat = "identity", fill = "steelblue") +
  coord_flip() +  
  labs(title = "Top 10 thành phố có số vụ tai nạn cao nhất tại Mỹ",
       x = "Thành phố",
       y = "Số vụ tai nạn") +
  theme_minimal() +
  geom_text(aes(label = paste0(n, " (", ty_le, "%)")),
            hjust = -0.1, size = 3.5) +              
  theme(plot.title = element_text(hjust = 0.5))    

ggplot(top10_city, aes(x = reorder(City, n), y = n)) nạp dữ liệu và ánh xạ thẩm mỹ: trục hoành là tên thành phố được reorder theo n để sắp xếp tăng (sau khi lật trục sẽ thành giảm dần), trục tung là số vụ n.

Lớp geom_bar(stat = “identity”, fill = “steelblue”) vẽ cột với chiều dài đúng bằng giá trị n (vì dùng “identity”) và tô màu xanh.

Lớp coord_flip() đảo hệ trục để cột nằm ngang—giúp đọc nhãn thành phố dài dễ hơn. Lớp labs(…) đặt tiêu đề và nhãn trục. Lớp theme_minimal() áp dụng giao diện tối giản.

Lớp geom_text(aes(label = paste0(n, ” (“, ty_le,”%)“)), hjust = -0.1, size = 3.5) thêm nhãn số ngay trên mỗi cột, gồm cả số vụ (n) và tỷ lệ phần trăm (ty_le) trong ngoặc;

Tham số hjust đẩy nhãn ra ngoài đầu cột để dễ đọc. Cuối cùng theme(plot.title = element_text(hjust = 0.5)) căn giữa tiêu đề.

Nhận xét: Top 10 thành phố theo số vụ ghi nhận trong bộ dữ liệu 2022. Miami đứng đầu với 64,544 vụ, chiếm 3.66% toàn tập; theo sau là Orlando (2.07%), Los Angeles (1.76%), Houston (1.47%)… Tỷ lệ phần trăm mỗi thành phố khá nhỏ do tổng mẫu rất lớn, và phân bố có đuôi dài (chỉ vài thành phố chiếm phần đáng kể). Cần lưu ý khả năng thiên lệch bao phủ: số vụ cao không nhất thiết đồng nghĩa “nguy hiểm hơn”, có thể phản ánh mức bao phủ cảm biến/nguồn dữ liệu và quy mô dân số/lưu lượng

1.3.1.1.4 Thống kê tần suất, tỷ lệ % và trực quan hoá của Top 10 đường (Street) xảy ra nhiều tai nạn nhất
top10_street <- Accidents %>%
  count(Street, name = "n") %>%                        
  mutate(ty_le = round(n / sum(n) * 100, 2)) %>%    
  arrange(desc(n)) %>%                              
  slice_head(n = 10)   
top10_street
## # A tibble: 10 × 3
##    Street     n ty_le
##    <chr>  <int> <dbl>
##  1 I-95 S 23999  1.36
##  2 I-95 N 15773  0.89
##  3 I-95   12712  0.72
##  4 I-5 N  11419  0.65
##  5 I-10 E 10637  0.6 
##  6 I-5 S  10323  0.59
##  7 I-10 W  9731  0.55
##  8 I-10    8964  0.51
##  9 I-75    8633  0.49
## 10 I-90 W  8114  0.46

ount(Street, name = “n”) gom theo biến Street và đếm số bản ghi, đặt tên cột đếm là n.

Tiếp đó, mutate(ty_le = round(n / sum(n) * 100, 2)) tính tỷ lệ phần trăm của từng tuyến trên tổng số (lấy n chia tổng sum(n), nhân 100 và làm tròn 2 số).

Sau khi có số vụ và tỷ lệ, ta sắp xếp giảm dần bằng arrange(desc(n)) rồi lấy 10 dòng đầu với slice_head(n = 10).

ggplot(top10_street, aes(x = reorder(Street, n), y = n)) +
  geom_bar(stat = "identity", fill = "#66c2a5", color = "white") +
  coord_flip() +
  labs(title = "Top 10 con đường có số vụ tai nạn cao nhất tại Mỹ",
       x = "Tên đường", y = "Số vụ tai nạn") +
  theme_minimal(base_size = 13) +
  geom_text(aes(label = paste0(n, " (", ty_le, "%)")), 
            hjust = -0.1, size = 3.5, color = "#333333") +
  theme(plot.title = element_text(hjust = 0.5))

ggplot(): nạp dữ liệu top10_street và ánh xạ thẩm mỹ. Trục hoành là tên đường, được reorder(Street, n) để sắp theo giá trị n (sau khi lật trục sẽ thấy thứ tự giảm dần). Trục tung là số vụ tai nạn n. 

geom_bar(): vẽ cột với chiều dài đúng bằng n (vì dùng “identity”); tô màu xanh mã hex #66c2a5 và viền trắng cho dễ tách cột.

coord_flip(): đảo hệ trục để cột nằm ngang, giúp đọc nhãn tên đường dài dễ hơn.

labs(): đặt tiêu đề và nhãn trục. theme_minimal(): áp dụng giao diện tối giản, phông chữ mặc định lớn hơn một chút cho dễ đọc.

geom_text(): in nhãn ở cuối mỗi cột với định dạng “số vụ (tỷ lệ%)”.

hjust = -0.1 đẩy nhãn ra ngoài đầu cột. theme(plot.title = element_text(hjust = 0.5)): căn giữa tiêu đề biểu đồ (hjust = 0.5)

Nhận xét:Các hướng của đường liên bang chiếm ưu thế: I-95 S dẫn đầu với 16,763 vụ (0.95%), kế đến I-95 N (0.89%), rồi I-95 (không ghi hướng), I-5 N, I-10 E,… Mỗi tuyến riêng lẻ chỉ chiếm <1% tổng số vụ do quy mô mẫu rất lớn. Danh sách này phản ánh các trục cao tốc liên bang lưu lượng lớn; tuy nhiên con số cao còn chịu ảnh hưởng của mức độ ghi nhận/bao phủ dữ liệu ở từng khu vực, nên khi so sánh rủi ro giữa tuyến đường, nên chuẩn hoá thêm theo lưu lượng giao thông hoặc chiều dài tuyến.

1.3.1.1.5 Thống kê tần suất, tỷ lệ % và trực quan hoá của Top 10 State xảy ra nhiều tai nạn nhất
top10_bang <- Accidents %>%
  count(State, name = "n") %>%
  arrange(desc(n)) %>% slice_head(n = 10) %>%
  mutate(ty_le = round(100*n/sum(n), 2))
top10_bang
## # A tibble: 10 × 3
##    State      n ty_le
##    <chr>  <int> <dbl>
##  1 CA    375913 30.1 
##  2 FL    263119 21.1 
##  3 VA     99311  7.96
##  4 TX     95509  7.65
##  5 NY     94765  7.59
##  6 SC     84880  6.8 
##  7 PA     80839  6.48
##  8 NC     72167  5.78
##  9 NJ     42244  3.38
## 10 AZ     39423  3.16

count(State, name = “n”): gom theo mã bang (State) và đếm số bản ghi, đặt tên cột đếm là n.

arrange(desc(n)) %>% slice_head(n = 10): sắp xếp giảm dần theo n và lấy 10 bang có số vụ cao nhất.

mutate(ty_le = round(n / sum(n) * 100, 2)): tính tỷ lệ % nhưng chỉ trong phạm vi TOP-10 (vì sum(n) lúc này là tổng của 10 bang đã lọc).

library(treemapify)   
## Warning: package 'treemapify' was built under R version 4.4.3
ggplot(top10_bang,
       aes(area = n, fill = n,
           label = paste0(State, "\n", scales::comma(n), " (", ty_le, "%)"))) +
  geom_treemap() +
  geom_treemap_text(colour = "white", place = "centre", grow = TRUE) +
  scale_fill_gradient(low = "#9ECAE1", high = "#08519C", guide = "none") +
  labs(title = "Top 10 bang – Treemap theo số vụ tai nạn") +
  theme_minimal(base_size = 13)

ggplot() khởi tạo biểu đồ với dữ liệu top 10 bang; trong đó area = n ánh xạ diện tích mỗi ô tỉ lệ với số vụ tai nạn, fill = n tô màu theo cùng thang cường độ (nhiều vụ hơn → ô to và màu đậm hơn), còn label = paste0(…) ghép nhãn hiển thị mã bang + số vụ đã định dạng dấu phẩy + phần trăm.

Lớp geom_treemap() vẽ các ô hình chữ nhật theo các ánh xạ đó, và geom_treemap_text() chèn nhãn vào giữa ô, dùng chữ màu trắng để tương phản và cho phép chữ co giãn theo diện tích ô để đọc dễ hơn.

Thang màu liên tục được thiết lập bằng scale_fill_gradient(), chuyển từ xanh nhạt đến xanh đậm và ẩn chú giải vì chính ô đã thể hiện rõ độ lớn.

Phần tiêu đề được thêm với labs(), và theme_minimal(base_size = 13) áp theme tối giản cùng cỡ chữ lớn để tăng tính đọc.

Nhận xét:Trong TOP-10 bang, California (CA) đứng đầu với 375,913 bản ghi và chiếm 30.12% tổng của nhóm TOP-10; Florida (FL) theo sau (21.08%). Các bang đông dân và có mạng lưới cao tốc dày (VA, TX, NY, SC, …) cũng góp mặt. Cần lưu ý: tỷ lệ hiển thị là tỷ lệ trong TOP-10, không phải tỷ lệ trên toàn bộ nước Mỹ. Ngoài ra, số vụ cao còn chịu ảnh hưởng của quy mô dân số, lưu lượng giao thông và mức độ bao phủ/ghi nhận dữ liệu từng bang.

1.3.1.1.6 Thống kê tần suất, tỷ lệ % và trực quan hoá củ số vụ tai nạn theo nhóm điều kiện thời tiết
weather_summary <- Accidents %>%
  count(weather_simple, name = "So_vu") %>%  
  mutate(Ty_le = round(So_vu / sum(So_vu) * 100, 2)) %>%
  arrange(desc(So_vu))
weather_summary
## # A tibble: 6 × 3
##   weather_simple  So_vu Ty_le
##   <chr>           <int> <dbl>
## 1 Trời quang     917040 52.0 
## 2 Nhiều mây      627144 35.6 
## 3 Mưa            100495  5.7 
## 4 Tuyết           45742  2.6 
## 5 Sương mù        36333  2.06
## 6 Khác            35698  2.03

count() gom theo biến weather_simple và đếm số bản ghi, đặt tên cột đếm là So_vu.

Tiếp theo, mutate() * 100, 2)) tính tỷ lệ phần trăm trên toàn bộ dữ liệu cho từng nhóm (lấy So_vu chia tổng, nhân 100, làm tròn 2 số).

Cuối cùng, arrange(desc(So_vu)) sắp xếp giảm dần theo số vụ để xếp hạng.

ggplot(weather_summary, aes(x = reorder(weather_simple, So_vu), 
                            y = So_vu, fill = weather_simple)) +
  geom_col() +
  coord_flip() +
  geom_text(aes(label = paste0(So_vu, " (", Ty_le, "%)")),
            hjust = -0.1, size = 1.5) +
  scale_fill_brewer(palette = "Set2") +
  labs(title = "Tần suất và tỷ lệ % theo điều kiện thời tiết lúc xảy ra tai nạn",
       x = "Điều kiện thời tiết",
       y = "Số vụ tai nạn") +
  theme_minimal() +
  theme(legend.position = "none")

ggplot() Nạp bảng tổng hợp; sắp xếp nhãn weather_simple theo So_vu để hiển thị theo thứ tự giảm dần; tô màu theo từng nhóm thời tiết.

geom_col(): Vẽ cột với chiều cao chính là So_vu.

coord_flip(): Lật trục để thành bar ngang, giúp đọc nhãn tiếng Việt dài rõ ràng.

geom_text() Gắn nhãn ở đầu cột theo định dạng “Số_vụ (Tỷ_lệ%)”; hjust = -0.1 đẩy nhãn ra ngoài cột, size chỉnh cỡ chữ.

scale_fill_brewer(palette = “Set2”): Dùng bảng màu rời rạc dễ phân biệt giữa các nhóm.

labs(title = …, x = “Điều kiện thời tiết”, y = “Số vụ tai nạn”) Đặt tiêu đề và tên trục.

theme_minimal() + theme(legend.position = “none”): Theme tối giản và ẩn chú giải vì màu đã trùng với nhãn trục.

Nhận xét:Tai nạn chủ yếu xảy ra khi trời quang (~49.84%) và nhiều mây (~35.58%). Các điều kiện mưa (~5.70%), khác (~4.22%), tuyết (~2.60%) và sương mù (~2.06%) chiếm tỷ trọng nhỏ hơn nhiều. Điều này không có nghĩa thời tiết xấu “an toàn hơn”; thay vào đó phản ánh hiệu ứng phơi nhiễm: phần lớn thời gian/lưu lượng giao thông diễn ra trong điều kiện bình thường nên số vụ ghi nhận nhiều ở các nhóm đó.

1.3.1.1.7 Thống kê tần suất ngày trong tuần xảy ra tai nạn
library(dplyr)
day_summary <- Accidents %>%
  count(Day, name = "So_vu") %>%                     
  mutate(Ty_le = round(So_vu / sum(So_vu) * 100, 2)) %>%  
  arrange(desc(So_vu))                               
day_summary
## # A tibble: 7 × 3
##   Day       So_vu Ty_le
##   <chr>     <int> <dbl>
## 1 Thứ Sáu  307733 17.5 
## 2 Thứ Năm  292526 16.6 
## 3 Thứ Tư   287516 16.3 
## 4 Thứ Ba   274019 15.6 
## 5 Thứ Hai  246663 14   
## 6 Thứ Bảy  199110 11.3 
## 7 Chủ Nhật 154885  8.79

count(Day, name = “So_vu”) gom dữ liệu theo biến Day (đã tạo từ Start_Time) và đếm số bản ghi ở mỗi ngày, đặt tên cột đếm là So_vu.

Tiếp theo, mutate(Ty_le = round(So_vu / sum(So_vu) * 100, 2)) tính tỷ lệ phần trăm của từng ngày trên toàn bộ tập 2022 (chia cho tổng, nhân 100, làm tròn 2 chữ số).

Cuối cùng, arrange(desc(So_vu)) sắp xếp giảm dần theo số vụ để xếp hạng và day_summary in ra bảng kết quả.

ggplot(day_summary, aes(x = reorder(Day, -So_vu), y = So_vu, fill = Day)) +
  geom_col() +
  geom_text(aes(label = paste0(So_vu, " (", Ty_le, "%)")), 
            vjust = -0.5, size = 2.5) +
  scale_fill_brewer(palette = "Set2") +
  labs(title = "Phân bố số vụ tai nạn theo ngày trong tuần",
       x = "Ngày trong tuần", y = "Số vụ tai nạn") +
  theme_minimal() +
  theme(legend.position = "none")

ggplot(): nạp dữ liệu và ánh xạ thẩm mỹ. Trục x là Day được sắp xếp giảm dần bằng reorder(Day, -So_vu) để ngày có nhiều vụ đứng trước; trục y là số vụ So_vu; màu cột tô theo từng ngày để dễ phân biệt.

geom_col(): vẽ cột với chiều cao đúng bằng So_vu

geom_text(): gắn nhãn “số vụ (tỷ lệ%)” ngay trên đỉnh cột; vjust = -0.5 đẩy nhãn lệch lên một chút để không đè lên cột.

scale_fill_brewer(): dùng bảng màu rời rạc “Set2”

labs(): đặt tiêu đề và tên trục.

theme_minimal() + theme(): áp theme tối giản và ẩn chú giải vì màu đã trùng với nhãn trục x nên legend không cần thiết.

Tai nạn tập trung vào các ngày làm việc, đặc biệt Thứ Sáu (≈307,7k vụ; 17.46%) cao nhất, kế đến Thứ Năm (16.60%) và Thứ Tư (16.31%). Cuối tuần thấp hơn rõ: Thứ Bảy 11.30% và Chủ Nhật thấp nhất 8.79%. Mẫu hình này phù hợp với “hiệu ứng phơi nhiễm”: lưu lượng giao thông đi làm/đi học tăng mạnh vào ngày thường, nhất là cuối tuần làm việc (Thứ Sáu), nên số vụ ghi nhận nhiều hơn

1.3.1.1.8 Thống kê tần suất thời điểm trong ngày xảy ra tai nạn
time_summary <- Accidents %>%
  count(time_period, name = "So_vu") %>%
  mutate(Ty_le = round(So_vu / sum(So_vu) * 100, 2)) %>%
  arrange(desc(So_vu))
time_summary
## # A tibble: 4 × 3
##   time_period  So_vu Ty_le
##   <ord>        <int> <dbl>
## 1 Chiều       758195  43.0
## 2 Sáng        534504  30.3
## 3 Đêm         239379  13.6
## 4 Tối         230374  13.1

count() gộp dữ liệu theo biến time_period (đã cắt giờ thành Đêm–Sáng–Chiều–Tối) và đếm số bản ghi, đặt tên cột đếm là So_vu.

mutate() tính tỷ lệ phần trăm trên toàn bộ tập cho từng khoảng thời gian, làm tròn 2 chữ số.

arrange() sắp xếp giảm dần theo số vụ để xếp hạng

ggplot(time_summary, aes(x = reorder(time_period, -So_vu), y = So_vu, fill = time_period)) +
  geom_col() +
  geom_text(aes(label = paste0(So_vu, " (", Ty_le, "%)")), 
            vjust = -0.5, size = 3.5) +
  scale_fill_manual(values = c("#f4a261", "#e76f51", "#2a9d8f", "#264653")) +
  labs(title = "Phân bố số vụ tai nạn theo thời điểm trong ngày",
       x = "Thời điểm trong ngày", y = "Số vụ tai nạn") +
  theme_minimal() +
  theme(legend.position = "none")

ggplot(): dùng bảng đã tổng hợp; trục x là time_period được sắp giảm dần theo So_vu để xếp hạng; trục y là số vụ; màu cột gắn với từng khoảng thời gian.

geom_col() vẽ cột có chiều cao đúng bằng So_vu.

Lớp geom_text() chèn nhãn ngay trên đỉnh cột theo định dạng “số vụ (tỷ lệ %)”; vjust = -0.5 đẩy nhãn lên khỏi cột để không chồng lấn.

Màu cột được cố định thủ công bằng scale_fill_manual(), bảo đảm bảng màu ổn định giữa các lần vẽ.

labs() đặt tiêu đề và tên trục.

theme_minimal() + theme() áp dụng giao diện tối giản và ẩn chú giải (legend) vì màu đã trùng khớp với nhãn trên trục x.

Tai nạn tập trung chủ yếu vào buổi Chiều (≈ 758,195 vụ; 43.02%), tiếp đến buổi Sáng (≈ 534,504; 30.33%). Hai khoảng Đêm (≈ 239,379; 13.58%) và Tối (≈ 230,374; 13.07%) chiếm tỷ trọng nhỏ hơn. Mẫu hình này phản ánh hiệu ứng phơi nhiễm giao thông: khung 12–18h (đặc biệt 15–19h) và 6–12h là các “giờ cao điểm” nên số vụ nhiều nhất. Đêm/Tối ít phương tiện hơn nên tỷ lệ thấp hơn, dù rủi ro trên mỗi km có thể cao

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

1.3.1.2.1 Thống kê mô tả biến Severity
summary(Accidents$Severity)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##   1.000   2.000   2.000   2.072   2.000   4.000

summary() cho nhanh 6 thống kê cơ bản (min, 4 tứ phân vị, mean, max), đủ để nhận diện xu hướng, độ lệch, và khả năng mất cân bằng lớp

Mức độ nghiêm trọng (Severity) Min = 1, Max = 4: thang giá trị đầy đủ 1–4 đều xuất hiện.

Q1 = 2, Median = 2, Q3 = 2: cả ba tứ phân vị đều đúng bằng 2 → phân phối cực kỳ tập trung ở mức 2. Điều này hàm ý ít nhất 75% các vụ có mức độ không vượt quá 2 (mức “nhẹ/vừa”).

Mean = 2.072 nhỉnh hơn 2 một chút, cho thấy có đuôi phải rất ngắn do một phần nhỏ các vụ ở mức 3–4 kéo trung bình lên, nhưng ảnh hưởng không lớn.

Sự trùng nhau của Q1, Q2, Q3 và mean gần 2 cho thấy mode (giá trị xuất hiện nhiều nhất) nhiều khả năng cũng là 2, đồng thời độ phân tán thấp (phần lớn vụ việc có mức nghiêm trọng tương tự nhau)..

Về ý nghĩa kinh tế, kết quả này phản ánh sự cải thiện của hệ thống an toàn giao thông, chất lượng phương tiện và hạ tầng đường bộ tại Mỹ, góp phần giảm thiểu thiệt hại vật chất và chi phí y tế – bảo hiểm do tai nạn gây ra so với các giai đoạn trước. Điều này cũng cho thấy nỗ lực đầu tư vào công nghệ an toàn (ADAS, phanh tự động, cảnh báo va chạm) đang mang lại hiệu quả rõ rệt.

1.3.1.2.2 Thống kê mô tả biến Distance(mi)
summary(Accidents$`Distance(mi)`)
##     Min.  1st Qu.   Median     Mean  3rd Qu.     Max. 
##   0.0000   0.0570   0.2600   0.9264   0.9780 336.5700

Biến Distance(mi) đo chiều dài đoạn đường bị ảnh hưởng (mile) cho thấy một phân phối lệch phải rất mạnh: nửa số vụ tai nạn có phạm vi ảnh hưởng không quá 0,26 mile và 75% không quá 0,978 mile, nghĩa là đa phần sự cố chỉ tác động trên đoạn đường ngắn, trong phạm vi hẹp (cục bộ) như tại giao lộ, khu dân cư, hoặc khu vực đô thị đông đúc, nơi mật độ phương tiện ca. Trung bình 0,9264 mile cao hơn trung vị nhiều lần phản ánh đuôi phải dài do một số ít vụ việc có phạm vi rất lớn kéo trung bình lên. Ở đầu dưới, Min = 0 cho thấy nhiều bản ghi gần như không lan tỏa theo chiều dài (tai nạn cục bộ hoặc làm tròn về 0). Ở đầu trên, Max = 336,57 mile vượt xa Q3 (tỷ lệ Max/Q3 ≈ 344) là dấu hiệu điển hình của ngoại lệ—có thể là sự kiện đặc biệt (phong tỏa đường dài), tai nạn dây chuyền hoặc sai lệch ghi nhận.

Về ý nghĩa kinh tế, việc phần lớn các vụ tai nạn diễn ra trong phạm vi ngắn cho thấy rủi ro giao thông đô thị là nguyên nhân chính gây tổn thất kinh tế, như tắc nghẽn, hư hại tài sản nhỏ, hoặc mất năng suất lao động do chậm trễ. Từ đó, có thể nhấn mạnh tầm quan trọng của chính sách kiểm soát tốc độ, hệ thống đèn giao thông thông minh và quy hoạch đô thị hợp lý nhằm giảm tần suất tai nạn cục bộ.

1.3.1.2.3 Thống kê mô tả biến Temperature(C)
summary(Accidents$`temp_c`)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##  -42.78   10.56   18.89   17.32   25.56   77.78

Nhiệt độ có khoảng quan sát rất rộng từ –42.78°C đến 77.78°C, cho thấy tồn tại các giá trị cực trị (có thể là điều kiện thời tiết khắc nghiệt hoặc sai số đo/ghi). Trung vị 18.89°C cao hơn trung bình 17.32°C, hàm ý phân phối lệch trái nhẹ (đuôi về phía nhiệt độ thấp), còn khoảng tứ phân vị IQR ≈ 15.0°C (Q1 = 10.56°C, Q3 = 25.56°C) cho thấy 50% vụ tai nạn diễn ra trong dải mát–ấm tương đối dễ chịu.

Về ý nghĩa thống kê, phân phối nhiệt độ tương đối đối xứng quanh giá trị trung bình, cho thấy yếu tố thời tiết không tạo ra biến động cực lớn trong dữ liệu tai nạn.

Về ý nghĩa kinh tế, điều này phản ánh rằng tai nạn giao thông không chỉ phụ thuộc vào khí hậu khắc nghiệt, mà chủ yếu bắt nguồn từ yếu tố hành vi con người, mức độ đô thị hóa và lưu lượng phương tiện. Các điều kiện thời tiết ôn hòa nhưng mật độ giao thông cao (đặc biệt ở đô thị lớn như California, Texas, Florida) vẫn dẫn đến rủi ro tai nạn cao, gây thiệt hại kinh tế do tắc nghẽn và chi phí bảo hiểm tăng.

1.3.1.2.4 Thống kê mô tả biến Humidity(%)
summary(Accidents$`Humidity(%)`)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##    1.00   45.00   63.00   62.06   81.00  100.00

Độ ẩm (Humidity%) độ ẩm trải từ 1% đến 100%, với trung vị = 63% và trung bình = 62.06%, khá sát nhau → phân phối gần đối xứng, tập trung quanh mức ẩm trung bình–cao. Tứ phân vị thứ nhất Q1 = 45% và thứ ba Q3 = 81% cho thấy 50% vụ tai nạn xảy ra khi độ ẩm nằm trong khoảng 45–81%; đây là dải phổ biến của thời tiết ẩm/ẩm cao. Giá trị cực đại 100% thường gắn với sương mù/ mưa (bão hòa hơi nước), còn cực tiểu 1% nhiều khả năng là ngoại lệ đo lường hoặc điều kiện rất khô hiếm gặp

Độ ẩm cao đồng nghĩa mặt đường ướt/trơn, quãng phanh dài hơn, kính xe dễ đọng hơi làm giảm tầm nhìn; hậu quả là tăng xác suất va chạm nhẹ (đuôi–đầu, trượt bánh) và độ trễ giao thông. Điều này kéo theo chi phí: bảo hiểm (tần suất bồi thường tăng), chi phí vận hành (tiêu hao nhiên liệu do chạy chậm, bảo dưỡng phanh/lốp), và chi phí thời gian trong logistics đô thị.

Ngược lại, ẩm quá thấp (khí khô) có thể tăng bụi và kích thích mệt mỏi mắt, nhưng tác động nhỏ hơn về mặt ma sát đường. Gợi ý chính sách/ứng dụng: triển khai giới hạn tốc độ linh hoạt khi ẩm vượt ngưỡng (ví dụ >80%), cảnh báo phanh–khoảng cách trên bảng điện tử, ưu tiên vỏ lốp có rãnh thoát nước, và điều chỉnh lịch giao nhận tránh khung giờ ẩm cao đi kèm mưa/sương mù. Những biện pháp này giúp giảm tai nạn và ổn định chi phí chuỗi cung ứng trong điều kiện thời tiết ẩm ướt.

1.3.1.2.5 Thống kê mô tả biến Pressure(in)
summary(Accidents$`Pressure(in)`)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##    0.00   29.21   29.72   29.38   29.96   58.63

Về thống kê, các tóm tắt cho thấy áp suất tập trung rất chặt quanh mức chuẩn mực: Q1 = 29.21, Median = 29.72, Mean = 29.38, Q3 = 29.96 (đơn vị inch Hg). Khoảng 29–30 inHg cũng đúng với dải điển hình ở mực nước biển (~29.92 inHg), cho thấy đa số bản ghi hợp lý và biến có độ phân tán nhỏ. Tuy nhiên, có hai cực trị bất thường: Min = 0.00 và Max = 58.63 inHg. Cả hai đều phi vật lý đối với áp suất khí quyển (áp suất mặt đất thực tế hiếm khi ra ngoài 28–31 inHg). Điều này nhiều khả năng là mã hóa lỗi/thiết bị đo (0 được ghi thay cho thiếu dữ liệu; 58.63 do lỗi nhập). Khi mô hình hóa, nên thay 0 hoặc giá trị < 25 hay > 32 inHg bằng NA, hoặc winsorize về rìa hợp lý; đồng thời chuẩn hóa (center/scale) hoặc tạo nhóm áp suất (thấp/bình thường/cao) để bắt tính phi tuyến.

Về thực tiễn–kinh tế, áp suất thấp thường đi kèm hệ thống bão, mưa, gió mạnh, kéo theo độ bám đường giảm, tầm nhìn kém, rủi ro va chạm và độ trễ giao thông cao hơn (chi phí thời gian, nhiên liệu, gián đoạn logistics, bồi thường bảo hiểm). Ngược lại, áp suất cao gắn với trời quang và lưu thông ổn định hơn. Vì áp suất có tương quan cấu trúc với các biến thời tiết khác (Humidity, Visibility, Weather_Condition), cần kiểm soát đa cộng tuyến (VIF/regularization) hay rút gọn thành chế độ thời tiết để diễn giải rõ hơn. Thực hành, có thể thiết lập cảnh báo vận hành khi áp suất xuống dưới ngưỡng (ví dụ < 29.5 inHg cùng xu hướng giảm), kích hoạt giới hạn tốc độ linh hoạt, điều chỉnh kế hoạch giao nhận và tăng tần suất thông báo an toàn nhằm giảm tổn thất kinh tế do điều kiện khí áp bất lợi.

1.3.1.2.6 Thống kê mô tả biến Visibility(mi)
summary(Accidents$`Visibility(mi)`)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##   0.000  10.000  10.000   9.178  10.000 100.000

Tầm nhìn (Visibility) có trung bình 9,178 dặm, trung vị 10 dặm, dao động từ 0 đến 100 dặm. Giá trị 0 và 100 là bất thường (0 có thể là sương mù dày hoặc lỗi ghi; 100 gần chắc là sai đơn vị/nhập liệu vì thang đo thực tế thường tối đa 10), trong đó phần lớn giá trị tập trung quanh 10 dặm – cho thấy đa số tai nạn xảy ra khi tầm nhìn khá tốt.

Tầm nhìn thấp → nguy cơ tai nạn cao hơn, xe phải chạy chậm, tốn thời gian và chi phí (nhiên liệu, nhân công, bảo hiểm). Tầm nhìn 10 mi thường là thời tiết quang mây, giao thông thuận lợi. Trong vận hành, có thể đặt ngưỡng cảnh báo (ví dụ <2 mi) để tự động hạ tốc độ, tăng khoảng cách an toàn hoặc dời lịch vận tải.

=> Kết quả này chứng minh rằng nguyên nhân chính của tai nạn không đến từ điều kiện môi trường (sương mù, mưa lớn), mà chủ yếu là lỗi của người lái, sự chủ quan, hoặc hạ tầng giao thông phức tạp.

Trong bối cảnh kinh tế, điều này cho thấy các thiệt hại do tai nạn không chỉ phát sinh từ điều kiện tự nhiên, mà còn từ yếu tố con người và hành vi vận hành giao thông – là trọng tâm cần được quản lý trong chính sách giảm thiểu tổn thất xã hội.

1.3.2 Thống kê tần số một vài biến theo nhóm

1.3.2.1 Thời tiết & ca trong ngày

freq2_row <- Accidents %>%
  count(weather_simple, time_period, name = "n") %>%
  group_by(weather_simple) %>%
  mutate(row_pct = 100*n/sum(n)) %>%
  ungroup() %>%
  mutate(`n (% )` = sprintf("%s (%.2f%%)", comma(n), row_pct)) %>%
  select(weather_simple, time_period, `n (% )`) %>%
  pivot_wider(names_from = time_period, values_from = `n (% )`, values_fill = "0 (0.00%)")

freq2_row
## # A tibble: 6 × 5
##   weather_simple Đêm              Sáng             Chiều            Tối         
##   <chr>          <chr>            <chr>            <chr>            <chr>       
## 1 Khác           3,660 (10.25%)   7,660 (21.46%)   20,062 (56.20%)  4,316 (12.0…
## 2 Mưa            15,359 (15.28%)  29,254 (29.11%)  41,815 (41.61%)  14,067 (14.…
## 3 Nhiều mây      78,500 (12.52%)  189,858 (30.27%) 287,956 (45.92%) 70,830 (11.…
## 4 Sương mù       8,862 (24.39%)   16,196 (44.58%)  7,844 (21.59%)   3,431 (9.44…
## 5 Trời quang     125,177 (13.65%) 275,320 (30.02%) 386,311 (42.13%) 130,232 (14…
## 6 Tuyết          7,821 (17.10%)   16,216 (35.45%)  14,207 (31.06%)  7,498 (16.3…

count(…): Đếm số vụ tai nạn (n) cho từng tổ hợp trạng thái thời tiết (weather_simple) × ca trong ngày (time_period).

group_by(…): Với mỗi nhóm thời tiết, tính tỉ trọng theo hàng: phần trăm số vụ của từng ca trong ngày trong tổng số vụ của đúng nhóm thời tiết đó. (Tức là các phần trăm trong cùng 1 hàng cộng ~100%).

mutate(…): Ghép số đếm đã format dấu phẩy và phần trăm thành một ô duy nhất kiểu “12,345 (27.80%)”.

select(…): Giữ lại cột thời tiết, ca và cột đã gộp “n ( % )”.

pivot_wider(…): Xoay bảng sang dạng rộng: mỗi ca là một cột (Đêm/Sáng/Chiều/Tối). Nếu ô nào không có dữ liệu thì điền “0 (0.00%)”.

Nhận xét kết quả:

Sương mù: tập trung rõ vào buổi Sáng (≈44.6%), thấp hơn vào Tối (≈9.4%) và Đêm (≈24% trong bảng 1). Điều này hợp lý vì sương mù thường dày vào sáng sớm → rủi ro tầm nhìn tăng đầu ngày.

Trời quang: tỉ trọng cao nhất vào Chiều (≈42.1%), kế đến Sáng (≈30.0%); Đêm chiếm nhỏ (≈13.7%). Đây là mẫu hình giao thông: lưu lượng cao ban ngày/chiều → nhiều vụ hơn ngay cả khi thời tiết tốt.

Nhiều mây: thiên về Chiều (≈45.9%), Sáng (≈30.3%), Tối thấp (≈11.3%) — khá giống “Trời quang”, phản ánh hiệu ứng lưu lượng hơn là tác động xấu của thời tiết.

Mưa: phân bố khá đều nhưng vẫn nghiêng về Chiều (≈41.6%) > Sáng (≈29.1%) > Tối (≈14.0%). Mưa giờ cao điểm chiều có thể làm xung đột giao thông tăng (đường trơn, tắc đường).

Tuyết: Sáng (≈35.5%) hơi cao hơn Chiều (≈31.1%); Tối (≈16.4%), Đêm (≈17.1%) tương đương. Ở vùng có tuyết, xử lý đường sá & di chuyển đầu ngày có thể làm rủi ro tăng.

Nhóm “Khác”: tỉ trọng Chiều (≈56.2%) nổi trội; có thể là các điều kiện khó phân loại nhưng vẫn chịu tác động lưu lượng giờ chiều.

Hàm ý thực tiễn – xã hội:

Ưu tiên can thiệp theo khung giờ & thời tiết rủi ro: sáng sớm có sương mù cần biển cảnh báo tầm nhìn, giới hạn tốc độ tạm thời; chiều mưa/nhiều mây/trời quang cần quản lý dòng xe giờ cao điểm (điều tiết đèn, thông tin lộ trình).

Truyền thông an toàn theo bối cảnh: cảnh báo người đi làm buổi sáng mùa lạnh (sương mù/tuyết), và chiến dịch an toàn giờ tan tầm khi mưa.

Lập lịch bảo trì & tuần tra: tăng cường lực lượng vào buổi sáng (sương mù/tuyết) và buổi chiều ở các bang/thành phố có lưu lượng lớn.

1.3.2.2 Ca trong ngày & ngày trong tuần

freq_colpct <- Accidents %>%
  count(time_period, Day, name = "n") %>%
  group_by(time_period) %>%
  mutate(col_pct = 100 * n / sum(n)) %>%
  ungroup() %>%
  mutate(cell = sprintf("%s (%.2f%%)", comma(n), col_pct)) %>%
  select(time_period, Day, cell) %>%
  pivot_wider(names_from = Day, values_from = cell, values_fill = "0 (0.00%)")
freq_colpct
## # A tibble: 4 × 8
##   time_period `Chủ Nhật`      `Thứ Ba`   `Thứ Bảy` `Thứ Hai` `Thứ Năm` `Thứ Sáu`
##   <ord>       <chr>           <chr>      <chr>     <chr>     <chr>     <chr>    
## 1 Đêm         27,976 (11.69%) 36,033 (1… 29,209 (… 34,556 (… 38,154 (… 35,513 (…
## 2 Sáng        37,529 (7.02%)  88,483 (1… 55,179 (… 78,602 (… 92,988 (… 88,165 (…
## 3 Chiều       58,803 (7.76%)  121,263 (… 75,919 (… 106,041 … 129,034 … 142,348 …
## 4 Tối         30,577 (13.27%) 28,240 (1… 38,803 (… 27,464 (… 32,350 (… 41,707 (…
## # ℹ 1 more variable: `Thứ Tư` <chr>

count(…): đếm số vụ tai nạn cho từng cặp (ca trong ngày, ngày trong tuần); kết quả có cột n.

group_by(time_period): gom nhóm theo ca để chuẩn bị tính tỷ lệ theo “cột” (mỗi ca là một cột logic trong chuẩn hoá).

mutate(…): tính tỷ lệ theo ca (tức là trong một ca, mỗi ngày chiếm % bao nhiêu). Đây là lý do gọi là “column percent” theo ngữ nghĩa.

mutate(…): ghép tần số tuyệt đối và tỷ lệ % vào chung một ô dạng “n (% )”.

select(time_period, Day, cell): chỉ giữ 3 cột cần để bung bảng.

pivot_wider(…): xoay bảng thành ma trận, hàng = ca trong ngày, cột = ngày trong tuần, ô = “n (%)”; ô trống được điền “0 (0.00%)”.

Nhận xét:

  • Mẫu hình theo ca trong ngày: Chiều chiếm tỷ trọng lớn nhất trong tuần. Ví dụ: Thứ Sáu 142,348 (18.77%); Thứ Năm 129,034 (17.02%); Thứ Hai 121,263 (15.99%).

⇒ Phù hợp quy luật giao thông giờ cao điểm tan sở (15–18h): lưu lượng cao, mệt mỏi sau làm việc, nắng/oi buổi chiều cũng có thể làm giảm tập trung.

Sáng đứng thứ hai: Thứ Năm 92,988 (17.40%), Thứ Sáu 88,165 (16.49%), Thứ Ba 88,483 (15.99%).

⇒ Giờ cao điểm đi làm/đi học (6–9h) tạo mật độ va chạm cao.

Tối thấp hơn chiều/sáng nhưng nhích mạnh vào Thứ Sáu: 41,707 (18.10%), vượt trội so với nhiều ngày khác.

⇒ Ảnh hưởng của di chuyển buổi tối cuối tuần (giải trí, tụ tập), mệt mỏi + tầm nhìn đêm kém.

Đêm phân bố khá đều và thấp hơn, cao nhất vào Thứ Năm ~15.94% và Thứ Hai ~15.05%, thấp hơn rõ ở cuối tuần (Chủ Nhật 11.69%, Thứ Bảy 12.26%).

⇒ Lưu lượng đêm nhìn chung thấp; riêng giữa tuần có thể do xe tải, đường dài hoạt động nhiều.

  • Mẫu hình theo ngày trong tuần:

Giữa & cuối tuần (Thứ Năm–Thứ Sáu) là “điểm nóng”:

  • Sáng: Thứ Năm 17.40%, Thứ Sáu 16.49%

  • Chiều: Thứ Sáu 18.77% (cao nhất tuần)

  • Tối: Thứ Sáu 18.10% (cao nhất tuần)

⇒ Cộng dồn cho thấy Thứ Sáu là ngày rủi ro cao nhất (đặc biệt chiều–tối).

Đầu tuần (Thứ Hai) cao ở sáng–chiều (đi học/đi làm trở lại), nhưng tối không nổi trội.

Cuối tuần (Chủ Nhật) giảm mạnh ở sáng–chiều, tối vẫn đáng kể (13.27%) do hoạt động giải trí/di chuyển về muộn.

Hàm ý quản lý – kinh tế – xã hội:

Quản trị giao thông theo thời gian: tăng cường tuần tra/điều tiết ở giờ cao điểm sáng & chiều trong tuần; riêng chiều–tối Thứ Sáu cần chiến dịch mạnh (uống rượu lái xe, mệt mỏi, tầm nhìn đêm).

Tối ưu nguồn lực cấp cứu: bố trí thêm kíp chiều–tối ngày Thứ Sáu/Thứ Năm tại điểm nóng; tăng năng lực xử lý nhanh để giảm ùn tắc thứ cấp.

Truyền thông an toàn: nhắm mục tiêu người đi làm giờ cao điểm và người trẻ di chuyển tối cuối tuần (thắt dây an toàn, hạn chế tốc độ, không sử dụng rượu bia).

Hỗ trợ hạ tầng: cải thiện chiếu sáng, biển báo/điểm giao ở khu vực thường đông chiều–tối; điều chỉnh đèn tín hiệu theo khung giờ.

Dữ liệu cho thấy tai nạn tập trung vào Sáng & đặc biệt Chiều, với đỉnh rủi ro rơi vào Thứ Sáu (chiều–tối). Đây là các “khung giờ–ngày” cần ưu tiên can thiệp để giảm thiểu thiệt hại xã hội và kinh tế.

1.3.2.3 Ca trong ngày và mức độ nghiêm trọng

tab_sev3_shift <- Accidents %>%
  mutate(sev3 = ifelse(Severity >= 3, "Nặng (≥3)", "Nhẹ (<3)")) %>%
  count(time_period, sev3, name = "n") %>%
  group_by(time_period) %>%
  mutate(col_pct = 100*n/sum(n),
         cell = sprintf("%s (%.2f%%)", comma(n), col_pct)) %>%
  ungroup() %>%
  select(time_period, sev3, cell) %>%
  pivot_wider(names_from = sev3, values_from = cell, values_fill = "0 (0.00%)")
tab_sev3_shift
## # A tibble: 4 × 3
##   time_period `Nhẹ (<3)`       `Nặng (≥3)`   
##   <ord>       <chr>            <chr>         
## 1 Đêm         220,007 (91.91%) 19,372 (8.09%)
## 2 Sáng        495,955 (92.79%) 38,549 (7.21%)
## 3 Chiều       712,332 (93.95%) 45,863 (6.05%)
## 4 Tối         212,927 (92.43%) 17,447 (7.57%)

mutate(…): Tạo biến phân loại sev3: tai nạn nặng nếu mức Severity ≥ 3, ngược lại là nhẹ.

count(…): Đếm số vụ n cho từng cặp (ca trong ngày, mức độ nặng/nhẹ).

group_by(time_period) → mutate(col_pct = 100 * n / sum(n)): Gom theo ca rồi tính tỷ lệ % trong từng ca (mỗi ô/ca chiếm bao nhiêu phần trăm tổng số vụ của ca đó).

mutate(…): Ghép tần số tuyệt đối và tỷ lệ % thành một chuỗi “n (%)”.

select(time_period, sev3, cell): Giữ 3 cột cần thiết.

pivot_wider(…): Xoay bảng cho gọn: hàng = ca trong ngày, cột = mức độ (nhẹ/nặng), ô = “n (%)”.

Nhận xét kết quả

Phần lớn vụ tai nạn là nhẹ (≈92–94%) ở mọi ca. Tỷ lệ nặng cao hơn vào Đêm (8.09%) và Tối (7.57%), thấp nhất Chiều (6.05%).

→ Cho thấy điều kiện ban đêm (tầm nhìn kém, mệt mỏi, tốc độ cao trên đường vắng) có liên quan đến mức độ nghiêm trọng.

Hàm ý thực tế–xã hội:

Ưu tiên can thiệp buổi Đêm/Tối: tăng chiếu sáng, tuần tra tốc độ, kiểm soát rượu bia, nhắc nhở mệt mỏi khi lái xe.

Nguồn lực y tế khẩn cấp: bố trí sẵn sàng hơn vào Đêm/Tối ở các điểm nóng để giảm tử vong do chậm xử trí.

Truyền thông an toàn: thông điệp nhấn mạnh “lái xe ban đêm rủi ro nặng cao hơn”, khuyến nghị nghỉ ngơi, giảm tốc, kiểm tra đèn–kính lái.

1.3.2.4 Ca trong ngày và nhiệt độ

tab_temp_band <- Accidents %>%
  mutate(temp_band = cut(temp_c,
                         breaks = c(-Inf, 10, 25, Inf),
                         labels = c("Thấp (≤10°C)", "Vừa (10–25°C)", "Cao (>25°C)"))) %>%
  count(temp_band, time_period, name = "n") %>%
  group_by(temp_band) %>%
  mutate(row_pct = 100*n/sum(n),
         cell = sprintf("%s (%.2f%%)", comma(n), row_pct)) %>%
  ungroup() %>%
  select(temp_band, time_period, cell) %>%
  pivot_wider(names_from = time_period, values_from = cell, values_fill = "0 (0.00%)")
tab_temp_band
## # A tibble: 3 × 5
##   temp_band     Đêm              Sáng             Chiều            Tối          
##   <fct>         <chr>            <chr>            <chr>            <chr>        
## 1 Thấp (≤10°C)  90,787 (21.44%)  145,816 (34.43%) 123,581 (29.18%) 63,269 (14.9…
## 2 Vừa (10–25°C) 135,847 (15.33%) 278,129 (31.39%) 345,413 (38.99%) 126,587 (14.…
## 3 Cao (>25°C)   12,745 (2.81%)   110,559 (24.40%) 289,201 (63.84%) 40,518 (8.94…

mutate(…), labels = c(“Thấp (≤10°C)”, “Vừa (10–25°C)”, “Cao (>25°C)”))): Chia biến nhiệt độ temp_c thành 3 khoảng (thấp/vừa/cao).

count(…): Đếm số vụ tai nạn n theo từng tổ hợp (khoảng nhiệt độ × ca trong ngày).

group_by(temp_band) %>% mutate(row_pct = 100 * n / sum(n)): Tính tỷ trọng theo HÀNG: trong mỗi khoảng nhiệt độ, phần trăm các ca (Đêm/Sáng/Chiều/Tối) cộng lại bằng 100%.

mutate(…): Ghép số đếm và % vào cùng một ô hiển thị dạng “n (p%)”.

select(…): Trải bảng rộng: mỗi hàng là khoảng nhiệt độ, mỗi cột là ca trong ngày; ô là “n (p%)”.

Nhận xét:

Vừa (10–25°C): là dải nhiệt độ có nhiều vụ nhất (Sáng 278,129; Chiều 345,413; Tối 126,587; Đêm 135,847). Tỷ trọng trong dải này lần lượt Sáng 31.39% – Chiều 38.99% – Tối 14.29% – Đêm 15.33% → tai nạn tập trung mạnh vào buổi Chiều.

Thấp (≤10°C): tổng số vụ thấp hơn nhưng phân bố đều hơn: Sáng 34.43%, Chiều 29.18%, Đêm 21.44%, Tối 14.94%. Buổi Sáng nhỉnh hơn (khả năng liên quan sương giá, băng tuyết/độ ẩm thấp + giờ cao điểm).

Cao (>25°C): tai nạn tập trung áp đảo vào buổi Chiều (63.84%), sau đó Sáng 24.40%, Tối 8.94%, Đêm 2.81%. Nắng nóng + lưu lượng chiều (tan tầm), mệt mỏi, chói nắng có thể là yếu tố rủi ro.

Mẫu số theo ca (nhìn theo từng hàng): trong mọi dải nhiệt độ, Chiều luôn là ca rủi ro cao nhất, đặc biệt rõ ở dải >25°C; Tối thường có tỷ trọng thấp nhất.

Hàm ý chính sách

An toàn giao thông: tăng cường tuần tra/nhắc nhở vào buổi Chiều, nhất là những ngày nóng; tổ chức che nắng, chống chói (biển báo phản quang, sơn vạch), bố trí giờ làm linh hoạt để giảm dồn ứ chiều.

Mùa lạnh: ưu tiên biện pháp chống trơn trượt, cải thiện tầm nhìn vào buổi Sáng (rải muối, cảnh báo sương mù/băng giá).

Truyền thông: thông điệp khác nhau theo mùa/nhiệt: “tránh nắng nóng buổi chiều”, “cẩn trọng sương mù buổi sáng”.

1.4 Trực quan hoá bộ dữ liệu

pal_many <- c("#1f77b4","#ff7f0e","#2ca02c","#d62728","#9467bd",        
               "#8c564b","#e377c2","#7f7f7f","#bcbd22","#17becf",
               "#a55194","#637939","#e6550d","#6b6ecf","#bd9e39")
Acc_raw <- Accidents
windowsFonts(Times = windowsFont("Times New Roman"))

pal_many <- c()” Tạo một vector chứa mã màu Hex. Đây là bảng màu tuỳ chỉnh bạn sẽ tái sử dụng cho nhiều biểu đồ.

Acc_raw <- Accidents: Sao chép dữ liệu phân tích sang đối tượng Acc_raw để giữ nguyên bản gốc. Sau đó có thể thao tác, lọc, biến đổi trên Accidents mà không sợ mất dữ liệu gốc; bất kỳ lúc nào cũng có thể so sánh lại với Acc_raw.

1.4.1 Không gian và toạ độ

1.4.1.1 Mật độ tai nạn theo toạ độ và mức độ nghiêm trọng (Heatmap 2D × Severity)

library(ggplot2)
ggplot(Acc_raw, aes(Start_Lng, Start_Lat)) +
  stat_bin2d(bins = 80) +
  scale_fill_viridis_c() +
  facet_wrap(~Severity, nrow = 1) +
  labs(title="Mật độ tai nạn theo toạ độ & mức độ",
       x="Kinh độ", y="Vĩ độ", fill="Đếm") +
  theme_minimal()

ggplot(): khởi tạo đồ thị với dữ liệu gốc Acc_raw. Trục x là kinh độ nơi bắt đầu vụ tai nạn (Start_Lng, độ âm vì ở Tây Bán Cầu), trục y là vĩ độ (Start_Lat). Mỗi bản ghi là một điểm trên bản đồ (tọa độ WGS84).

stat_bin2d(bins = 80): thay vì vẽ hàng triệu điểm chồng lên nhau, ta dùng histogram 2 chiều: mặt phẳng (Lng–Lat) được chia thành 80×80 ô vuông; mỗi ô đếm số vụ nằm trong ô đó. Kết quả là bản đồ nhiệt (heatmap) mật độ tai nạn theo không gian.

scale_fill_viridis_c(): thang màu liên tục “viridis”, cảm nhận đồng đều, thân thiện với người mù màu; màu đậm hơn = nhiều vụ hơn.

facet_wrap(~Severity, nrow = 1): tách đồ thị thành 4 ô theo mức Severity (1→4). Nhờ vậy ta so sánh phân bố không gian giữa các mức độ nghiêm trọng mà không bị lẫn.

labs(title=…, x=“Kinh độ”, y=“Vĩ độ”, fill=“Đếm”): tiêu đề, nhãn trục, và nhãn thang màu (“Đếm” là số vụ/ô).

theme_minimal(): bố cục tối giản, dễ đọc.

Dải tọa độ thể hiện đúng nước Mỹ lục địa: kinh độ khoảng –125 → –67, vĩ độ ~25 → 50. Màu sắc đậm nhất tập trung ở một số vùng đô thị hành lang ven biển:

Bờ Đông: khoảng –80 đến –70 (New York–Boston–Washington DC–Miami).

Bờ Tây: khoảng –124 đến –117 (Seattle, Bay Area, LA–San Diego).

Vùng Hồ Lớn/Trung Tây: –95 đến –83, vĩ độ ~35–45 (Chicago–Detroit–Minneapolis).

Texas & Hành lang I-35/I-10: –105 đến –93, vĩ độ ~26–33 (Houston–San Antonio–Dallas).

Tính nhất quán theo mức độ: Bốn ô Severity = 1,2,3,4 có hình thái không gian tương đồng—cùng “đi theo” các trục dân cư & cao tốc.

Tuy nhiên:

Ở Severity 1–2: vùng màu đậm (nhiều vụ) nổi bật rõ rệt tại các siêu đô thị—đặc trưng của tai nạn nhẹ (nhiều va chạm, tốc độ thấp).

Ở Severity 3–4: mật độ không tăng theo kiểu “đậm thêm” ở đô thị; ngược lại, có xu hướng trải rộng hơn vào các ô thưa hoặc ven đô/nông thôn. Điều này gợi ý tai nạn nghiêm trọng thường xuất hiện ở đoạn đường tốc độ cao, ít giao cắt, và khoảng cách cứu hộ dài hơn, nên hậu quả nặng hơn mặc dù tần suất không lớn.

Kinh tế – hạ tầng: Vùng màu đậm trùng với trung tâm kinh tế – mật độ dân cư cao – mạng lưới cao tốc dày. Số vụ nhiều kéo theo chi phí xã hội lớn (ùn tắc, thời gian chết, chi phí bảo hiểm, cứu hộ). Vì vậy các đô thị lớn cần ưu tiên quản lý giao thông đô thị: tổ chức nút giao, kiểm soát tốc độ, camera phạt nguội, làn riêng BRT, v.v.

An toàn ngoài đô thị: Mặc dù đếm tuyệt đối ở nông thôn thấp hơn, nhưng mức độ nghiêm trọng (3–4) xuất hiện không ít—hàm ý rủi ro trên mỗi km di chuyển có thể cao. Chính sách nên tập trung hành lang tốc độ cao: dải phân cách cứng, rào chống vượt, điểm dừng khẩn cấp, chiếu sáng, cảnh báo sương mù/gió ngang, thời gian tiếp cận y tế.

Quy hoạch và nhắm mục tiêu can thiệp:

Đô thị: giảm xung đột tại giao lộ (đèn thông minh, vòng xuyến), kiểm soát giờ cao điểm, ưu tiên phương tiện công cộng.

Ngoại đô/nông thôn: cảnh báo tốc độ, chống buồn ngủ (rumble strips), tăng biển báo nguy cơ ở đoạn cong, cầu, khu vực sương mù.

Truyền thông & bảo hiểm: Do Severity 1–2 tập trung ở đô thị, chiến dịch an toàn va chạm tốc độ thấp (giữ khoảng cách, không dùng điện thoại) hiệu quả nhất ở đây; trong khi đai an toàn, mệt mỏi khi lái xa, kiểm soát tốc độ là thông điệp trọng tâm cho hành trình đường dài.

1.4.1.2 Mật độ tai nạn theo toạ độ (Hexbin)

ggplot(Acc_raw, aes(Start_Lng, Start_Lat)) +
  geom_hex() +
  scale_fill_viridis_c() +
  labs(title="Mật độ tai nạn theo toạ độ (hexbin)") +
  theme_minimal()

ggplot(): Khởi tạo đồ thị với dữ liệu gốc. Trục x là kinh độ nơi xảy ra tai nạn, trục y là vĩ độ.

geom_hex(): Vẽ bản đồ mật độ theo lưới lục giác (hexbin). Không vẽ từng điểm (sẽ chồng đè), mà gom các điểm vào các tổ lục giác; mỗi lục giác hiển thị số vụ tai nạn rơi vào đó. Hexbin có ưu thế so với ô vuông vì mỗi ô có 6 láng giềng đều nhau → hiển thị mượt, ít “tọa độ giả”.

scale_fill_viridis_c(): Thang màu liên tục “viridis” (thân thiện người mù màu). Màu càng sáng/đậm = số vụ trong ô lục giác càng nhiều.

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

theme_minimal(): Giao diện tối giản, tập trung vào dữ liệu.

Cụm nóng (hotspots) rõ ràng dọc các siêu đô thị & hành lang giao thông lớn:

Bờ Đông (khoảng kinh độ –80 → –70): vùng NY/NJ/Philly/DC sáng mạnh; thêm cụm Florida (–80, ~26) và vùng Boston–NY.

Bờ Tây/California Nam (khoảng –118, ~34): LA–Orange County nổi bật.

Texas – Gulf & I-10/I-35 (khoảng –95 → –100, ~30–33): thấy điểm sáng (Houston, San Antonio, Dallas).

Một số cụm nội địa khác như Atlanta (~–84, 33) cũng xuất hiện.

Hình dạng tổng thể khớp biên giới Mỹ lục địa (vĩ độ ~25–50, kinh độ ~–125 đến –67), chứng tỏ dữ liệu tọa độ hợp lý, không có lệch hệ quy chiếu.

Hiệu ứng đô thị hóa: lục giác sáng tập trung nơi mật độ dân cư cao + mạng lưới cao tốc dày → tần suất va chạm cao do lưu lượng lớn, nhiều nút giao, xung đột dòng xe. Vùng loãng (High Plains, Rocky Mountains, sa mạc) có màu tối: ít dân, ít xe → ít vụ, phù hợp kỳ vọng.

Ý nghĩa kinh tế - xã hội:

Ưu tiên chính sách theo vùng

Đô thị lớn (NYC, LA, Houston…): cần giải pháp giảm số vụ (quản lý tốc độ trong phố, tổ chức nút giao, làn dành riêng, ITS, camera vi phạm). Chi phí xã hội ở đây rất lớn (ùn tắc, thời gian, bảo hiểm, y tế).

Hành lang cao tốc liên bang: dù đếm tuyệt đối có thể thấp hơn trung tâm đô thị, nhưng mức độ nghiêm trọng thường cao hơn → đầu tư dải phân cách, chiếu sáng, làn khẩn cấp, biển cảnh báo thời tiết, rút ngắn thời gian phản ứng y tế.

Hàm ý cho bảo hiểm & truyền thông

Ở đô thị: nhấn mạnh giữ khoảng cách, không xao lãng, tuân thủ giới hạn tốc độ thấp.

Ở đường dài: nhấn đai an toàn, chống buồn ngủ, kiểm soát tốc độ, điều kiện tầm nhìn (sương mù, mưa bão).

1.4.1.3 Toạ độ tai nạn theo ca và tầm nhìn

ggplot(Acc_raw, aes(`Start_Lng`, `Start_Lat`,
                      color=time_period, size=`Visibility(mi)`)) +
  geom_point(alpha=.04) +                                    
  facet_wrap(~ time_period) +                                
  scale_color_manual(values=pal_many, name="Ca") +           
  scale_size_area(max_size=2, name="Tầm nhìn (mi)") +        
  coord_quickmap(xlim=c(-125,-70), ylim=c(25,50)) +          
  labs(title="Toạ độ tai nạn theo ca & tầm nhìn", x="Kinh độ", y="Vĩ độ") +
  theme_minimal()

ggplot(): Khởi tạo biểu đồ với dữ liệu Acc_raw. Trục X là kinh độ, trục Y là vĩ độ. Mỗi điểm là một vụ tai nạn: màu cho biết ca trong ngày (Đêm/Sáng/Chiều/Tối), kích thước điểm tỉ lệ với tầm nhìn (mile) tại thời điểm xảy ra.

geom_point(alpha = .04): Vẽ điểm phân tán; alpha = 0.04 làm điểm trong suốt để giảm chồng lấp vì dữ liệu rất lớn ⇒ vùng dày điểm sẽ đậm hơn (ý niệm “mật độ”).

facet_wrap(~ time_period): Chia thành 4 ô theo từng ca trong ngày (Đêm, Sáng, Chiều, Tối) để so sánh trực quan.

scale_color_manual(): Ấn định bảng màu tùy chỉnh cho các ca

scale_size_area(): Ánh xạ tầm nhìn sang diện tích điểm (trực quan đúng hơn so với bán kính); giới hạn kích thước tối đa để tránh che khuất.

coord_quickmap():Cố định khung toạ độ CONUS (Mỹ lục địa), tỉ lệ trục gần đúng bản đồ nên hình dáng nước Mỹ không bị méo.

labs(…) + theme_minimal(): Thêm tiêu đề, nhãn trục; dùng theme tối giản để nổi bật dữ liệu.

Nhận xét:

Các “vệt” điểm đậm tập trung tại bờ Đông Bắc (hành lang Boston–NY–NJ–DC), Florida, Texas (Houston–Dallas–San Antonio), và California (LA–SF). Đây là các cụm dân cư – giao thông dày đặc, hạ tầng đường cao tốc nhiều, lưu lượng lớn ⇒ rủi ro tai nạn cao hơn.

Vùng trung tâm nội địa thưa dân (Dakotas, Montana, Wyoming) có mật độ điểm mỏng.

So sánh theo ca:

  • Chiều: mật độ điểm đậm nhất ở hầu hết khu đô thị. Điều này phù hợp thống kê tần suất theo ca (ca Chiều chiếm tỷ lệ lớn nhất): giờ tan tầm, giao thông dồn, mệt mỏi sau ngày làm việc, nhiều điểm giao cắt ⇒ rủi ro tăng.

  • Sáng: mật độ cũng cao nhưng thấp hơn Chiều; chịu ảnh hưởng rush hour buổi sáng, mặt trời ngược hướng lái ở phía Đông vào đầu ngày.

  • Đêm: tổng số vụ ít hơn, nhưng vẫn thấy “chuỗi” điểm tại các hành lang cao tốc (I-95 dọc bờ Đông, I-10/I-40, SoCal). Đêm có điều kiện ánh sáng kém, phản xạ mệt mỏi/thiếu ngủ ⇒ mức độ nghiêm trọng có thể cao hơn dù số vụ thấp hơn.

  • Tối: phân bố tương tự Đêm, tập trung quanh đô thị và vành đai ngoại ô – thời điểm hoạt động giải trí, di chuyển cá nhân tăng.

Vai trò tầm nhìn (kích thước điểm):

Trong điều kiện đô thị ven biển ẩm (Florida, Gulf Coast) hay địa hình sương mù (vịnh SF, thung lũng), xuất hiện nhiều điểm nhỏ (tầm nhìn kém). Ngược lại, cao nguyên nội địa khô ráo thường điểm lớn (tầm nhìn xa).

Ý nghĩa kinh tế:

  • Cần ưu tiên đầu tư an toàn giao thông cho các điểm nóng đô thị và hành lang cao tốc liên bang (I-95, I-10, I-5…). Biện pháp: mở rộng làn tách dòng, cải tiến nút giao, quản lý tốc độ linh hoạt theo thời tiết/giờ cao điểm.

  • Quản lý theo thời gian: tăng tuần tra, tín hiệu đèn “green wave”, thông tin VMS trong giờ cao điểm chiều; chương trình truyền thông “đừng lái xe khi mệt mỏi/thiếu ngủ” cho đêm/tối.

  • Theo thời tiết: cảnh báo sương mù tự động, cảm biến tầm nhìn, hạn tốc độ động ở khu vực bờ biển và thung lũng thường mù sương; khuyến khích đèn chạy ngày (DRL), kiểm tra lốp/phanh mùa mưa.

1.4.1.4 Toạ độ theo ca trong ngày (contours mật độ 2D)

ggplot(Acc_raw, aes(Start_Lng, Start_Lat, color=time_period)) +
  geom_point(alpha=.08, size=.5) +
  stat_density_2d(aes(linetype=time_period), color="black", bins=5) +
  scale_color_manual(values=pal_many) +
  labs(title="Toạ độ theo ca trong ngày (contours)") +
  theme_minimal()

ggplot(): Khởi tạo biểu đồ tán xạ toạ độ tai nạn: trục X = kinh độ, trục Y = vĩ độ. Mỗi điểm là 1 vụ tai nạn; màu điểm thể hiện ca trong ngày (Đêm/Sáng/Chiều/Tối).

geom_point(): Vẽ hàng triệu điểm với độ trong suốt 0.08 để giảm chồng lấp; size .5 cho điểm nhỏ, giúp nhìn rõ “vệt” giao thông.

stat_density_2d(): Tính mật độ nhân (2D KDE) theo toạ độ và vẽ đường đồng mức (contour) màu đen. Thuộc tính linetype = time_period cho phép mỗi ca có kiểu nét khác nhau (liền, chấm, gạch…), nên ta thấy vùng nóng của từng ca. bins = 5 chia ra 5 “vành” mật độ (từ cao đến thấp).

scale_color_manual(): Gán bảng màu tùy chỉnh cho các ca, đồng bộ với những biểu đồ khác.

labs(…) + theme_minimal(): Tiêu đề, nhãn trục, và giao diện tối giản để làm nổi bật dữ liệu.

Nhận xét:

  • Vùng nóng không gian rõ rệt: Các đường đồng mức (contour) “bó” lại quanh các đô thị và hành lang cao tốc quen thuộc:

  • Bờ Đông Bắc (hành lang Boston–NYC–NJ–Philadelphia–DC)

  • Florida (Miami khu Nam Florida),

  • Texas (Houston–Dallas),

  • California (Nam California/LA & vùng Vịnh San Francisco),

  • vệt dọc I-95, I-5, và các đoạn I-10/I-40.

=> Đây là nơi mật độ giao thông cao, nút giao phức tạp, nên tần suất va chạm dày đặc là điều phù hợp thống kê.

  • Khác biệt theo ca trong ngày:

  • Các nét đồng mức chồng lên nhau ở những điểm nóng cho thấy ca Chiều (rush hour tan tầm) thường có đường bao rộng/đậm hơn (mật độ cao hơn), tiếp đến là Sáng.

  • Đêm/Tối có vùng nóng nhỏ hơn về diện tích (ít vụ hơn), nhưng vẫn “ôm” sát các hành lang cao tốc và đô thị – phù hợp với rủi ro do ánh sáng kém, mệt mỏi, khả năng mức độ nghiêm trọng cao hơn dù tần suất thấp hơn.

Ý nghĩa kinh tế:

  • Quy hoạch hạ tầng theo không gian: ưu tiên cải thiện điểm nóng (nút giao phức tạp, lối ra vào cao tốc, đoạn hay ùn tắc). Biện pháp: làn tách dòng, tín hiệu thông minh, dải phân cách, tăng chiếu sáng.

  • Quản lý theo thời gian: tăng tuần tra/điều tiết giờ cao điểm chiều và sáng; biển VMS cảnh báo thời gian thực, hạn tốc động khi tắc đường.

  • An toàn ban đêm: bổ sung đèn đường, sơn/phản quang, kiểm soát lái xe mệt mỏi/thiếu ngủ ở cao tốc ngoại ô .

  • Truyền thông & bảo hiểm: định giá rủi ro theo khu vực–khung giờ, nhắm đúng nhóm commuters.

1.4.1.5 Bản đồ điểm nóng theo bang (Top 12 State)

top_states <- names(sort(table(Acc_raw$State),decreasing=TRUE))[1:12]
ggplot(subset(Acc_raw, State %in% top_states),
       aes(Start_Lng, Start_Lat)) +
  stat_bin2d(bins=60) +
  scale_fill_viridis_c() +
  facet_wrap(~State) +
  labs(title="Bản đồ điểm nóng theo bang (top 12)") +
  theme_minimal()

top_states <- names()[1:12]: Đếm số vụ theo bang, sắp xếp giảm dần, chọn 12 bang nhiều vụ nhất (tên viết tắt bang).

subset(): Lọc dữ liệu chỉ còn các bang top-12 để vẽ.

ggplot(): Dùng kinh độ (Start_Lng) và vĩ độ (Start_Lat) làm trục X–Y.

stat_bin2d(bins = 60): Chia bản đồ thành lưới 60×60 ô, đếm số vụ trong mỗi ô; ô càng sáng/màu càng đậm ⇒ mật độ tai nạn càng cao.

scale_fill_viridis_c(): Thang màu “viridis” trực quan, liên tục theo số vụ.

facet_wrap(~ State): Tạo small multiples: mỗi bang một ô đồ thị để so sánh trực quan.

labs(…); theme_minimal(): Thêm tiêu đề/nhãn và dùng theme tối giản.

**Nhận xét:

Các “vệt sáng” tập trung rõ ở đô thị lớn và dọc các trục cao tốc liên bang:

  • CA: quầng đậm quanh Nam California (Los Angeles–Orange County–Inland Empire) và Bay Area.

  • FL: Miami–Fort Lauderdale, Orlando, Tampa–St. Pete.

  • TX: Houston, Dallas–Fort Worth, dải Austin–San Antonio.

  • NY/NJ/PA/VA: cụm đậm quanh vùng đô thị NYC và hành lang I-95 (Philly, Northern Virginia).

  • AZ: Phoenix; MN: Twin Cities; NC/SC/TN:Charlotte/Raleigh, Greenville/Charleston, Nashville/Memphis.

=> Điểm nóng trùng với nơi dân cư dày, lưu lượng lớn, mạng cao tốc dày đặc → phản ánh hoạt động kinh tế, đô thị hóa, logistics.

Ý nghĩa kinh tế

Hành lang logistics & du lịch (I-95 bờ Đông, I-10/ I-5 bờ Tây; Orlando/Miami; Houston/Dallas) có mật độ cao → cần ưu tiên quản lý tốc độ, hạ tầng nút giao, thông tin thời tiết/ùn tắc.

  • Khác biệt vùng miền:

  • MN/VA/PA/NJ/NY chịu ảnh hưởng điều kiện mùa đông, đô thị dày → chú trọng bảo dưỡng mùa lạnh, phanh/ABS, lốp mùa đông, dọn tuyết.

  • FL/TX/AZ nắng nóng & mưa bão cục bộ → cần cảnh báo thời tiết, thoát nước, chiếu sáng.

Ứng dụng: ưu tiên phân bổ tuần tra, camera, cải tạo điểm đen, chiến dịch an toàn theo ô lưới sáng của từng bang.

1.4.1.6 Top City trong các bang dẫn đầu (Bubble theo ca trong ngày)

keep_states <- top_states[1:9] 
Acc_raw %>% filter(State %in% keep_states) %>%
  count(State, City, time_period, name="n") %>%
  group_by(State, City) %>% mutate(tot=sum(n)) %>% ungroup() %>%
  group_by(State) %>% slice_max(tot, n= 20, with_ties=FALSE) %>%
  mutate(City = forcats::fct_reorder(City, tot)) %>%
  ggplot(aes(n, City, size=n, color=time_period)) +
  geom_point(alpha=.9) + facet_wrap(~State, scales="free_y") +
  scale_size_area(max_size=20) + labs(title="Top 20 city trong 9 bang đầu", x="Số vụ", y=NULL) +
  theme_minimal(11)

keep_states <- top_states[1:9]: Lấy 9 bang đứng đầu từ danh sách 12 bang nhiều tai nạn.

Acc_raw %>% filter(State %in% keep_states) %>%: Lọc dữ liệu còn 9 bang này.

count() %>%: Đếm số vụ theo (bang, thành phố, ca trong ngày); lưu vào cột n.

group_by(State, City) %>% mutate(tot = sum(n)) %>% ungroup(): Tính tổng số vụ của mỗi City (cộng 4 ca trong ngày).

group_by(State) %>% slice_max(tot, n = 20, with_ties = FALSE) %>%: Với mỗi bang, chọn 20 city có tổng số vụ cao nhất (không giữ đồng hạng).

mutate(): Reorder trục Y theo tổng vụ để sắp xếp bubble tăng dần.

ggplot(): Vẽ bubble chart: trục X = số vụ (n) theo ca, trục Y = City; kích thước theo n, màu theo time_period (Đêm/Sáng/Chiều/Tối).

facet_wrap(): Mỗi bang một khung; free_y cho phép danh sách thành phố khác nhau.

scale_size_area(max_size = 20): Tỷ lệ hóa diện tích bọt (tránh phình to quá mức).

labs(…) + theme_minimal(11): Tiêu đề, nhãn, theme.

Nhận xét:

Trong mỗi bang, vài city chiếm phần lớn số vụ (quy luật 80/20):

  • FL: bọt cực lớn ở Miami và Orlando → hai trung tâm du lịch/giải trí & giao thông cao tốc dày đặc.

  • TX: Houston/Dallas/Austin nổi trội – hành lang đô thị I-45/I-35; vận tải nội bang lớn.

  • CA: Los Angeles dẫn đầu rõ rệt, sau đó là Sacramento/San Diego.

  • NC/VA/PA/NY/NJ/SC: các thành phố thủ phủ/đô thị lớn (Charlotte, Raleigh; Richmond, Norfolk; Philadelphia, Pittsburgh; New York, Bronx; Newark; Columbia/Greenville) đều nổi lên.

Về thời điểm trong ngày, màu sắc bọt cho thấy “Chiều” và “Sáng” chiếm ưu thế ở hầu hết city – phù hợp quy luật giờ cao điểm đi làm/ tan tầm. Một số city du lịch (Miami, Orlando) vẫn có bọt lớn ở Tối, phản ánh hoạt động đêm và giao thông phục vụ dịch vụ – giải trí.

Ý nghĩa kinh tế

Tập trung can thiệp theo địa điểm (các city/bán kính đô thị nổi trội) và theo thời gian (Sáng/Chiều) → điều tiết tín hiệu đèn, làn ưu tiên, kiểm soát tốc độ, thông tin thời gian thực.

Ở city du lịch, tăng tuần tra buổi tối, quản lý bãi đỗ & điều hướng lưu lượng gần khu vui chơi.

Tại các hành lang logistics (TX, CA), xem xét cửa sổ giờ giao hàng để giảm chồng lấn với giờ cao điểm dân sinh.

1.4.2 Thời gian

1.4.2.1 Phân bố số vụ tai nạn theo giờ và ca trong ngày

dat <- Acc_raw %>% count(time_period, Hour, name="n")
ggplot(dat, aes(Hour, n)) +
  geom_col(fill="#6b6ecf", color="white", width=.9) +
  geom_text(aes(label=scales::comma(n)), vjust=-.3, size=3) +
  facet_wrap(~time_period) +
  scale_x_continuous(breaks=0:23) +
  labs(title="Phân bố số vụ tai nạn theo giờ và ca trong ngày", x="Giờ", y="Số vụ") +
  theme_minimal()

dat <- Acc_raw %>% count(time_period, Hour, name=“n”): Tạo bảng tóm tắt dat bằng cách đếm số vụ tai nạn (n) theo hai biến: ca trong ngày (time_period: Đêm–Sáng–Chiều–Tối) và giờ (Hour, 0–23).

ggplot(dat, aes(Hour, n)): Khởi tạo ggplot, trục x là giờ, trục y là số vụ (n).

geom_col(): Vẽ cột (bar chart) chiều cao theo n, tô màu tím nhạt, viền trắng, cột rộng 90% ô.

geom_text(): Ghi nhãn số vụ lên đầu mỗi cột, giúp đọc nhanh giá trị (đã định dạng có dấu phẩy).

facet_wrap(~time_period): Chia thành 4 ô nhỏ cho 4 ca (Đêm/Sáng/Chiều/Tối) để so sánh song song.

scale_x_continuous(breaks=0:23): Hiển thị đầy đủ nhãn giờ từ 0 đến 23.

labs(“) + theme_minimal(): Đặt tiêu đề, nhãn trục và dùng giao diện tối giản.

Nhận xét

  • Sáng (khoảng 6–10h): Cột cao nhất trong khung Sáng, đỉnh ở 6–8h rồi giảm dần đến 10h.

Diễn giải: Đây là giờ cao điểm đi làm/đi học. Lưu lượng phương tiện lớn, mật độ giao cắt cao, tâm lý vội vã → xác suất va chạm tăng. Chính sách có thể nhắm vào quản lý giao thông giờ cao điểm (tối ưu đèn tín hiệu, làn ưu tiên bus, khuyến khích làm việc linh hoạt/remote).

  • Chiều (khoảng 13–18h): Nhóm Chiều cũng rất cao, thường đỉnh 15–17h (tan ca/tan học).

Diễn giải: Lưu lượng lớn + mệt mỏi cuối ngày, thời tiết buổi chiều có thể xuất hiện mưa giông cục bộ → rủi ro tăng. Gợi ý: giãn giờ tan tầm, cảnh báo mưa giông, tăng lực lượng điều tiết tại nút nóng.

  • Tối (19–22h): Mức trung bình, thấp hơn sáng/chiều nhưng vẫn đáng kể 19–21h.

Diễn giải: Di chuyển giải trí, mua sắm, giao đồ ăn; ánh sáng giảm → tầm nhìn, tương phản người đi bộ kém. Giải pháp: nâng cấp chiếu sáng, sơn/phản quang vạch qua đường, kiểm soát tốc độ khu dân cư.

  • Đêm (0–5h): Mức thấp nhất nhưng có nhú nhẹ ở 5–6h (cận giờ cao điểm sáng).

Diễn giải: Lưu lượng ít nhưng rủi ro nặng hơn nếu xảy ra (tốc độ cao, buồn ngủ, rượu bia). Nên tăng tuần tra, kiểm soát nồng độ cồn, điểm nghỉ tài xế đường dài.

=> Hai “đỉnh” rõ rệt vào giờ cao điểm sáng và chiều là đặc trưng nổi bật của tai nạn giao thông đô thị.

=> Yếu tố hành vi & hạ tầng (vội vã, mệt mỏi, chiếu sáng, tín hiệu giao thông) giải thích tốt mô hình quan sát.

Hàm ý chính sách: (1) Quản trị lưu lượng giờ cao điểm; (2) can thiệp chiếu sáng/tốc độ buổi tối; (3) truyền thông an toàn theo khung giờ; (4) áp dụng làm việc linh hoạt để “cắt đỉnh” giao thông.

1.4.2.2 Xu hướng theo ngày và ca

daily <- Acc_raw %>% count(Date, time_period, name = "n")
ggplot(daily, aes(Date, n, color=time_period, group=time_period)) +
  geom_line(alpha=.4) +
  geom_smooth(se=FALSE, method="gam", formula = y ~ s(x, k = 20)) +
  scale_color_manual(values=pal_many) +
  labs(title="Số vụ theo ngày & ca trong ngày", x="Ngày", y="Số vụ") +
  theme_minimal()

daily <- Acc_raw %>% count(): Gom số liệu theo ngày (Date) và ca trong ngày (time_period: Đêm–Sáng–Chiều–Tối), đếm số vụ vào cột n.

ggplot(): Tạo khung vẽ, trục x là ngày, trục y là số vụ, tô màu theo ca.

geom_line(alpha=.4): Vẽ đường thời gian hằng ngày cho từng ca; alpha=.4 làm đường mờ bớt để giảm nhiễu.

geom_smooth(): Thêm đường xu thế trơn riêng cho từng ca dùng GAM (spline mượt s(x)), k=20 nút giúp nắm xu hướng/chu kỳ mà không bám sát nhiễu ngày-ngày; tắt dải sai số (se=FALSE) để đồ thị thoáng.

scale_color_manual(values=pal_many): Dùng bảng màu tùy chỉnh cho các ca.

labs(…) + theme_minimal(): Đặt tiêu đề – nhãn trục và dùng theme tối giản.

Nhận xét

  • Chiều (màu xanh lá) luôn cao nhất suốt năm; Sáng đứng thứ hai; Tối và Đêm thấp hơn đáng kể.

→ Trùng khớp phân tích theo giờ: giờ cao điểm sáng/chiều là động lực chính của tai nạn.

  • Tính mùa vụ trong năm: Cả Sáng và Chiều đều tăng mạnh vào cuối đông-xuân (Q1–Q2), đạt đỉnh cỡ tháng 3–5, sau đó giảm về hè (Q3), rồi nhích lại Q4.

→ Hợp lý với bối cảnh năm học/nhịp lao động (mùa xuân lưu lượng đậm đặc; hè có nghỉ học, du lịch phân tán; Q4 cuối năm hoạt động thương mại tăng).

→ Thời tiết chuyển mùa (mưa xuân, sương mù cục bộ) có thể góp phần làm rủi ro tăng.

  • Biến động ngày-ngày (đường mảnh): Dao động lớn quanh xu thế, đặc biệt ở Chiều — phản ánh dịp cuối tuần/lễ hoặc sự kiện thời tiết; một số rãnh giảm sâu (ngày lễ lớn/giảm lưu lượng) và cụm đỉnh (mưa bão khu vực).

→ Quản trị an toàn nên kết hợp dự báo thời tiết và lịch sự kiện/du lịch để phân bổ lực lượng.

Hàm ý chính sách – kinh tế – xã hội:

Nhắm đúng mùa & ca: tăng tuần tra/điều tiết và truyền thông vào Q1–Q2, đặc biệt 15–18h; các chiến dịch an toàn đi làm/đi học vào đầu năm hiệu quả hơn.

Quản lý giờ cao điểm: tối ưu pha đèn, làn riêng buýt/BRT, khuyến khích làm việc linh hoạt/giờ lệch ca nhằm “cắt đỉnh” lưu lượng → giảm trễ giờ, giảm chi phí xã hội (thời gian, nhiên liệu, ô nhiễm).

Hạ tầng “thích ứng mùa vụ”: tăng biển cảnh báo trơn trượt/sương mù, bảo trì chiếu sáng & vạch sơn trước mùa mưa xuân; phối hợp giáo dục an toàn với trường học.

Đêm/Tối tuy thấp về số lượng nhưng rủi ro nặng khi sự cố xảy ra (tốc độ cao, mệt mỏi): ưu tiên chiếu sáng, kiểm soát nồng độ cồn, điểm nghỉ tài xế đường dài.

Tóm lại, đồ thị cho thấy xu hướng theo thời gian và mùa vụ rõ ràng, trong đó ca Chiều là “điểm nóng” ổn định. Điều này gợi ý các biện pháp điều hành giao thông theo mùa & theo ca sẽ đem lại hiệu quả giảm tai nạn/giảm chi phí xã hội tốt hơn so với các biện pháp dàn trải.

1.4.2.3 Tỷ trọng ca trong ngày theo thứ (stacked = “fill”)

ggplot(Acc_raw, aes(Day, fill=time_period)) +
  geom_bar(position="fill") +
  scale_y_continuous(labels=scales::percent) +
  scale_fill_manual(values=pal_many) +
  labs(title="Tỷ trọng ca trong ngày theo thứ", y="Tỷ trọng") +
  theme_minimal()

ggplot(): tạo khung vẽ; trục x là thứ trong tuần, màu (fill) là ca (Đêm–Sáng–Chiều–Tối).

geom_bar(): vẽ cột chồng 100% ⇒ mỗi cột cao đúng 1 (100%), phần màu thể hiện tỷ trọng từng ca trong tổng số vụ của ngày đó.

scale_y_continuous(): hiển thị trục y dưới dạng %.

scale_fill_manual(values = pal_many): dùng bảng màu tùy chỉnh cho các ca.

labs(…); theme_minimal(): đặt tiêu đề, nhãn trục và theme tối giản.

Nhận xét:

Tất cả các ngày, Chiều (màu xanh lá) luôn chiếm tỷ trọng lớn nhất (~40–45%), Sáng (cam) kế tiếp (~30–35%), Tối (đỏ) và Đêm (xanh dương) thấp hơn (~10–15% mỗi nhóm).Đây là hình ảnh “giờ cao điểm” đi làm/đi học, về nhà.

  • Ngày trong tuần (Thứ Hai–Thứ Sáu): cơ cấu khá ổn định; Chiều trội hơn Sáng rõ rệt → tai nạn tập trung khi tan ca/tan học (lưu lượng dày, mệt mỏi sau ngày làm việc, ánh sáng yếu dần).

-> ưu tiên điều tiết giao thông, tăng tần suất phương tiện công cộng, tối ưu pha đèn vào 16–19h các ngày làm việc.

  • Thứ Sáu: tỷ trọng Tối có xu hướng cao hơn các ngày thường → bắt đầu cuối tuần, nhu cầu vui chơi/di chuyển buổi tối tăng.

-> Hàm ý: kiểm soát nồng độ cồn, tăng tuần tra và chiếu sáng các trục chính.

  • Cuối tuần (Thứ Bảy & Chủ Nhật):

  • Chủ Nhật: Tối tăng rõ (≈20%) và Sáng giảm; Đêm cũng nhỉnh hơn. Phản ánh lịch sinh hoạt muộn và nhu cầu di chuyển cuối ngày (quay về sau du lịch/thăm thân).

  • Thứ Bảy: cơ cấu trung gian – Sáng hơi giảm, Tối/Đêm nhích lên so với ngày thường.

-> điều chỉnh lực lượng theo ca vào cuối tuần, truyền thông “lái xe an toàn ban đêm”, chiếu sáng & biển cảnh báo ở khu vui chơi/ăn uống.

Biểu đồ cho thấy cơ cấu rủi ro theo ca thay đổi theo thứ, nhưng “điểm nóng” nhất vẫn là ca Chiều, đặc biệt ngày làm việc và tối cuối tuần—nơi các can thiệp sẽ mang lại hiệu quả lớn nhất. Tập trung nguồn lực đúng ca/đúng ngày giúp giảm ùn tắc & tai nạn với chi phí thấp hơn so với triển khai dàn trải; doanh nghiệp vận tải có thể dịch chuyển giờ giao–nhận tránh ca Chiều; thành phố cân nhắc giờ làm linh hoạt để “cắt đỉnh” nhu cầu.

1.4.2.4 Phân bố Severity theo giờ & ca (violin + boxplot mỏng)

library(lubridate)
# Tạo Hour & ca (time_period), rồi tính trung bình Severity theo giờ & ca
plot10 <- Accidents %>%
  mutate(
    Hour = hour(Start_Time),
    time_period = case_when(
      Hour %in% 0:5   ~ "Đêm",
      Hour %in% 6:11  ~ "Sáng",
      Hour %in% 12:17 ~ "Chiều",
      TRUE            ~ "Tối")) %>%
  group_by(time_period, Hour) %>%
  summarise(
    mean_sev = mean(Severity, na.rm = TRUE),
    n        = n(),
    se       = sd(Severity, na.rm = TRUE) / sqrt(n),
    .groups  = "drop") 
peaks <- plot10 %>% group_by(time_period) %>%
  slice_max(mean_sev, n = 1, with_ties = FALSE)

ggplot(plot10, aes(Hour, mean_sev, color = time_period, fill = time_period)) +
  geom_ribbon(aes(ymin = mean_sev - 1.96*se, ymax = mean_sev + 1.96*se),
              alpha = .2, color = NA) +
  geom_line(linewidth = 1) +
  geom_point(size = 1.6) +
  geom_text(data = peaks,
            aes(label = paste0("Max: ", round(mean_sev, 3))),
            vjust = -0.6, show.legend = FALSE) +
  scale_x_continuous(breaks = seq(0, 23, 2)) +
  labs(title = "Mức độ nghiêm trọng trung bình theo giờ & ca",
       x = "Giờ", y = "Severity (trung bình)") +
  theme_minimal()

library(lubridate): Nạp gói để trích xuất thành phần thời gian (giờ, phút…) từ cột datetime.

Hour = hour(Start_Time): lấy giờ (0–23) từ thời điểm bắt đầu tai nạn.

time_period = case_when(…): gán ca làm việc theo giờ: 0–5 = Đêm, 6–11 = Sáng, 12–17 = Chiều, 18–23 = Tối.

group_by() %>% summarise(…): gom theo cặp (ca, giờ) và tính thống kê:

mean_sev = mean(…): mức độ nghiêm trọng trung bình ở giờ đó trong mỗi ca.

n = n(): số vụ quan sát.

se = sd(…)/sqrt(n): sai số chuẩn của trung bình để vẽ dải tin cậy.

slice_max(…): tìm điểm cực đại (giờ có mean_sev cao nhất) trong từng ca → dùng để gắn nhãn “Max: …” trên đồ thị.

ggplot(…): trục X là giờ, trục Y là Severity trung bình; màu theo ca.

geom_ribbon(…): vẽ dải tin cậy ~95% quanh đường trung bình (±1.96 * SE).

geom_line(…) + geom_point(…): đường xu hướng và điểm theo giờ.

geom_text(…): gắn nhãn tại giờ có Severity trung bình cao nhất mỗi ca.

scale_x_continuous(breaks = seq(0, 23, 2)): mốc giờ cách 2 tiếng.

labs(…) + theme_minimal(): nhãn và giao diện tối giản.

Nhận xét:

Severity trung bình dao động trong biên hẹp ~2.02–2.12 (thang 1–4), nhưng có mẫu hình theo thời gian trong ngày rất rõ.

  • Đêm (0–5h): Đường màu xanh lá đậm nằm cao, đỉnh ~2.117 vào khoảng 2–3h. Dải tin cậy hẹp, cho thấy xu hướng ổn định . -> khung giờ ít xe nhưng tốc độ cao, mệt mỏi/buồn ngủ, khả năng liên quan rượu bia → khi đã xảy ra tai nạn thì nặng hơn.

  • Sáng (6–11h): Mức thấp nhất toàn ngày, đặc biệt 7–9h xuống ~2.025–2.035; sau 9–11h nhích lên.

-> giờ cao điểm sáng đông xe, nhiều va chạm nhẹ tốc độ thấp (đuôi xe, đổi làn) → nhiều vụ nhưng ít nghiêm trọng.

  • Chiều (12–17h): Bắt đầu cao nhẹ vào ~11–12h (đỉnh ~2.089) rồi giảm dần đến 16–17h ~2.056.

-> giữa trưa nắng nóng, mệt mỏi làm tăng mức nghiêm trọng; về chiều mật độ đông lên nên tốc độ giảm → mức độ nặng giảm.

  • Tối (18–23h): Tăng lại rất rõ sau 19h, đỉnh ~2.118 vào khoảng 21–22h (cao nhất trong ngày, tương đương ca đêm đầu sáng).

-> trời tối, tầm nhìn kém, mệt mỏi cuối ngày/khuya, giải trí rượu bia → khi xảy ra va chạm thường nặng hơn.

Hàm ý quản lý & kinh tế – xã hội:

An toàn giao thông: tăng tuần tra & kiểm soát tốc độ/nồng độ cồn ban đêm và tối muộn; cải thiện chiếu sáng & biển báo trên các tuyến nguy cơ.

Y tế khẩn cấp: bố trí lực lượng/xe cứu thương dày hơn vào tối muộn–rạng sáng, khi nguy cơ chấn thương nặng cao hơn.

Quản lý đô thị: sáng/chiều là giờ “nhiều vụ nhưng nhẹ” → tối ưu tổ chức giao thông (làn riêng, đèn tín hiệu thông minh) để giảm tần suất.

Truyền thông: chiến dịch “đã uống rượu bia không lái xe”, cảnh báo mệt mỏi/thiếu ngủ cho tài xế ca đêm.

Tóm lại, đồ thị chỉ ra mức độ nghiêm trọng cao nhất vào buổi tối muộn và rạng sáng, thấp nhất giờ cao điểm sáng. Điều này phù hợp cơ chế rủi ro theo tốc độ – ánh sáng – trạng thái lái xe, và gợi ý các biện pháp can thiệp theo khung giờ để giảm thiểu thiệt hại.

1.4.2.5 Mật độ Severity theo ca trong ngày (density)

ggplot(Acc_raw, aes(Severity, fill=time_period)) +
  geom_density(alpha=.35) +
  scale_fill_manual(values=pal_many) +
  labs(title="Mật độ Severity theo ca trong ngày") +
  theme_minimal()

ggplot(): tạo khung vẽ từ dữ liệu, trục X là Severity (mức độ nghiêm trọng 1–4), tô màu theo biến time_period (Đêm/Sáng/Chiều/Tối).

geom_density(alpha = .35): vẽ đường mật độ xác suất ước lượng bằng KDE cho mỗi ca; alpha=.35 làm vùng tô trong suốt 35% để nhìn chồng lấp.

scale_fill_manual(): dùng bảng màu tùy chỉnh đã khai báo trước.

labs(…), theme_minimal():* đặt tiêu đề và áp giao diện tối giản.

Nhận xét: Cả bốn đường mật độ đều đỉnh rất nhọn tại Severity ≈ 2, nghĩa là đa số vụ tai nạn được ghi nhận ở mức trung bình/nhẹ (mã 2). Có đuôi mỏng quanh Severity = 3 và 4, tỉ lệ nhỏ; Severity = 1 gần như hiếm. - So sánh giữa các ca: + Đêm và Tối có đuôi phải (hướng về 3–4) hơi dày hơn Sáng/Chiều → xác suất gặp vụ nặng cao hơn một chút. + Sáng có mật độ tập trung sát quanh 2 nhất → ít lệch về mức cao, phù hợp bức tranh “nhiều va chạm nhẹ giờ cao điểm”. + Chiều nằm giữa Sáng và Tối: vẫn tập trung ở 2, nhưng hơi dày hơn về phía 3 so với Sáng.

Ý nghĩa kinh tế - xã hội: Phần lớn tai nạn là mức 2, hàm ý gánh nặng kinh tế chủ yếu đến từ tần suất cao (kẹt xe, trễ giờ, hư hỏng nhẹ, chi phí bảo hiểm nhỏ nhưng lặp lại), hơn là từ mỗi vụ đơn lẻ quá nặng. Đêm/Tối có xác suất vụ nặng hơn (đuôi về 3–4 dày hơn): cần ưu tiên chiếu sáng, kiểm soát tốc độ/nồng độ cồn, cảnh báo mệt mỏi; bệnh viện/cấp cứu nên trực dày về đêm vì chi phí y tế/nhân mạng tăng theo mức độ nghiêm trọng. Sáng chủ yếu va chạm nhẹ: chính sách hiệu quả nhất là quản trị lưu lượng (làn riêng, tín hiệu thông minh, tuyên truyền giữ khoảng cách) để giảm số lượng vụ.

Phân phối Severity gần như giống nhau giữa các ca và đỉnh ở mức 2; Đêm/Tối có rủi ro vụ nặng cao hơn chút, còn Sáng tập trung mạnh ở mức nhẹ. Điều này khớp với các phân tích theo giờ & ca trước đó.

1.4.3 Thời tiết và tầm nhìn

1.4.3.1 Nhiệt độ & tầm nhìn theo trạng thái thời tiết (scatter + smooth)

ggplot(Acc_raw, aes(temp_c, `Visibility(mi)`, color=weather_simple)) +
  geom_point(alpha=.15) +
  geom_smooth(se=FALSE) +
  scale_color_manual(values=pal_many) +
  labs(title="Nhiệt độ & tầm nhìn theo trạng thái thời tiết") +
  theme_minimal()
## `geom_smooth()` using method = 'gam' and formula = 'y ~ s(x, bs = "cs")'

ggplot(): dựng đồ thị phân tán tầm nhìn (mi) theo nhiệt độ C; mỗi điểm được tô màu theo nhóm thời tiết* đã rút gọn (weather_simple`: Trời quang, Mưa, Nhiều mây, Sương mù, Tuyết, Khác).

geom_point(alpha = .15): vẽ điểm với độ trong suốt 15% để giảm chồng lấn (dữ liệu rất lớn).

geom_smooth(se = FALSE): vẽ đường xu hướng cho từng nhóm thời tiết (không hiển thị dải tin cậy). Mặc định ggplot dùng GAM cho tập dữ liệu lớn → đường cong mượt mô tả xu thế tổng thể.

scale_color_manual(values = pal_many): dùng bảng màu để các nhóm dễ phân biệt.

labs(…) + theme_minimal(): tiêu đề, trục, chú giải và chủ đề tối giản.

Nhận xét

Trời quang: đường xu hướng nằm cao nhất, tầm nhìn xấp xỉ “trần” ~10 mi gần như mọi mức nhiệt → điều kiện tốt, ít bị ảnh hưởng bởi nhiệt độ.

Nhiều mây: thấp hơn “trời quang” một chút; tầm nhìn vẫn tốt (~8–10 mi). Ảnh hưởng nhiệt độ nhẹ, xu hướng hơi tăng khi ấm hơn.

Mưa: tầm nhìn giảm rõ (~5–8 mi), ít phụ thuộc nhiệt độ; nước mưa và spray xe làm mờ đường.

Sương mù: thấp nhất và ổn định thấp (thường 1–4 mi, nhiều điểm sát 0–2 mi) → rủi ro an toàn giao thông rất cao.

Tuyết: tầm nhìn thấp tương tự mưa/sương mù khi nhiệt độ lạnh. Ở vùng nhiệt cao, đường mượt “Tuyết” xuất hiện dao động bất thường (thậm chí xuống dưới 0 do nội suy) — đây là nghệ thuật của đường GAM khi dữ liệu rất ít ở vùng nhiệt đó → không nên diễn giải phần đuôi này; có thể đặt giới hạn trục y ≥ 0 và/hoặc không vẽ smooth cho nhóm có ít quan sát.

Hàm ý thực tiễn – kinh tế/xã hội

Chính sách an toàn: ưu tiên cảnh báo và giới hạn tốc độ động khi sương mù/mưa/tuyết, vì tầm nhìn sụt mạnh; lắp biển VMS, cảm biến sương mù, hệ thống phun sương cảnh báo ở điểm đen.

Quản lý hạ tầng: cải thiện thoát nước mặt đường (mưa), khử băng/tuyết (tuyết), và đèn chiếu sáng – sơn phản quang ở khu vực hay sương mù.

Vận hành đô thị: tối ưu giờ làm việc linh hoạt/khuyến khích làm từ xa trong ngày có sương mù dày; tăng cường dịch vụ công (cảnh sát giao thông, cứu hộ) vào các thời đoạn dự báo tầm nhìn kém.

1.4.3.2 Tầm nhìn theo trạng thái thời tiết (Top 10, Violin + boxplot)

w10 <- Acc_raw %>%
  count(weather_simple, sort = TRUE) %>%   
  slice_head(n = 10) %>%                   
  pull(weather_simple)                     

ggplot(subset(Acc_raw, weather_simple %in% w10),
       aes(x = reorder(weather_simple, `Visibility(mi)`, median),
           y = `Visibility(mi)`, fill = weather_simple)) +
  geom_violin(trim = TRUE, alpha = .7, color = NA) +
  geom_boxplot(width = .12, outlier.alpha = .05, color = "black") +
  stat_summary(fun = median, geom = "point", size = 2.6, shape = 21, fill = "white") +
  scale_fill_manual(values = pal_many, guide = "none") +
  coord_flip() + coord_cartesian(ylim = c(0, 15)) +
  labs(title = "Tầm nhìn theo trạng thái thời tiết (Top 10)",
       x = NULL, y = "Tầm nhìn (mile)") +
  theme_minimal()
## Coordinate system already present. Adding new coordinate system, which will
## replace the existing one.

w10 <- Acc_raw %>% count(…) %>% slice_head(n = 10) %>% pull(…): Đếm số bản ghi theo weather_simple, sắp xếp giảm dần, lấy 10 nhóm thời tiết xuất hiện nhiều nhất và rút ra thành một vector các tên nhóm.

ggplot(…): Lọc dữ liệu chỉ giữ 10 nhóm thời tiết top; trục X là tên nhóm được sắp theo trung vị của tầm nhìn (nhìn nhanh nhóm nào tầm nhìn thấp/cao). Trục Y là tầm nhìn (mile); tô màu theo nhóm thời tiết.

geom_violin(): Biểu đồ violin để thấy hình dạng phân phối (độ dày = mật độ). trim=TRUE cắt phần đuôi thưa, alpha=.7 trong suốt nhẹ, không viền.

geom_boxplot(): Chồng boxplot mảnh lên violin để thấy tứ phân vị và ngoại lệ. Ngoại lệ vẫn vẽ nhưng rất mờ (.05).

stat_summary(): Đặt dấu chấm vào đúng trung vị mỗi nhóm (chấm trắng ở giữa).

scale_fill_manual():Dùng bảng màu tùy chỉnh pal_many, ẩn chú giải màu (legend) vì nhãn đã có trên trục.

coord_flip(): Đảo trục cho dễ đọc: nhóm thời tiết dọc theo trục Y.

coord_cartesian(ylim = c(0, 15)): Giới hạn phạm vi Y từ 0–15 mile để tránh đuôi quá xa làm méo khung hình.

labs(…) + theme_minimal(): Tiêu đề, nhãn trục và nền tối giản.

Nhận xét:

Trời quang & Nhiều mây: trung vị tầm nhìn ~10 mile, hộp rất hẹp → điều kiện nhìn ổn định và tốt. Thực tế, đây là bối cảnh lái xe thuận lợi, rủi ro do tầm nhìn nhỏ.

Mưa: trung vị giảm xuống ~5–6 mile, violin dày và rộng → biến thiên lớn giữa mưa nhẹ/mưa to. Về thực tiễn, cần giới hạn tốc độ linh hoạt, bật đèn chiếu gần/đèn sương mù, và cảnh báo “đường trơn – khoảng cách phanh dài hơn”.

Sương mù: trung vị ~1–2 mile (rất thấp), phân phối đậm ở vùng thấp → rủi ro quan sát cao nhất. Khuyến nghị hạ tốc độ theo thời gian thực, biển cảnh báo sương mù, sơn vạch phản quang, adaptive headlights.

Tuyết: trung vị thấp tương tự sương mù, nhưng độ phân tán lớn hơn (điều kiện tuyết/đá bào khác nhau). Cần phun muối/sand, dọn tuyết sớm, và cảnh báo băng đen (black ice).

Khác: median gần 7–8 mile, độ rộng lớn → nhóm tổng hợp, chất lượng không đồng nhất; nên tách nhỏ nếu cần phân tích sâu.

Ngoại lệ lên tới ~10–15 mile trong các nhóm “xấu” (ví dụ vài điểm ở Tuyết/Sương mù) nhiều khả năng là điểm đo/ghi nhận đặc thù (khu vực ít tuyết, sương mù thoáng), hoặc nhiễu báo cáo; hữu ích để thiết lập kiểm tra chất lượng dữ liệu (ví dụ winsorize hoặc gắn cờ kiểm tra thủ công).

Biểu đồ xác nhận trực quan rằng tầm nhìn giảm mạnh trong Mưa/Sương mù/Tuyết và ổn định ở Trời quang/Nhiều mây. Đây là cơ sở dữ liệu cho các chính sách an toàn giao thông theo điều kiện thời tiết: giới hạn tốc độ biến thiên, biển cảnh báo động, tăng chiếu sáng/vạch phản quang, ưu tiên bảo trì mặt đường vào mùa mưa tuyết, và nhắn tin cảnh báo qua ứng dụng/hệ thống VMS (biển điện tử).

1.4.3.3 Cụm khí tượng: Độ ẩm & Áp suất (scatter + contour)

set.seed(1)
pdat <- Acc_raw %>%
  filter(between(`Pressure(in)`, 28, 31), between(`Humidity(%)`, 0, 100)) %>%
  dplyr::slice_sample(n = 200000)

ggplot(pdat, aes(`Pressure(in)`, `Humidity(%)`)) +
  geom_density_2d_filled(contour_var = "ndensity") +   # dải màu theo mật độ
  labs(title = "Độ ẩm vs Áp suất (contour mật độ)",
       x = "Áp suất (inHg)", y = "Độ ẩm (%)", fill = "Mật độ") +
  theme_minimal()

set.seed(1): cố định “hạt giống” ngẫu nhiên để việc lấy mẫu cho ra cùng kết quả mỗi lần chạy (tái lập được).

pdat <- Acc_raw %>% filter(…)`:Lọc dữ liệu chỉ giữ các quan sát có áp suất trong khoảng 28–31 inHg (loại bỏ ngoại lai cực đoan) và độ ẩm 0–100%.

dplyr::slice_sample(n = 200000): lấy mẫu ngẫu nhiên 200.000 dòng (dataset rất lớn → tăng tốc vẽ mà vẫn giữ phân bố tổng quát).

ggplot(…): Vẽ bản đồ mật độ 2D có tô màu (contour filled). Tham số **contour_var = “ndensity”` chuẩn hoá mật độ theo cực đại (0–1), nên thang màu biểu diễn mức tương đối chứ không phải tần suất tuyệt đối.

labs(…) + theme_minimal(): đặt nhãn trục/tiêu đề và dùng giao diện tối giản.

Nhận xét kết quả:

Vệt “nóng” đậm nhất tập trung quanh áp suất ≈ 29.7–29.9 inHg và độ ẩm ≈ 55–75%.

→ Đây là vùng mật độ cao nhất (màu sáng hơn trong thang viridis), nghĩa là phần lớn vụ việc xảy ra khi áp suất gần mức bình thường (mực biển ≈ 29.92 inHg) và độ ẩm trung bình–cao.

Khi áp suất thấp < 29.3 hoặc cao > 30.2, mật độ loãng rõ rệt → hiếm gặp trong dữ liệu (hoặc ít khi trùng thời điểm tai nạn).

Dải độ ẩm rất thấp (<30%) hoặc rất cao (>90%) cũng có mật độ thấp hơn; trọng tâm vẫn là vùng 50–85%.

Ý nghĩa thực tế – kinh tế/xã hội:

Áp suất: vì phần lớn tai nạn diễn ra ở mức áp suất “bình thường”, có thể kết luận áp suất khí quyển không phải tác nhân trực tiếp mạnh đối với rủi ro tai nạn đường bộ (ít nhất ở thang đo thường ngày). Do đó, việc dự báo rủi ro không nên dựa riêng vào áp suất.

Độ ẩm trung bình–cao đi kèm rủi ro lớn hơn (mật độ dày): độ ẩm cao thường đi với mưa/đường ướt/sương mù nhẹ, làm: Giảm ma sát lốp–mặt đường → quãng phanh dài hơn, dễ trượt.

Suy giảm tầm nhìn (bụi nước, sương) → chậm phản ứng.

=> Cơ quan giao thông nên tăng cảnh báo tốc độ, chiếu sáng/biển báo phản quang, quản lý thoát nước mặt đường trong thời kỳ ẩm ướt; doanh nghiệp logistics cân nhắc lịch trình, lốp phù hợp, khoảng cách an toàn để giảm chi phí tai nạn.

Tai nạn tập trung ở áp suất bình thường và độ ẩm 55–75%—gợi ý độ ẩm/điều kiện ướt mới là yếu tố thời tiết cần ưu tiên quản trị rủi ro, hơn là áp suất khí quyển.

1.4.3.4 Phân bố nhiệt độ theo ca trong ngày (histogram + facet)

ggplot(Acc_raw, aes(`temp_c`)) +
  geom_histogram(bins=40, fill="#e6550d", color="white") +
  facet_wrap(~time_period) +
  labs(title="Phân bố nhiệt độ theo ca trong ngày (°C)",
       x = "Nhiệt độ (°C)", y = "Số vụ tai nạn") +
  theme_minimal()

ggplot(…) +: Khởi tạo biểu đồ dùng dữ liệu Acc_raw, ánh xạ trục X là nhiệt độ đã đổi sang độ C (temp_c).

geom_histogram(…) +: Vẽ histogram với 40 “thùng” (bins), tô màu cột cam, viền trắng để dễ đọc.

facet_wrap(~ time_period): Bẻ (facet) biểu đồ thành 4 ô riêng theo biến phân loại time_period (Đêm, Sáng, Chiều, Tối), giúp so sánh phân bố nhiệt độ theo từng ca.

labs(…) + theme_minimal(): Đặt tiêu đề/nhãn trục và dùng chủ đề tối giản để biểu đồ sạch, dễ nhìn.

Nhận xét

Đêm: Phân bố dồn ở khoảng ~5–20 °C (lệch trái nhẹ). Nhiệt độ mát/lạnh chiếm đa số — phù hợp đặc trưng ban đêm. Số vụ không quá lớn so với ca khác.

Sáng: Phân bố dịch lên ~10–30 °C, đỉnh ở khoảng 15–25 °C. Đây là thời điểm số vụ nhiều (cột cao), phản ánh giao thông giờ cao điểm buổi sáng; nhiệt độ ôn hòa không làm giảm mật độ di chuyển.

Chiều: Phân bố ấm nhất, ~15–35 °C, đỉnh quanh 20–30 °C. Ca Chiều có số vụ cao nhất, trùng giờ cao điểm tan ca + thời tiết ấm dễ làm lưu lượng tăng, mệt mỏi/thiếu tập trung nhiều hơn.

Tối: Phân bố ~5–25 °C, thấp hơn buổi chiều, số vụ giảm so với Sáng/Chiều nhưng vẫn hiện diện ở ngưỡng ấm-mát.

Hàm ý thực tế – kinh tế/xã hội:

Quản lý giao thông theo thời gian/ngày: Ca Sáng và Chiều là điểm nóng bất kể nhiệt độ cụ thể (trong vùng ấm–mát), nên cần ưu tiên tuần tra/điều tiết và tối ưu tín hiệu đèn giờ cao điểm.

An toàn theo thời tiết: Không thấy “đuôi” nhiệt độ cực đoan dài; đa số vụ xảy ra ở nhiệt độ ôn hòa (10–30 °C) → yếu tố lưu lượng và hành vi có thể quan trọng hơn bản thân nhiệt độ.

Quy hoạch & truyền thông: Tập trung nhắc nhở lái xe vào các khung 7–9h, 16–19h khi nhiệt độ dễ chịu nhưng mật độ xe cao; doanh nghiệp vận tải có thể xếp ca linh hoạt để né giờ cao điểm, giảm chi phí rủi ro.

Tóm lại, biểu đồ cho thấy khác biệt phân bố nhiệt độ theo ca, nhưng đỉnh tai nạn gắn với ca Sáng/Chiều hơn là cực trị nhiệt độ, gợi ý ưu tiên can thiệp vào quản lý lưu lượng và hành vi lái xe trong những khung giờ này.

1.4.3.5 Nhiệt độ theo trạng thái thời tiết (violin)

ggplot(subset(Acc_raw, weather_simple %in% w10),
       aes(weather_simple, `temp_c`, fill=weather_simple)) +
  geom_violin(trim=FALSE, alpha=.8) +
  scale_fill_manual(values=pal_many) +
  coord_flip() +
  labs(title="Nhiệt độ theo trạng thái thời tiết (violin)", y="Nhiệt độ(°C)") +
  theme_minimal()

ggplot(): Lọc dữ liệu chỉ còn 10 trạng thái thời tiết phổ biến nhất (đã lưu trong w10). Khởi tạo ggplot: trục hoành là nhóm thời tiết (weather_simple), trục tung là nhiệt độ °C (temp_c), và màu tô theo từng nhóm thời tiết.

geom_violin(): Vẽ violin plot cho phân bố nhiệt độ của từng nhóm. trim = FALSE giữ toàn bộ đuôi phân bố (không cắt), alpha=.8 tạo độ trong suốt nhẹ.

scale_fill_manual(): Dùng bảng màu tùy chỉnh pal_many để các nhóm nhất quán màu trên toàn báo cáo.

coord_flip(): Đảo trục để nhóm nằm dọc, giúp đọc nhãn thời tiết thuận mắt khi nhiều hạng mục.

labs(…): Đặt tiêu đề và nhãn trục

theme_minimal(): Giao diện tối giản, dễ đọc.

Nhận xét kết quả

Trời quang: violin rộng và dài nhất, tâm phân bố nằm trên 15–25°C nhưng kéo dài tới nhiệt độ cao >35°C. → Tai nạn xảy ra trong biên độ nhiệt rất rộng khi trời đẹp (do lưu lượng giao thông lớn, tốc độ cao).

Nhiều mây: phân bố tập trung khoảng 10–25°C, hẹp hơn trời quang. → Điều kiện trung tính, nhiệt độ ôn hòa.

Mưa: phân bố hẹp hơn, tâm khoảng 5–20°C; đuôi lạnh ít. → Mưa thường đi kèm nền nhiệt mát, cần kiểm soát tốc độ/phanh vì mặt đường trơn.

Sương mù: nhiệt độ chủ yếu 5–15°C, dải hẹp. → Điều kiện ẩm lạnh, tầm nhìn giảm; cần tăng chiếu sáng, biển cảnh báo. Tuyết: phân bố ở nhiệt độ rất thấp, nhiều giá trị âm đến ~5°C, đuôi trái dài. → Rủi ro do đóng băng, cần rải muối, lốp mùa đông, hạn chế tốc độ, bố trí dịch vụ cứu hộ.

Khác: dải rộng nhưng mật độ chính ở quanh 0–15°C, có vài ngoại lệ. → Nhóm tạp, nên xem xét tách chi tiết nếu cần (bão, giông…).

Hàm ý vận hành/kinh tế–xã hội:

Phân bổ nguồn lực theo mùa & thời tiết: tăng tuần tra và nhắc nhở an toàn trong trời quang ấm (lưu lượng lớn), tăng xử lý mặt đường khi tuyết/lạnh.

Hạ tầng thông minh: biển báo giảm tốc tự động khi mưa/sương (nhiệt độ mát), hệ thống cảm biến đóng băng kích hoạt rải muối chủ động.

Truyền thông an toàn: bản tin khuyến cáo theo ngưỡng nhiệt đặc trưng của từng hiện tượng (ví dụ <0°C cho tuyết/băng, 5–10°C cho sương mù).

1.4.3.6 Tỷ lệ tai nạn nặng (sev≥3) theo thời tiết và ca (heatmap)

tab <- within(Acc_raw, {sev3 <- Severity>=3})
heat <- aggregate(sev3 ~ weather_simple + time_period, tab, mean, na.rm=FALSE)
ggplot(heat, aes(time_period, weather_simple, fill=sev3)) +
  geom_tile() +
  scale_fill_viridis_c(labels=scales::percent) +
  labs(title="Tỷ lệ tai nạn nặng (sev≥3) theo thời tiết & ca",
       fill="Tỷ lệ") +
  theme_minimal()

tab <- within(…): Tạo biến nhị phân sev3 (1 nếu vụ việc thuộc mức nặng ≥3, ngược lại 0). Việc mã hoá nhị phân cho phép tính tỉ lệ bằng cách lấy trung bình (mean) của biến 0/1.

heat <- aggregate(…): Gộp dữ liệu theo 2 chiều: nhóm thời tiết rút gọn weather_simple và ca trong ngày time_period, rồi tính trung bình của sev3. Vì sev3 là 0/1, trung bình = tỉ lệ (proportion) các vụ nặng trong nhóm đó. (Ở đây chưa loại NA ở khoá nhóm; nếu có NA ở Severity thì sev3 đã là FALSE/TRUE theo phép so sánh; tuỳ cấu trúc dữ liệu có thể cân nhắc na.rm=TRUE.)

ggplot(…) + geom_tile(): Vẽ heatmap: trục x là ca, trục y là nhóm thời tiết, và màu biểu diễn tỉ lệ vụ nặng.

scale_fill_viridis_c(labels = scales::percent): Dùng thang màu Viridis (dễ đọc, tuyến tính, thân thiện người mù màu) và hiển thị nhãn màu theo %.

labs(…) + theme_minimal(): Đặt tiêu đề, nhãn trục và theme tối giản.

Nhận xét:

  • Mẫu hình theo ca (trục x): Nhìn chung, tối và đêm có tỉ lệ vụ nặng cao hơn sáng/chiều (màu sáng hơn). Điều này phù hợp thực tế: điều kiện ánh sáng kém, mệt mỏi, tốc độ cao hơn do đường vắng → khi va chạm dễ nghiêm trọng hơn.

Chiều thường có tỉ lệ thấp nhất trong hầu hết nhóm thời tiết, gợi ý điều kiện quan sát tốt và mật độ giao thông dày làm tốc độ trung bình thấp hơn.

  • Khác biệt theo thời tiết (trục y): “Khác” (các điều kiện hiếm/không phân loại) có tỉ lệ rất cao đặc biệt ban đêm/tối (~màu sáng nhất). Có thể chứa các tình huống thời tiết cực đoan hoặc ghi chép không chi tiết; cần rà soát định nghĩa/trường hợp ngoại lệ. Mưa có tỉ lệ cao thứ hai, nổi bật vào đêm/tối. Điều này hợp lý: mặt đường trơn trượt + tầm nhìn giảm → khi tai nạn xảy ra dễ ở mức nặng.

Nhiều mây ở mức trung bình-khá; không làm xấu tầm nhìn như mưa/sương nhưng thường đi kèm ánh sáng yếu → mức độ nặng tăng vừa phải.

Sương mù cho thấy tỉ lệ thấp nhất, đặc biệt buổi chiều (ô tối nhất). Có thể do số vụ ở sương mù tập trung buổi sáng, chiều ít; hoặc khi có sương, tài xế giảm tốc độ mạnh → ít tai nạn nặng dù vẫn có va chạm. (Nên kiểm tra quy mô mẫu từng ô; tỉ lệ thấp với mẫu nhỏ cũng có thể do biến động ngẫu nhiên.)

Trời quang: tỉ lệ thấp–trung bình, song cao hơn về đêm so với sáng/chiều → phù hợp vai trò ánh sáng là yếu tố chính.

Hàm ý chính sách & vận hành:

Chiếu sáng & tốc độ ban đêm: tăng cường đèn đường, biển cảnh báo, cưỡng chế tốc độ, tuần tra vào đêm/tối.

Hạ tầng khi mưa: cải thiện thoát nước, nhám mặt đường, biển “đường trơn khi ướt”, cảnh báo tự động bằng VMS; khuyến nghị giảm tốc độ khi mưa.

Quản lý tầm nhìn: hệ thống cảnh báo sương mù khu vực thường phát sinh (biển điện tử, đèn dẫn đường), dù tỉ lệ nặng thấp thì rủi ro chuỗi nhiều xe vẫn đáng lưu ý.

Dữ liệu “Khác”: nên làm sạch/chuẩn hoá điều kiện thời tiết để không trộn các tình huống dị biệt; tỉ lệ cao ở nhóm này có thể che khuất nguyên nhân cụ thể.

1.4.4 Liên hệ với Mức độ nghiêm trọng (Severity)

1.4.4.1 Mức độ nghiêm trọng theo tầm nhìn và ca trong ngày

set.seed(1)
pdat <- Acc_raw[, c("Visibility(mi)","Severity","time_period")] |>
  dplyr::slice_sample(n = 150000)

ggplot(pdat, aes(`Visibility(mi)`, Severity, color=time_period)) +
  geom_point(alpha=.05, size=.7) +
  geom_smooth(se=FALSE, method="gam", formula = y ~ s(x, k=10)) +
  scale_color_manual(values=pal_many) +
  labs(title="Severity ~ Tầm nhìn theo ca", x="Tầm nhìn (mile)", y="Severity") +
  theme_minimal()

set.seed(1): cố định hạt giống để mọi lần lấy mẫu ngẫu nhiên đều cho cùng kết quả (tái lập phân tích).

pdat <- Acc_raw[…] |> dplyr::slice_sample(n = 150000)*: Lấy 3 cột cần thiết và lấy mẫu ngẫu nhiên 150k dòng (giảm tải tính toán nhưng vẫn đại diện tốt).

ggplot(…): khởi tạo ggplot, trục X là tầm nhìn (mile), trục Y là mức độ nghiêm trọng (Severity); tô màu theo ca trong ngày.

geom_point(alpha=.05, size=.7): vẽ scatter mờ (alpha thấp) để thấy mật độ điểm mà không bị “bết”.

geom_smooth(…): vẽ đường xu thế trơn cho từng ca bằng GAM với spline s(x) 10 nút – phù hợp khi quan hệ phi tuyến; tắt dải tin cậy để đồ thị gọn.

*scale_color_manual(values=pal_many): dùng bảng màu đã tự định nghĩa.

labs(…) + theme_minimal():* đặt tiêu đề, nhãn trục, theme tối giản.

Nhận xét kết quả

  • Mức độ chung và phân bố điểm Severity là thang rời rạc 1–4 nên các điểm nằm thành dải ngang ở mức ~2, ~3, ~4; phần lớn tập trung quanh mức 2. Ở tầm nhìn rất thấp (≈0–2 mile) xuất hiện đủ các mức 1–4 → đây là vùng có rủi ro nghiêm trọng hơn.

  • Xu thế theo từng ca (đường GAM) Đêm & Tối: đường xu thế nhích lên nhẹ khi tầm nhìn tăng từ 0→~40–50 mile, sau đó đổi chiều/đi ngang. Điều này phản ánh khi tầm nhìn quá kém (sương mù, mưa to), tài xế thường giảm tốc mạnh, làm mức độ nghiêm trọng không tăng thêm nhiều; nhưng khi tầm nhìn khá hơn, tốc độ thực tế tăng, nên nếu tai nạn xảy ra có thể nghiêm trọng hơn đôi chút. Sau ~50–60 mile lợi ích biên của tầm nhìn gần như bão hòa.

Sáng: xu thế giảm rất nhẹ theo tầm nhìn; ca sáng có ánh sáng tự nhiên tăng dần nên tầm nhìn tốt đi kèm mức nghiêm trọng thấp hơn.

Chiều: có “u-shape” nhẹ – tăng đến khoảng 40–50 mile rồi ngang/giảm; có thể do giờ cao điểm chiều: giao thông dày đặc, tốc độ dao động, hành vi phanh–tăng tốc khiến mức độ nếu va chạm xảy ra có thể nhỉnh hơn ở tầm nhìn trung bình–khác

Khác biệt giữa các ca là nhỏ (đường dao động quanh 2.0–2.2). Nghĩa là tầm nhìn một mình không giải thích mạnh cho mức độ nghiêm trọng; bối cảnh (tốc độ cho phép, loại đường, mật độ xe, thời tiết đi kèm) mới là yếu tố nặng ký.

Hàm ý thực tế / chính sách

Ưu tiên cảnh báo & can thiệp khi tầm nhìn rất thấp (0–2 mile): biển báo sương mù/mưa, hạ tốc độ bắt buộc, bật đèn sương mù; đặc biệt ban đêm & chiều tối.

Chiếu sáng & vạch phản quang trên đường giúp giảm rủi ro ở ca đêm/tối – nơi đường GAM cao hơn đôi chút.

Quản lý tốc độ theo điều kiện thực tế: vì khi tầm nhìn khá hơn, tài xế có xu hướng chạy nhanh → mức độ nếu xảy ra va chạm có thể tăng nhẹ; biển “variable speed limit” có ích.

Tầm nhìn rất kém gắn với nguy cơ mức độ cao hơn; ngoài vùng này, ảnh hưởng của tầm nhìn đến Severity khá yếu và khác biệt giữa các ca không lớn. Biện pháp an toàn nên tập trung vào điều kiện tầm nhìn kém và ca đêm/tối.

1.4.4.2 Mức độ nghiêm trọng theo nhiệt độ trong 6 trạng thái thời tiết phổ biến (scatter + smooth)

w6 <- names(sort(table(Acc_raw$weather_simple),decreasing=TRUE))[1:6]
ggplot(subset(Acc_raw, weather_simple %in% w6),
       aes(temp_c, Severity, color=weather_simple)) +
  geom_point(alpha=.05) +
  geom_smooth(se=FALSE) +
  scale_color_manual(values=pal_many) +
  labs(title="Severity theo nhiệt độ trong 6 trạng thái thời tiết phổ biến", y="Mức độ nghiêm trọng", x="Nhiệt độ") +
  theme_minimal()
## `geom_smooth()` using method = 'gam' and formula = 'y ~ s(x, bs = "cs")'

w6 <- names(…)[1:6]:Lấy 6 trạng thái thời tiết xuất hiện nhiều nhất (phổ biến) để vẽ — giúp biểu đồ gọn, dễ so sánh, tránh nhiễu từ nhóm hiếm.

ggplot(…): Chỉ dùng các bản ghi thuộc 6 trạng thái trên; trục x = nhiệt độ (°C), trục y = mức độ nghiêm trọng (Severity); màu thể hiện nhóm thời tiết.

geom_point(alpha=.05): Vẽ scatter rất trong suốt (alpha = 0.05) để nhìn mật độ điểm thay vì một mảng đặc (dữ liệu lớn).

geom_smooth(se=FALSE): Vẽ đường xu hướng trơn (GAM mặc định của ggplot2) cho từng nhóm thời tiết (không hiển thị dải sai số) – giúp nhìn quan hệ chung Severity ~ nhiệt độ theo từng điều kiện thời tiết.

scale_color_manual(values=pal_many) & theme_minimal():Đặt bảng màu nhất quán, nền tối giản.

labs(…): Tiêu đề & nhãn trục.

Nhận xét:

Mặt bằng chung: Các đường trơn đều xoay quanh Severity ≈ 2–2.3; biến thiên theo nhiệt độ khá nhẹ → quan hệ yếu giữa nhiệt độ và mức độ nghiêm trọng so với các yếu tố khác (giờ, tầm nhìn, loại đường…).

  • Theo từng thời tiết:

  • Trời quang / Nhiều mây / Mưa: xu hướng hơi “nhô” ở vùng nhiệt độ vừa phải (≈15–30°C) rồi giảm nhẹ ở đầu lạnh/nóng. Mức dao động < ~0.2 đơn vị Severity → tác động nhỏ về độ lớn.

  • Sương mù: đường trơn hơi cao hơn vùng nhiệt độ mát (≈5–20°C), gợi ý rủi ro nghiêm trọng hơn khi có sương ở nhiệt độ mát/lạnh.

  • Tuyết: trong dữ liệu thường gắn với nhiệt độ thấp, đường trơn nằm quanh ~2.2 và có đỉnh nhẹ ở rất lạnh → phù hợp trực giác về điều kiện bám đường kém.

  • Khác: biến động lớn hơn ở đầu nóng (ít dữ liệu hơn) → nên cẩn trọng khi diễn giải.

-> Với dữ liệu quan sát, nhiệt độ đơn lẻ không phải là yếu tố mạnh quyết định độ nghiêm trọng; hiệu ứng tương tác với trạng thái thời tiết (đặc biệt sương mù, tuyết) mới thấy khác biệt hữu ích.

Hàm ý thực tế – kinh tế – xã hội

Ưu tiên an toàn theo kịch bản thời tiết, không chỉ theo nhiệt độ:

  • Sương mù & tuyết: cần giảm tốc độ khuyến nghị, tăng biển cảnh báo, rải muối/chống trơn; bố trí camera/cảm biến tầm nhìn ở đoạn đường hay có sương/tuyết .

  • Chi phí–hiệu quả: Đầu tư vào hệ thống thông tin thời tiết theo đoạn đường và cảnh báo thời gian thực có khả năng giảm mức độ nghiêm trọng hiệu quả hơn so với các biện pháp chung theo nhiệt độ.

  • Truyền thông an toàn: Nhấn mạnh cho tài xế rằng trời quang nhưng nóng/lạnh không làm Severity tăng mạnh; nguy cơ lớn đến từ điều kiện làm giảm tầm nhìn & độ bám đường (sương mù, tuyết, mưa).

1.4.4.3 Mức độ nghiêm trọng ~ Quãng đường theo bang (Top 8, median line)

s8 <- names(sort(table(Acc_raw$State),decreasing=TRUE))[1:8]
ggplot(subset(Acc_raw, State %in% s8),
       aes(`Distance(mi)`, Severity)) +
  geom_point(alpha=.06) +
  geom_smooth(se=FALSE) +
  facet_wrap(~State) +
  geom_hline(yintercept=median(Acc_raw$Severity, na.rm=TRUE),
             linetype="dashed") +
  labs(title="Mức độ nghiêm trọng & quãng đường sự cố theo bang (top 8)") +
  theme_minimal()
## `geom_smooth()` using method = 'gam' and formula = 'y ~ s(x, bs = "cs")'

s8 <- names(…)[1:8]: Lấy 8 bang có số vụ nhiều nhất (đếm tần suất theo State, sắp xếp giảm dần, lấy 8 tên đầu).

ggplot(…) + Lọc dữ liệu về 8 bang top và vẽ đồ thị phân tán với trục X là độ dài ảnh hưởng của sự cố (mile) và trục Y là mức độ nghiêm trọng (Severity`, 1–4).

geom_point(alpha=.06): Vẽ điểm phân tán, độ trong suốt thấp để tránh chồng lấp (nhiều điểm).

geom_smooth(se=FALSE): Thêm đường xu hướng trơn (loess mặc định) cho từng ô—cho ta cái nhìn tổng quát về xu hướng Severity theo Distance. Không vẽ dải sai số.

facet_wrap(~State): Chia nhỏ thành 8 ô (mỗi bang một ô) để so sánh song song.

geom_hline(…): Kẻ đường ngang nét đứt tại trung vị Severity toàn bộ dữ liệu (≈2). Đây là mốc tham chiếu.

labs(…) + theme_minimal(): Đặt tiêu đề, nhãn trục và dùng chủ đề tối giản.

Nhận xét kết quả:

Phần lớn điểm tập trung quanh Severity ≈ 2 (đường đứt), chứng tỏ đa số vụ ở mức vừa.

Distance(mi) rất lệch phải: nhiều vụ có quãng đường ảnh hưởng ngắn (≤10–20 mi), còn quãng dài xuất hiện ít → ở cuối trục X số điểm thưa hơn, khiến đường trơn ở đuôi ít tin cậy hơn.

  • Theo từng bang (xem đường trơn trong mỗi ô):

  • CA, NY, SC: đường trơn tương đối phẳng quanh mức 2 → Severity ít thay đổi theo Distance. Hàm ý: ở các bang đô thị hóa/đường dày đặc, độ nghiêm trọng chịu ảnh hưởng bởi yếu tố khác (mật độ xe, giao cắt, thời tiết, giờ cao điểm) hơn là độ dài đoạn ảnh hưởng.

  • FL: có xu hướng nhích lên khi Distance tăng (đỉnh khoảng 30–80 mi, rồi giảm nhẹ) → các sự cố ảnh hưởng dài ở FL dễ nặng hơn. Có thể liên quan cao tốc dài ven biển, thời tiết nhiệt đới gây chuỗi va chạm hoặc tắc kéo dài.

  • PA: dạng “gù”: tăng khi Distance lên ~50–90 mi rồi về lại gần 2 → gợi ý chỉ một vùng Distance trung bình liên quan severity cao; ngoài vùng đó mức độ ổn định.

  • TX: xu hướng tăng rõ theo Distance (đến gần 3 ở Distance lớn) → các đoạn ảnh hưởng dài (thường là xa lộ dài, vận tải nặng) liên quan va chạm nghiêm trọng hơn. Hàm ý quản lý: ưu tiên cảnh báo sớm, phân luồng trên các tuyến dài/ngoại ô.

  • NC, VA: đường trơn hơi giảm ở khoảng Distance lớn → có thể do các sự cố kéo dài chủ yếu là tắc nghẽn/điểm nghẽn nhẹ hơn là va chạm nặng; cũng có khả năng kích thước mẫu ít ở đuôi làm đường trơn dao động.

Hàm ý quản trị & chính sách:

  • Ở các bang như TX, FL, nơi Severity tăng theo Distance, nên:

Tăng mật độ camera/cảm biến trên các hành lang dài; Quy trình phản ứng nhanh (towing, cảnh báo VMS) để rút ngắn thời gian ảnh hưởng, tránh escalation; Nhắm vào khung giờ/đoạn đường thường tạo ra các sự cố dài (kết hợp dữ liệu giờ/ngày).

  • Ở các bang CA, NY (đường trơn phẳng): ưu tiên biện pháp giảm va chạm tại nút giao, khu đô thị, quản lý tốc độ – làn – giao cắt hơn là chỉ chú ý đến chiều dài ảnh hưởng.

  • Truyền thông an toàn: nhấn mạnh rủi ro trên cao tốc/hành lang dài (đặc biệt ở TX, FL), khuyến nghị giữ khoảng cách – quan sát xa – tránh lái xe mệt mỏi.

Tóm lại, biểu đồ cho thấy mối liên hệ không đồng nhất theo bang giữa quãng ảnh hưởng và mức độ nghiêm trọng. Những bang có hành lang dài/cao tốc nhiều (TX, FL) biểu hiện mối liên hệ tăng, gợi ý ưu tiên nguồn lực cho quản trị sự cố trên tuyến dài; còn các bang đô thị dày đặc thì yếu tố vận hành đô thị có thể quan trọng hơn quãng đường ảnh hưởng.

2 CHƯƠNG 2: Phân tích cơ cấu và xu hướng biến động tài sản của SHB giai đoạn 2014-2023

2.1 Tổng quan về dữ liệu

Bộ dữ liệu được sử dụng trong đề tài là báo cáo tài chính hợp nhất của Ngân hàng TMCP Sài Gòn – Hà Nội (SHB) trong giai đoạn từ năm 2014 đến năm 2023. Dữ liệu được thu thập từ báo cáo thường niên và báo cáo tài chính đã kiểm toán công bố trên trang thông tin điện tử chính thức của SHB (https://www.shb.com.vn) và các nguồn dữ liệu uy tín như Vietstock và Ủy ban Chứng khoán Nhà nước.

Dữ liệu bao gồm các chỉ tiêu tài chính cơ bản phản ánh cơ cấu tài sản của ngân hàng, được tổng hợp theo từng năm, giúp đánh giá sự biến động tài sản, danh mục đầu tư và hoạt động tín dụng của SHB trong giai đoạn 10 năm nghiên cứu.

2.1.1 Đọc dữ liệu

shb <- read_csv("datap2.csv")
## Rows: 10 Columns: 12
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## dbl  (1): Year
## num (11): Tổng Tài Sản, Chứng Khoán Đầu Tư, Góp Vốn Dài Hạn, Tiền Gửi Khách ...
## 
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.

Hàm read_csv() (thuộc gói readr) được dùng để đọc tệp dữ liệu định dạng CSV (Comma-Separated Values) và lưu vào một tibble trong R (phiên bản hiện đại của data frame).

File có 10 hàng dữ liệu (tương ứng với 10 năm: 2014–2023);

Có 12 cột (biến) — bao gồm năm và 11 chỉ tiêu tài chính của ngân hàng SHB;

Delimiter: “,” cho biết dữ liệu được phân cách bằng dấu phẩy (chuẩn CSV);

R tự động nhận diện kiểu dữ liệu cho từng cột:

dbl (double) là kiểu số thực, ở đây là biến Year;

num là kiểu số (numeric), cho 11 biến còn lại (các giá trị tài chính).

2.1.2 Xem kích thước bộ dữ liệu

dim(shb)
## [1] 10 12

dim() là hàm trong R dùng để trả về kích thước (dimension) của một đối tượng dạng bảng

Bộ dữ liệu shb có 10 dòng (quan sát) và 12 cột (biến số):

Bộ dữ liệu này ghi nhận 10 năm tài chính từ 2014 đến 2023, tương ứng với 10 quan sát.

Có 12 biến (chỉ tiêu tài chính) phản ánh cơ cấu tài sản của Ngân hàng SHB

2.1.3 Kiểm tra kiểu đối tượng của bảng

Sử dụng hàm class() là để xác định kiểu đối tượng (object type) của dữ liệu đang được lưu trong biến shb.

Điều này rất quan trọng khi làm việc trong R vì:

Một tập dữ liệu có thể thuộc nhiều loại cấu trúc khác nhau (data.frame, matrix, list, tibble, v.v.).

Một số hàm trong R chỉ hoạt động đúng với một loại đối tượng nhất định, ví dụ:

Các hàm từ base R thường yêu cầu dạng data.frame.

Các hàm từ tidyverse (như dplyr, ggplot2) thường hoạt động tốt với tibble.

class(shb)
## [1] "spec_tbl_df" "tbl_df"      "tbl"         "data.frame"

spec_tbl_df: là lớp đặc biệt được tạo ra khi dữ liệu được đọc bằng read_csv() từ gói readr. Nó lưu thêm thông tin về cấu trúc cột (column specification).

tbl_df và tbl: là lớp dữ liệu của tibble, một phiên bản “nâng cấp” của data.frame trong tidyverse — giúp hiển thị đẹp, gọn, và không tự động chuyển kiểu dữ liệu.

data.frame: lớp dữ liệu cơ bản trong R, đảm bảo tương thích ngược với các hàm base R.

Bộ dữ liệu shb là một bảng tibble (thuộc họ data.frame), có thể sử dụng linh hoạt trong cả base R và tidyverse. Đây là định dạng tiêu chuẩn và tối ưu cho việc xử lý, thống kê, và trực quan hóa dữ liệu trong RMarkdown.

2.1.4 Liệt kê tên các biến

names(shb)
##  [1] "Year"                   "Tổng Tài Sản"           "Chứng Khoán Đầu Tư"    
##  [4] "Góp Vốn Dài Hạn"        "Tiền Gửi Khách Hàng"    "Cho vay các TCTD khác" 
##  [7] "Chứng khoán Kinh Doanh" "Tiền Vàng Ngoại Tệ"     "Tiền Gửi NHNN"         
## [10] "TS Hữu Hình"            "TS Vô Hình"             "Tài Sản Khác"

names() là hàm trong R dùng để xem hoặc thay đổi tên các biến (tên cột) của một data frame.

Bộ dữ liệu shb hiện có 12 biến (cột), trong đó:

Biến “Year” thể hiện năm tài chính, ghi nhận giai đoạn báo cáo của ngân hàng từ năm 2014 đến năm 2023. Biến này giúp xác định thời gian của từng quan sát trong chuỗi dữ liệu, phục vụ cho việc phân tích xu hướng biến động của các chỉ tiêu tài sản qua từng năm. Đơn vị tính là năm.

Biến “Tổng Tài Sản” phản ánh tổng giá trị tài sản mà Ngân hàng TMCP Sài Gòn – Hà Nội (SHB) sở hữu hoặc kiểm soát tại thời điểm cuối năm tài chính. Đây là chỉ tiêu tổng hợp nhất, thể hiện quy mô hoạt động và năng lực tài chính của ngân hàng. Đơn vị tính: đồng Việt Nam (VND).

Biến “Chứng Khoán Đầu Tư” thể hiện giá trị các khoản đầu tư của SHB vào cổ phiếu, trái phiếu và các công cụ tài chính khác nhằm mục tiêu sinh lời hoặc đảm bảo thanh khoản. Biến này giúp đánh giá chiến lược đầu tư và quản lý danh mục tài chính của ngân hàng. Đơn vị tính: VND.

Biến “Góp Vốn Dài Hạn” cho biết giá trị các khoản đầu tư dài hạn của ngân hàng vào công ty con, công ty liên kết hoặc các dự án có thời gian thu hồi vốn trên một năm. Biến này phản ánh chiến lược mở rộng đầu tư dài hạn và khả năng tham gia vào các hoạt động kinh tế ngoài lĩnh vực ngân hàng cốt lõi. Đơn vị tính: VND.

Biến “Tiền Gửi Khách Hàng” thể hiện tổng giá trị tiền gửi của cá nhân, doanh nghiệp và tổ chức kinh tế tại SHB. Đây là nguồn vốn huy động chủ yếu, đóng vai trò quan trọng trong việc hình thành nguồn vốn cho hoạt động tín dụng và đầu tư của ngân hàng. Đơn vị tính: VND.

Biến “Cho vay các TCTD khác” biểu thị tổng giá trị cho vay mà SHB cấp cho các tổ chức tín dụng khác trong hệ thống. Biến này giúp phản ánh khả năng hỗ trợ thanh khoản, phân bổ vốn và quan hệ hợp tác tài chính giữa các ngân hàng. Đơn vị tính: VND.

Biến “Chứng khoán Kinh Doanh” thể hiện giá trị danh mục chứng khoán mà SHB nắm giữ để mua bán ngắn hạn, nhằm tìm kiếm lợi nhuận trong thời gian ngắn. Chỉ tiêu này cho thấy mức độ linh hoạt trong hoạt động đầu tư và khả năng tận dụng cơ hội thị trường của ngân hàng. Đơn vị tính: VND.

Biến “Tiền Vàng Ngoại Tệ” phản ánh tổng giá trị tiền mặt, vàng và ngoại tệ quy đổi mà ngân hàng đang nắm giữ. Biến này thể hiện khả năng thanh khoản cao và năng lực dự trữ tiền tệ của SHB trong việc đáp ứng nhu cầu chi trả, rút tiền hoặc thanh toán quốc tế. Đơn vị tính: VND.

Biến “Tiền Gửi NHNN” cho biết số tiền mà SHB gửi tại Ngân hàng Nhà nước Việt Nam, bao gồm tiền gửi dự trữ bắt buộc và tiền gửi thanh toán. Đây là chỉ tiêu thể hiện mức độ tuân thủ quy định dự trữ bắt buộc và khả năng thanh toán trong hệ thống ngân hàng. Đơn vị tính: VND.

Biến “TS Hữu Hình” phản ánh giá trị các tài sản vật chất hữu hình như nhà cửa, trụ sở, máy móc và thiết bị phục vụ hoạt động kinh doanh. Biến này thể hiện quy mô cơ sở vật chất và năng lực hạ tầng của ngân hàng. Đơn vị tính: VND.

Biến “TS Vô Hình” bao gồm các tài sản phi vật chất như phần mềm, bản quyền, thương hiệu, hoặc lợi thế thương mại. Biến này phản ánh giá trị thương hiệu và năng lực công nghệ – hai yếu tố quan trọng trong hoạt động ngân hàng hiện đại. Đơn vị tính: VND.

Biến “Tài Sản Khác” thể hiện các khoản tài sản không thuộc những nhóm trên, như khoản phải thu, tài sản chờ xử lý hoặc tài sản thu hồi. Biến này giúp nhận diện phần tài sản phụ trợ và đánh giá mức độ an toàn, rủi ro tiềm ẩn trong danh mục tài sản của SHB. Đơn vị tính: VND.

2.1.5 Hiển thị 5 dòng đầu tiên của dữ liệu

head(shb, 5)
## # A tibble: 5 × 12
##    Year `Tổng Tài Sản` `Chứng Khoán Đầu Tư` `Góp Vốn Dài Hạn`
##   <dbl>          <dbl>                <dbl>             <dbl>
## 1  2023        6.31e14              3.21e13      414448000000
## 2  2022        5.51e14              3.30e13       46699000000
## 3  2021        5.07e14              2.51e13      131652000000
## 4  2020        4.13e14              2.86e13      133140000000
## 5  2019        3.65e14              2.16e13      133140000000
## # ℹ 8 more variables: `Tiền Gửi Khách Hàng` <dbl>,
## #   `Cho vay các TCTD khác` <dbl>, `Chứng khoán Kinh Doanh` <dbl>,
## #   `Tiền Vàng Ngoại Tệ` <dbl>, `Tiền Gửi NHNN` <dbl>, `TS Hữu Hình` <dbl>,
## #   `TS Vô Hình` <dbl>, `Tài Sản Khác` <dbl>

Hàm head() trong R được dùng để hiển thị một số dòng đầu tiên của bộ dữ liệu (data frame hoặc tibble).

→ Khi chạy head(shb, 5), R sẽ in ra 5 dòng đầu tiên của bảng dữ liệu, giúp người dùng kiểm tra nhanh cấu trúc, kiểu dữ liệu, và định dạng giá trị của các biến.

Kết quả cho thấy dữ liệu shb gồm 12 cột (biến) và các giá trị thuộc 5 năm gần nhất (2019–2023).

Trong giai đoạn này, tổng tài sản của SHB có xu hướng tăng mạnh và liên tục qua các năm. Cụ thể, năm 2019 tổng tài sản đạt khoảng 365.254 tỷ đồng, đến năm 2023 đã tăng lên 630.501 tỷ đồng, thể hiện mức tăng trưởng ổn định, phản ánh quy mô hoạt động ngày càng mở rộng và năng lực tài chính được củng cố.

Chứng khoán đầu tư dao động từ khoảng 21.604 tỷ đồng năm 2019 lên đến 32.064 tỷ đồng năm 2023, cho thấy hoạt động đầu tư tài chính được mở rộng, phù hợp với định hướng đa dạng hóa nguồn thu của ngân hàng.

Góp vốn dài hạn tăng từ 13.314 tỷ đồng năm 2019 lên 41.444 tỷ đồng năm 2023, thể hiện nỗ lực mở rộng đầu tư chiến lược vào các công ty con hoặc dự án tiềm năng.

Tiền gửi khách hàng tăng đáng kể từ 259.236 tỷ đồng năm 2019 lên 447.503 tỷ đồng năm 2023, minh chứng cho sự tin tưởng ngày càng lớn của khách hàng đối với SHB và khả năng huy động vốn tốt.

Bên cạnh đó, cho vay các tổ chức tín dụng khác có giá trị dao động, trong đó cao nhất là năm 2023 với 4.999 tỷ đồng, phản ánh mối quan hệ thanh khoản và luân chuyển vốn giữa các ngân hàng.

Các khoản chứng khoán kinh doanh tuy chiếm tỷ trọng nhỏ (khoảng vài nghìn tỷ đồng), song vẫn cho thấy SHB có tham gia vào hoạt động đầu tư ngắn hạn nhằm tối ưu hóa lợi nhuận.

Đối với nhóm tài sản bằng tiền, bao gồm Tiền vàng ngoại tệ và Tiền gửi tại Ngân hàng Nhà nước, đều duy trì ở mức hàng chục nghìn tỷ đồng, thể hiện khả năng thanh khoản và dự trữ bắt buộc được đảm bảo tốt.

Nhóm tài sản cố định như Tài sản hữu hình và Tài sản vô hình duy trì ổn định qua các năm, lần lượt ở mức khoảng 500–700 tỷ đồng, thể hiện quy mô cơ sở vật chất và giá trị thương hiệu ổn định.

Cuối cùng, Tài sản khác dao động mạnh giữa các năm, đạt 35.954 tỷ đồng năm 2023, phản ánh phần tài sản phụ trợ và các khoản phải thu, thể hiện mức độ linh hoạt trong cơ cấu tài sản của ngân hàng.

Tổng tài sản của SHB tăng mạnh từ 365,254 tỷ đồng (2019) lên 630,500 tỷ đồng (2023) — phản ánh quá trình mở rộng quy mô nhanh chóng. Một số cột có giá trị NA (thiếu dữ liệu) ở những năm đầu (như “Cho vay các TCTD khác”), có thể do ngân hàng chưa phát sinh, chưa công bố khoản mục đó hoặc thay đổi cách phân loại tài sản trong báo cáo tài chính.

2.1.6 Hiển thị 5 dòng cuối của dữ liệu

tail(shb, 5)
## # A tibble: 5 × 12
##    Year `Tổng Tài Sản` `Chứng Khoán Đầu Tư` `Góp Vốn Dài Hạn`
##   <dbl>          <dbl>                <dbl>             <dbl>
## 1  2018        3.23e14              4.80e13      195767000000
## 2  2017        2.86e14              2.12e13      215465000000
## 3  2016        2.34e14              1.88e13      222949000000
## 4  2015        2.05e14              1.73e13      303409000000
## 5  2014        1.69e14              1.35e13      321032000000
## # ℹ 8 more variables: `Tiền Gửi Khách Hàng` <dbl>,
## #   `Cho vay các TCTD khác` <dbl>, `Chứng khoán Kinh Doanh` <dbl>,
## #   `Tiền Vàng Ngoại Tệ` <dbl>, `Tiền Gửi NHNN` <dbl>, `TS Hữu Hình` <dbl>,
## #   `TS Vô Hình` <dbl>, `Tài Sản Khác` <dbl>

tail() là hàm trong R dùng để hiển thị một số dòng cuối của bộ dữ liệu (data frame hoặc tibble).

Khi chạy tail(shb, 5), R hiển thị 5 dòng cuối cùng của bảng dữ liệu — giúp người phân tích xem phần kết thúc của bộ dữ liệu, thường là các năm đầu tiên trong chuỗi thời gian (vì dữ liệu SHB được sắp xếp giảm dần theo năm).

Các năm 2014–2018 là giai đoạn đầu chuỗi thời gian, thể hiện quy mô tài sản của SHB còn nhỏ so với các năm gần đây:

Tổng tài sản tăng từ khoảng 169.035 tỷ đồng (2014) lên 323.276 tỷ đồng (2018), phản ánh quy mô hoạt động được mở rộng ổn định.

Tiền gửi khách hàng cũng tăng mạnh từ 123.227 tỷ đồng (2014) lên 225.224 tỷ đồng (2018).

Một số khoản mục như chứng khoán đầu tư và góp vốn dài hạn có xu hướng mở rộng, cho thấy ngân hàng bắt đầu đa dạng hóa danh mục tài sản.

Tài sản vô hình và tài sản khác chiếm tỷ trọng nhỏ, phản ánh cấu trúc tài sản vẫn chủ yếu tập trung vào hoạt động tín dụng và đầu tư tài chính.

Giai đoạn này đánh dấu quá trình tăng trưởng ổn định và mở rộng quy mô hoạt động của SHB trước khi bước vào giai đoạn phát triển mạnh (2019–2023).

2.1.7 Kiểm tra cấu trúc và kiểu dữ liệu của bộ dữ liệu

str(shb)
## spc_tbl_ [10 × 12] (S3: spec_tbl_df/tbl_df/tbl/data.frame)
##  $ Year                  : num [1:10] 2023 2022 2021 2020 2019 ...
##  $ Tổng Tài Sản          : num [1:10] 630500685000000 550904120000000 506604328000000 412679593000000 365254318000000 ...
##  $ Chứng Khoán Đầu Tư    : num [1:10] 32063660000000 32954676000000 25104577000000 28639598000000 21604317000000 ...
##  $ Góp Vốn Dài Hạn       : num [1:10] 414448000000 46699000000 131652000000 133140000000 133140000000 ...
##  $ Tiền Gửi Khách Hàng   : num [1:10] 447503426000000 361675593000000 327196828000000 303581729000000 259236746000000 ...
##  $ Cho vay các TCTD khác : num [1:10] 4999952000000 3863000000000 6911000000000 5654006000000 NA ...
##  $ Chứng khoán Kinh Doanh: num [1:10] 7792742000000 1547000000 3245000000 960000000 502000000 ...
##  $ Tiền Vàng Ngoại Tệ    : num [1:10] 1370849000000 1897545000000 1878293000000 1619927000000 1754801000000 ...
##  $ Tiền Gửi NHNN         : num [1:10] 54763646000000 15145862000000 14352057000000 14806140000000 10163244000000 ...
##  $ TS Hữu Hình           : num [1:10] 734978000000 502854000000 536194000000 532986000000 498334000000 ...
##  $ TS Vô Hình            : num [1:10] 4494438000000 4451542000000 4442784000000 4333077000000 4319172000000 ...
##  $ Tài Sản Khác          : num [1:10] 35954267000000 54049318000000 37992688000000 29030323000000 30868165000000 ...
##  - attr(*, "spec")=
##   .. cols(
##   ..   Year = col_double(),
##   ..   `Tổng Tài Sản` = col_number(),
##   ..   `Chứng Khoán Đầu Tư` = col_number(),
##   ..   `Góp Vốn Dài Hạn` = col_number(),
##   ..   `Tiền Gửi Khách Hàng` = col_number(),
##   ..   `Cho vay các TCTD khác` = col_number(),
##   ..   `Chứng khoán Kinh Doanh` = col_number(),
##   ..   `Tiền Vàng Ngoại Tệ` = col_number(),
##   ..   `Tiền Gửi NHNN` = col_number(),
##   ..   `TS Hữu Hình` = col_number(),
##   ..   `TS Vô Hình` = col_number(),
##   ..   `Tài Sản Khác` = col_number()
##   .. )
##  - attr(*, "problems")=<externalptr>

str() (structure) là hàm trong R dùng để xem cấu trúc tổng quát của một đối tượng.

Khi chạy str(shb), R hiển thị:

  • Số dòng (rows) và số cột (columns),

  • Kiểu dữ liệu (class) của từng biến,

  • Một vài giá trị đầu tiên trong mỗi biến.

Dữ liệu có 10 dòng (tương ứng với 10 năm, từ 2014 đến 2023),

Có 12 cột, bao gồm:

Year: năm quan sát,

11 biến tài chính khác: Tổng Tài Sản, Chứng Khoán Đầu Tư, Góp Vốn Dài Hạn, Tiền Gửi Khách Hàng, Cho vay các TCTD khác, Chứng khoán Kinh Doanh, Tiền Vàng Ngoại Tệ, Tiền Gửi NHNN, TS Hữu Hình, TS Vô Hình, Tài Sản Khác.

Kiểu dữ liệu từng biến

Tất cả các biến đều có kiểu số thực (num), phù hợp cho các phép tính và phân tích định lượng trong R.

Biến Year được định dạng dưới dạng col_double(), thể hiện năm tài chính. Các biến còn lại như Tổng Tài Sản, Chứng Khoán Đầu Tư, Góp Vốn Dài Hạn, Tiền Gửi Khách Hàng, Cho vay TCTD khác, Chứng khoán Kinh Doanh, Tiền Vàng Ngoại Tệ, Tiền Gửi NHNN, TS Hữu Hình, TS Vô Hình, Tài Sản Khác đều được định dạng col_number(), đảm bảo thống nhất kiểu dữ liệu.

Cấu trúc dữ liệu rõ ràng, định dạng chuẩn, không xuất hiện lỗi hoặc giá trị ký tự lẫn vào số. Điều này giúp dễ dàng thực hiện các bước thống kê mô tả, phân tích xu hướng và trực quan hóa dữ liệu trong các phần sau của tiểu luận.

Nhìn chung, dữ liệu cung cấp cái nhìn toàn diện về cơ cấu tài sản của ngân hàng SHB trong 10 năm, là nền tảng cho việc đánh giá xu hướng tăng trưởng và biến động tài chính trong giai đoạn nghiên cứu.

2.1.8 Kiểm tra kiểu dữ liệu

sapply(shb, class)
##                   Year           Tổng Tài Sản     Chứng Khoán Đầu Tư 
##              "numeric"              "numeric"              "numeric" 
##        Góp Vốn Dài Hạn    Tiền Gửi Khách Hàng  Cho vay các TCTD khác 
##              "numeric"              "numeric"              "numeric" 
## Chứng khoán Kinh Doanh     Tiền Vàng Ngoại Tệ          Tiền Gửi NHNN 
##              "numeric"              "numeric"              "numeric" 
##            TS Hữu Hình             TS Vô Hình           Tài Sản Khác 
##              "numeric"              "numeric"              "numeric"

Câu lệnh sapply(shb, class) trong R được sử dụng để kiểm tra kiểu dữ liệu (class) của từng biến trong bộ dữ liệu shb. Hàm sapply() sẽ áp dụng hàm class cho tất cả các cột của shb và trả về một bảng liệt kê tên biến cùng kiểu dữ liệu tương ứng.

Kết quả cho thấy toàn bộ 12 biến trong bộ dữ liệu SHB đều có kiểu “numeric”. Điều này có nghĩa là tất cả các cột đều ở dạng số, có thể sử dụng trực tiếp cho các phép tính toán, phân tích thống kê và mô hình hóa trong R mà không cần chuyển đổi thêm.

Việc các biến đều ở dạng số thực (numeric) chứng tỏ dữ liệu đã được chuẩn hóa tốt, không chứa ký tự hoặc chuỗi văn bản, đảm bảo tính chính xác và nhất quán cho quá trình phân tích xu hướng, thống kê mô tả và trực quan hóa trong các bước tiếp theo của tiểu luận.

2.1.9 Kiểm tra NA cho mỗi cột

colSums(is.na(shb))
##                   Year           Tổng Tài Sản     Chứng Khoán Đầu Tư 
##                      0                      0                      0 
##        Góp Vốn Dài Hạn    Tiền Gửi Khách Hàng  Cho vay các TCTD khác 
##                      0                      0                      1 
## Chứng khoán Kinh Doanh     Tiền Vàng Ngoại Tệ          Tiền Gửi NHNN 
##                      0                      0                      0 
##            TS Hữu Hình             TS Vô Hình           Tài Sản Khác 
##                      0                      0                      0

Câu lệnh colSums(is.na(shb)) trong R được sử dụng để kiểm tra giá trị bị thiếu (NA) trong từng cột của bộ dữ liệu shb.

Hàm is.na() trả về giá trị TRUE nếu ô dữ liệu đó bị thiếu (NA), ngược lại là FALSE.

Hàm colSums() sẽ tính tổng số giá trị TRUE (tức là số lượng giá trị bị thiếu) trong mỗi cột. Kết quả cuối cùng là số lượng NA của từng biến trong toàn bộ tập dữ liệu.

Nhận xét:

Kết quả cho thấy hầu hết các biến trong bộ dữ liệu đều có giá trị 0, nghĩa là không có giá trị bị thiếu, ngoại trừ biến “Cho vay các TCTD khác” có 1 giá trị NA.

Nguyên nhân có thể do dữ liệu gốc không có thông tin trong năm đó. Rất có thể SHB không phát sinh hoạt động cho vay đối với các tổ chức tín dụng khác trong năm đó, nên báo cáo tài chính không ghi nhận hoặc để trống chỉ tiêu này.

Chính sách kế toán thay đổi qua các năm: Trong một số trường hợp, ngân hàng có thể thay đổi cách trình bày hoặc gộp chỉ tiêu kế toán, ví dụ: “Cho vay các TCTD khác” được gộp vào một khoản mục lớn hơn. Khi đó, chỉ tiêu riêng lẻ sẽ không có giá trị trong năm thay đổi, dẫn đến NA

Điều này chứng tỏ bộ dữ liệu tương đối đầy đủ và sạch, có thể sử dụng trực tiếp cho phân tích mà không cần xử lý thiếu dữ liệu phức tạp. Tuy nhiên, trước khi thực hiện các bước thống kê mô tả hoặc mô hình hóa, cần xem xét nguyên nhân giá trị NA duy nhất này và xử lý phù hợp

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

Trong quá trình xử lý và phân tích dữ liệu, việc kiểm tra các giá trị trùng lặp (duplicated) là một bước rất quan trọng để đảm bảo tính toàn vẹn và chính xác của dữ liệu. Các dòng trùng lặp có thể làm sai lệch kết quả thống kê, ảnh hưởng đến các phép tính trung bình, phương sai hoặc mô hình dự báo. Do đó, lệnh kiểm tra này giúp xác nhận rằng mỗi quan sát (mỗi năm trong bộ dữ liệu SHB) là duy nhất, không bị lặp lại.

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

duplicated(shb): Hàm này kiểm tra từng dòng trong bộ dữ liệu shb để xem dòng đó có trùng lặp với dòng trước đó hay không. → Trả về TRUE nếu dòng bị trùng, FALSE nếu không.

sum(duplicated(shb)): Hàm sum() cộng tất cả các giá trị TRUE, tức là đếm số dòng bị trùng lặp trong toàn bộ bảng dữ liệu.

Kết quả trả về là [1] 0, nghĩa là không có dòng dữ liệu nào bị trùng lặp trong bộ dữ liệu SHB. → Điều này cho thấy mỗi năm (Year) trong bộ dữ liệu là duy nhất và thông tin tài sản của ngân hàng được ghi nhận chính xác, không bị nhân đôi.

Bộ dữ liệu SHB đảm bảo tính toàn vẹn và hợp lệ, có thể sử dụng trực tiếp cho phân tích thống kê và mô hình hóa mà không cần loại bỏ hay xử lý trùng lặp.

2.1.11 Kiểm tra phạm vi năm

Xác định khoảng thời gian quan sát mà dữ liệu bao phủ — từ năm nào đến năm nào. Đây là một bước quan trọng trong phân tích dữ liệu chuỗi thời gian vì nó giúp người nghiên cứu biết được độ dài chuỗi, tính liên tục, và tính đại diện của dữ liệu theo thời gian.

range(shb$Year)
## [1] 2014 2023

range() trong R được dùng để trả về giá trị nhỏ nhất và lớn nhất của một vector số.

Dấu $ được sử dụng để truy cập vào một biến cụ thể trong bộ dữ liệu — ở đây là biến Year (năm).

→ Câu lệnh này sẽ cho biết năm đầu tiên và năm cuối cùng có trong dữ liệu SHB.

Kết quả trả về là [1] 2014 2023, cho thấy bộ dữ liệu SHB bao gồm 10 năm liên tiếp từ 2014 đến 2023.

→ Điều này chứng tỏ dữ liệu có tính liên tục theo thời gian, rất phù hợp cho các phân tích xu hướng, dự báo.

Ngoài ra, phạm vi 10 năm cũng giúp đánh giá được biến động dài hạn của tổng tài sản và cơ cấu tài sản của ngân hàng SHB, phản ánh rõ hơn sự thay đổi trong hoạt động tài chính qua từng giai đoạn.

2.1.12 Kiểm tra giá trị âm bất thường

Các biến như Tổng tài sản, Tiền gửi, Chứng khoán đầu tư, Góp vốn dài hạn,… đều là các chỉ tiêu có giá trị dương. Việc xuất hiện giá trị âm có thể cho thấy dữ liệu bị lỗi nhập liệu hoặc sai định dạng. Do đó, bước này giúp xác minh tính hợp lệ của dữ liệu trước khi phân tích sâu hơn.

library(dplyr)
shb %>% summarise(across(-Year, ~sum(.x < 0)))
## # A tibble: 1 × 11
##   `Tổng Tài Sản` `Chứng Khoán Đầu Tư` `Góp Vốn Dài Hạn` `Tiền Gửi Khách Hàng`
##            <int>                <int>             <int>                 <int>
## 1              0                    0                 0                     0
## # ℹ 7 more variables: `Cho vay các TCTD khác` <int>,
## #   `Chứng khoán Kinh Doanh` <int>, `Tiền Vàng Ngoại Tệ` <int>,
## #   `Tiền Gửi NHNN` <int>, `TS Hữu Hình` <int>, `TS Vô Hình` <int>,
## #   `Tài Sản Khác` <int>

%>%: toán tử pipe (đọc là “và sau đó”), chuyển dữ liệu qua bước tiếp theo.

summarise(): tạo bảng tóm tắt chứa kết quả tổng hợp.

-Year: bỏ cột “Year” (năm) vì không cần kiểm tra.

.x: đại diện cho từng cột còn lại trong dữ liệu.

sum(.x < 0): đếm số lượng giá trị nhỏ hơn 0 trong từng cột.

Nhận xét: Tất cả các biến trong kết quả đều có giá trị 0, nghĩa là không có giá trị âm nào trong dữ liệu.

Riêng cột “Cho vay các TCTD khác” hiển thị NA, điều này cho thấy trong cột này có ít nhất một giá trị bị thiếu (NA) nên không thể tính tổng điều kiện được.

Dữ liệu hợp lệ: không có giá trị âm, phù hợp với đặc điểm tài sản tài chính.Tuy nhiên, cần xử lý giá trị thiếu (NA) trong cột Cho vay các TCTD khác trước khi thực hiện các phân tích thống kê hoặc mô hình hóa tiếp theo.

2.2 Xử lý dữ liệu thô và mã hoá dữ liệu

2.2.1 Sao lưu dữ liệu gốc

Khi làm việc với dữ liệu trong R, ta thường cần sao lưu (backup) dữ liệu gốc trước khi thực hiện các bước xử lý, mã hóa hoặc biến đổi. Điều này giúp bảo toàn dữ liệu ban đầu trong trường hợp:

  • Có lỗi trong quá trình xử lý khiến dữ liệu bị thay đổi hoặc mất mát.

  • Muốn so sánh kết quả giữa dữ liệu gốc và dữ liệu đã xử lý.

  • Cần khôi phục dữ liệu về trạng thái ban đầu mà không cần nhập lại file.

Do đó, việc sao lưu dữ liệu là bước quan trọng trong quy trình phân tích dữ liệu chuyên nghiệp, giúp tránh rủi ro và dễ dàng kiểm soát quá trình biến đổi.

shb1 <- shb

Toán tử <- gán toàn bộ nội dung của shb sang biến mới shb1.

2.2.2 Chuẩn kiểu biến Year thành số nguyên

Trong quá trình nhập dữ liệu từ Excel hoặc CSV, cột năm đôi khi có thể bị đọc sai kiểu (ví dụ: dạng “số thực”, “ký tự” hoặc “chuỗi văn bản”). Nếu không chuyển đổi đúng kiểu, ta có thể gặp lỗi khi:

  • Thực hiện các phép so sánh hoặc lọc theo năm (ví dụ: Year > 2020),

  • Tính toán, nhóm dữ liệu (group_by(Year)),

  • Hoặc vẽ biểu đồ theo trục thời gian.

Do đó, việc chuyển “Year” sang kiểu số nguyên giúp dữ liệu nhất quán, dễ xử lý và phù hợp với các thao tác phân tích thống kê, trực quan hóa sau này.

shb1 <- shb1 %>% mutate(Year = as.integer(Year))

%>%: toán tử pipe (đọc là “và sau đó”), dùng để chuyển kết quả của bước trước sang bước sau.

mutate(): hàm trong dplyr, được dùng để thêm mới hoặc thay đổi giá trị của một biến.

as.integer(): hàm chuyển kiểu dữ liệu sang số nguyên.

Ở đây, nó biến đổi cột Year từ dạng chuỗi hoặc số thực → số nguyên (integer).

Kết quả sau khi chạy:

Cột Year của shb1 sẽ có kiểu dữ liệu integer,

Toàn bộ các giá trị năm (2014–2023) vẫn được giữ nguyên.

2.2.3 Sắp xếp theo thời gian tăng dần

Trong các phân tích tài chính, việc dữ liệu được sắp xếp theo thứ tự thời gian hợp lý là rất quan trọng vì:

  • Giúp dễ dàng quan sát xu hướng biến động của các chỉ tiêu tài chính qua các năm.

  • Là điều kiện cần để thực hiện các phép tính chuỗi thời gian (time series), như tính tăng trưởng, tỷ lệ thay đổi hay mô hình ARIMA.

  • Đảm bảo các biểu đồ và báo cáo thể hiện đúng thứ tự diễn tiến thực tế của các năm.

Nếu dữ liệu bị lộn xộn (ví dụ 2023 đứng trước 2014), kết quả phân tích hoặc biểu đồ sẽ sai lệch.

shb1 <- shb1 %>% arrange(Year)

arrange(Year): hàm trong dplyr giúp sắp xếp các dòng dữ liệu theo giá trị của biến “Year”.

Mặc định là sắp xếp tăng dần (từ năm nhỏ đến năm lớn).Nếu muốn sắp xếp giảm dần, có thể dùng arrange(desc(Year)).

Kết quả: bộ dữ liệu shb1 sẽ được sắp xếp theo thứ tự năm 2014 → 2023, đảm bảo tính tuần tự và logic thời gian.

2.2.4 Chuẩn hoá tên biến

Trong nhiều bộ dữ liệu nhập từ Excel hoặc CSV, tên biến thường chứa dấu tiếng Việt, khoảng trắng hoặc ký tự đặc biệt — khiến việc lập trình và gọi biến trong R dễ bị lỗi (ví dụ: Tổng Tài Sản, Chứng Khoán Đầu Tư).

Việc chuẩn hóa tên biến giúp:

  • Dễ dàng thao tác trong các hàm và biểu thức R (vì không còn dấu hoặc ký tự đặc biệt).

  • Đảm bảo tính thống nhất và chuyên nghiệp trong toàn bộ quy trình xử lý dữ liệu.

  • Tránh lỗi khi truy xuất, lọc hoặc vẽ biểu đồ (R không nhận diện được tên có dấu).

# Xem lại bảng tên biến
names(shb1)
##  [1] "Year"                   "Tổng Tài Sản"           "Chứng Khoán Đầu Tư"    
##  [4] "Góp Vốn Dài Hạn"        "Tiền Gửi Khách Hàng"    "Cho vay các TCTD khác" 
##  [7] "Chứng khoán Kinh Doanh" "Tiền Vàng Ngoại Tệ"     "Tiền Gửi NHNN"         
## [10] "TS Hữu Hình"            "TS Vô Hình"             "Tài Sản Khác"

Dùng để liệt kê danh sách tên các biến (cột) hiện có trong bộ dữ liệu.

Giúp kiểm tra xem có dấu, ký tự đặc biệt, hoặc khoảng trắng không.=> Tìm cách xử lý phù hợp

library(dplyr); library(readr)
# Chuẩn hoá tên bỏ dấu
simple_names <- function(x){
  x <- stringi::stri_trans_general(x, "Latin-ASCII")  # bỏ dấu
  x <- tolower(x)
  x <- gsub("[^a-z0-9]+","_", x)
  gsub("(^_|_$)", "", x)
}
shb1 <- shb
names(shb1) <- simple_names(names(shb1))
# Xem lại tên biến 
names(shb1)
##  [1] "year"                   "tong_tai_san"           "chung_khoan_dau_tu"    
##  [4] "gop_von_dai_han"        "tien_gui_khach_hang"    "cho_vay_cac_tctd_khac" 
##  [7] "chung_khoan_kinh_doanh" "tien_vang_ngoai_te"     "tien_gui_nhnn"         
## [10] "ts_huu_hinh"            "ts_vo_hinh"             "tai_san_khac"

stringi::stri_trans_general(x, “Latin-ASCII”): bỏ dấu tiếng Việt.

tolower(x): chuyển toàn bộ chữ hoa → chữ thường.

**gsub(“[^a-z0-9]+”, ““, x):** thay mọi ký tự không phải chữ hoặc số bằng dấu gạch dưới .

gsub(“(^|$)”, ““, x): xóa dấu gạch dưới ở đầu hoặc cuối tên biến nếu có.

names(shb1) <- simple_names(names(shb1)) Dòng này thay toàn bộ tên biến cũ bằng phiên bản đã chuẩn hóa.

Chuẩn hóa toàn bộ tên biến, loại bỏ dấu, khoảng trắng và ký tự đặc biệt.

Giúp tăng độ ổn định khi viết code R, đặc biệt trong các hàm phân tích, mô hình hóa và trực quan hóa.

Kết quả cuối cùng là một bộ dữ liệu có tên biến ngắn gọn, thống nhất và đúng chuẩn lập trình R.

2.2.5 Xử lý NA

Trong thực tế, khi thu thập dữ liệu tài chính hoặc kinh tế từ báo cáo nhiều năm, một số năm có thể bị thiếu dữ liệu do không công bố hoặc sai sót trong nhập liệu.Nếu không xử lý, giá trị NA có thể gây:

  • Lỗi khi tính toán (trung bình, hồi quy, tỷ lệ %…).

  • Làm sai kết quả khi phân tích thống kê hoặc mô hình hóa.

Vì vậy, cần thay thế giá trị bị thiếu bằng một giá trị hợp lý, giúp dữ liệu đầy đủ và phân tích được liên tục theo chuỗi thời gian.

Dữ liệu này có tính chuỗi thời gian (theo năm), nên giá trị các năm thường liên hệ chặt chẽ với nhau (ví dụ: tổng tài sản, dư nợ, tiền gửi… đều tăng/giảm dần qua các năm chứ không biến động đột ngột).

Khi chỉ thiếu 1 năm, việc lấy trung bình của 3 năm lân cận (năm trước 2 năm, năm trước 1 năm, và năm sau 1 năm) giúp ước lượng hợp lý, phản ánh xu hướng thật của chuỗi.

Cách này được gọi là nội suy (interpolation) – tức “ước lượng giá trị bị mất dựa vào xu hướng trước và sau”.

which(is.na(shb1$cho_vay_cac_tctd_khac))
## [1] 5
shb1 <- shb1 %>%
  mutate(
    cho_vay_cac_tctd_khac = ifelse(
      is.na(cho_vay_cac_tctd_khac),
      (
        lag(cho_vay_cac_tctd_khac, 2) + 
        lag(cho_vay_cac_tctd_khac, 1) + 
        lead(cho_vay_cac_tctd_khac, 1)
      ) / 3,
      cho_vay_cac_tctd_khac
    )
  )
  • Tìm vị trí NA:

is.na(…): tạo vector TRUE/FALSE cho biết phần tử nào đang NA.

which(…): trả về chỉ số hàng có giá trị TRUE (tức là vị trí NA)

Kết quả [1] 5 cho biết giá trị bị thiếu nằm ở hàng thứ 5 trong biến này.

  • Thay thế NA bằng bình quân các năm lân cận:

mutate(): hàm của dplyr dùng để tạo mới hoặc thay đổi giá trị biến trong dataframe.

ifelse(condition, value_if_true, value_if_false): Nếu điều kiện là đúng, thay thế giá trị theo công thức; Nếu sai, giữ nguyên giá trị cũ.

is.na(cho_vay_cac_tctd_khac) → kiểm tra ô nào bị thiếu.

Nếu bị thiếu → thay bằng trung bình của 3 giá trị lân cận:

lag(…, 2) → giá trị trước 2 năm,

lag(…, 1) → giá trị trước 1 năm,

lead(…, 1) → giá trị sau 1 năm.

Cuối cùng chia cho 3 để lấy giá trị trung bình xấp xỉ hợp lý.

Nếu không bị thiếu → giữ nguyên giá trị ban đầu.

Kết quả integer(0) có nghĩa là hiện tại trong cột cho_vay_cac_tctd_khac không còn bất kỳ giá trị NA nào nữa.

Dữ liệu trong cột cho_vay_cac_tctd_khac đã hoàn chỉnh, không còn thiếu dữ liệu.

Giờ đây, bộ dữ liệu shb1 đủ điều kiện để tiếp tục các bước phân tích tiếp theo như thống kê mô tả, trực quan hóa hay mô hình hóa chuỗi thời gian.

2.2.6 Tạo biến phần trăm cho Góp vốn dài hạn

Chuẩn hóa dữ liệu để so sánh quy mô của khoản “Góp vốn dài hạn” (long-term investments) với tổng tài sản của ngân hàng theo từng năm.

Các chỉ tiêu như “Tổng tài sản” hay “Góp vốn dài hạn” thường có giá trị tuyệt đối rất lớn, nên khó đánh giá nếu chỉ nhìn con số gốc.

Việc tính tỷ trọng (%) giúp: So sánh mức đóng góp tương đối của khoản “Góp vốn dài hạn” giữa các năm; Nhìn rõ xu hướng tăng hay giảm tỷ trọng trong cơ cấu tài sản; Dễ dàng so sánh với các ngân hàng khác có quy mô tài sản khác nhau.

→ Vì vậy, việc tạo thêm biến phần trăm Góp vốn dài hạn là cần thiết cho phân tích cơ cấu tài sản.

shb1 <- shb1 %>%
mutate(pct_GopVon = gop_von_dai_han / tong_tai_san * 100)

shb1 <- shb1 %>%→ Sử dụng toán tử pipe (%>%) của gói dplyr để nối mạch lệnh, nghĩa là lấy dữ liệu shb1 rồi áp dụng hàm mutate cho nó.

mutate() → Dùng để tạo hoặc cập nhật một cột mới trong bảng dữ liệu.

pct_GopVon = … → Tạo biến mới có tên là pct_GopVon.

gop_von_dai_han / tong_tai_san * 100 → Tính tỷ lệ phần trăm giữa giá trị “Góp vốn dài hạn” và “Tổng tài sản”:

2.2.7 Tạo các tỷ trọng khác

Chuẩn hóa toàn bộ các khoản mục tài sản về cùng một đơn vị đo lường tương đối (tỷ trọng %), để:

So sánh mức độ đóng góp của từng loại tài sản vào tổng tài sản qua các năm;

Phân tích cơ cấu tài sản — xem ngân hàng đang tập trung vào loại tài sản nào (ví dụ: tiền gửi, chứng khoán đầu tư, tài sản hữu hình, v.v.);

Dễ dàng theo dõi xu hướng biến động (tăng/giảm tỷ trọng) giữa các nhóm tài sản theo thời gian, thay vì chỉ nhìn số tuyệt đối.

→ Việc tạo thêm các biến phần trăm là bước rất quan trọng trong phân tích tài chính, giúp nhận định chiến lược đầu tư, thanh khoản và mức độ rủi ro của ngân hàng.

shb1 <- shb1 %>%
mutate(
pct_tvnt = tien_vang_ngoai_te / tong_tai_san * 100,
pct_Tien_gui_NHNN = tien_gui_nhnn / tong_tai_san * 100,
pct_ChoVay_TCTD_khac = cho_vay_cac_tctd_khac / tong_tai_san * 100,
pct_CK_kinh_doanh = chung_khoan_kinh_doanh / tong_tai_san * 100,
pct_CK_dau_tu = chung_khoan_dau_tu / tong_tai_san * 100,
pct_TS_huu_hinh = ts_huu_hinh / tong_tai_san * 100,
pct_TS_vo_hinh = ts_vo_hinh / tong_tai_san * 100,
pct_TS_khac = tai_san_khac / tong_tai_san * 100
)

shb1 <- shb1 %>% → Dùng toán tử pipe %>% (thuộc gói dplyr) để nối lệnh, nghĩa là: lấy bảng dữ liệu shb1 rồi áp dụng hàm mutate lên nó.

mutate(…) → Dùng để tạo mới hoặc cập nhật nhiều cột cùng lúc trong bảng dữ liệu.

pct_tvnt = tien_vang_ngoai_te / tong_tai_san * 100→ Tạo cột mới tên pct_tvnt, thể hiện tỷ trọng Tiền vàng ngoại tệ trong Tổng tài sản (theo %).

Các dòng sau tương tự:

pct_Tien_gui_NHNN: Tỷ trọng Tiền gửi tại Ngân hàng Nhà nước

pct_ChoVay_TCTD_khac: Tỷ trọng Cho vay các TCTD khác

pct_CK_kinh_doanh: Tỷ trọng Chứng khoán kinh doanh

pct_CK_dau_tu: Tỷ trọng Chứng khoán đầu tư

pct_TS_huu_hinh: Tỷ trọng Tài sản hữu hình

pct_TS_vo_hinh: Tỷ trọng Tài sản vô hình

pct_TS_khac: Tỷ trọng Tài sản khác

Bảng dữ liệu shb1 sẽ có thêm 8 cột mới thể hiện tỷ lệ phần trăm (%) của từng loại tài sản trong tổng tài sản qua các năm.

Các biến tỷ trọng này giúp:

Dễ dàng vẽ biểu đồ cơ cấu tài sản (dạng stacked bar hoặc pie chart);

So sánh xu hướng: ví dụ, tỷ trọng “chứng khoán đầu tư” có tăng dần → ngân hàng chuyển hướng sang đầu tư nhiều hơn;

Đánh giá thanh khoản: tỷ trọng “tiền gửi” hay “tiền vàng ngoại tệ” cao → khả năng thanh khoản tốt;

Nhận định rủi ro: tỷ trọng “chứng khoán kinh doanh” hoặc “cho vay TCTD khác” cao → rủi ro thị trường hoặc tín dụng cao hơn.

2.2.8 Tính tăng trưởng hàng năm

Mục tiêu của thao tác là đo lường mức tăng trưởng hằng năm của các chỉ tiêu tài chính (như Tổng tài sản, Góp vốn dài hạn, Chứng khoán đầu tư).

Việc phân tích giá trị tuyệt đối (như “Tổng tài sản = 630.500 tỷ”) chỉ cho biết quy mô, không phản ánh tốc độ thay đổi qua thời gian.

Tính tăng trưởng (% change) giúp:

So sánh được giữa các năm, bất kể quy mô khác nhau;

Nhận biết xu hướng: tăng ổn định, đột biến hay suy giảm;

Là cơ sở để đánh giá hiệu quả hoạt động, tốc độ mở rộng tài sản và danh mục đầu tư của ngân hàng.

=> Đây là một bước quan trọng trong phân tích động (dynamic analysis) – chuyển từ mô tả tĩnh sang đánh giá biến động qua thời gian.

shb1 <- shb1 %>%
arrange(year) %>%
mutate(
g_Tong_TS = (tong_tai_san / lag(tong_tai_san) - 1) * 100,
g_GopVon = (gop_von_dai_han / lag(gop_von_dai_han) - 1) * 100,
g_CK = (chung_khoan_dau_tu / lag(chung_khoan_dau_tu) - 1) * 100
)

shb1 <- shb1 %>% → Dùng toán tử pipe (%>%) để nối các thao tác trong chuỗi xử lý.

arrange(year)→ Sắp xếp dữ liệu theo năm tăng dần, đảm bảo khi dùng hàm lag() thì giá trị của năm trước được xác định đúng thứ tự thời gian.

mutate(…)→ Tạo thêm các biến mới trong bảng dữ liệu để lưu trữ kết quả tăng trưởng.

lag(x)→ Lấy giá trị của biến x ở năm liền trước (ví dụ, khi tính năm 2022 thì lag(tong_tai_san) sẽ là giá trị năm 2021).

(x / lag(x) - 1) * 100 → Đây là công thức tính tốc độ tăng trưởng (%) giữa hai năm liên tiếp

Bảng shb1 sẽ có thêm 3 cột thể hiện tốc độ tăng trưởng năm sau so với năm trước.

Các giá trị dương ( > 0 ) cho biết mức tăng, âm ( < 0 ) thể hiện sự giảm sút.

Có thể dùng các cột này để:

  • Vẽ biểu đồ tăng trưởng (% theo thời gian);

  • So sánh mức tăng của từng khoản mục;

  • Đánh giá giai đoạn ngân hàng mở rộng nhanh hay chững lại.

2.2.9 Tính CAGR 2014-2023 cho Tổng tài sản

Khi phân tích tài chính theo chuỗi thời gian nhiều năm, ta thường muốn biết: Trung bình mỗi năm tài sản của ngân hàng tăng bao nhiêu phần trăm; Tốc độ tăng trưởng dài hạn, đã loại bỏ biến động ngắn hạn giữa các năm.

CAGR (Compound Annual Growth Rate) — tốc độ tăng trưởng kép bình quân năm — là chỉ tiêu được sử dụng phổ biến trong tài chính vì:

  • Nó phản ánh xu hướng dài hạn ổn định hơn so với việc chỉ so sánh năm đầu – năm cuối hoặc các biến động từng năm;

  • Dễ dàng so sánh hiệu quả tăng trưởng giữa các ngân hàng hoặc các chỉ tiêu khác nhau (vốn, tài sản, lợi nhuận…);

  • Thường được dùng trong báo cáo chiến lược, đầu tư hoặc đánh giá hiệu quả kinh doanh.

=> Việc tính CAGR của “Tổng tài sản” giúp xác định tốc độ tăng trưởng trung bình hàng năm của SHB giai đoạn 2014–2023.

cagr <- function(x) {
x <- na.omit(x)
if(length(x) < 2) return(NA)
n <- length(x)
first <- x[1]; last <- x[n]
((last/first)^(1/(n-1)) - 1) * 100
}
cagr_TongTS <- cagr(shb1$tong_tai_san)

cagr <- function(x) → Tạo một hàm tự định nghĩa tên là cagr để tính tốc độ tăng trưởng kép cho một chuỗi giá trị x.

x <- na.omit(x) → Loại bỏ các giá trị bị thiếu (NA) để đảm bảo phép tính chính xác.

if(length(x) < 2) return(NA) → Nếu dữ liệu có ít hơn 2 điểm (ví dụ chỉ có 1 năm), thì không thể tính CAGR ⇒ trả về giá trị NA.

n <- length(x) → Đếm số lượng năm (chu kỳ) trong chuỗi dữ liệu. Ví dụ: 2014–2023 có 10 năm, tức n = 10.

first <- x[1]; last <- x[n] → Lấy giá trị đầu tiên (x[1] = Tổng tài sản năm 2014) và giá trị cuối cùng (x[n] = Tổng tài sản năm 2023).

((last/first)^(1/(n-1)) - 1) * 100 → Đây là công thức tính CAGR (%)

=> Ý nghĩa: Mức tăng trưởng bình quân mỗi năm nếu tài sản tăng đều theo cấp số nhân từ giá trị đầu đến giá trị cuối.

cagr_TongTS <- cagr(shb1$tong_tai_san) → Áp dụng hàm cagr() vừa định nghĩa để tính tốc độ tăng trưởng kép cho biến tong_tai_san trong bảng dữ liệu shb1.

Sau khi chạy lệnh, biến cagr_TongTS sẽ lưu giá trị tốc độ tăng trưởng kép trung bình năm của Tổng tài sản SHB giai đoạn 2014–2023.

Ví dụ nếu kết quả là CAGR = 15.75, nghĩa là:

Trong giai đoạn 2014–2023, Tổng tài sản của SHB tăng bình quân 15,75% mỗi năm (theo tốc độ tăng trưởng kép).

2.2.10 Phân tổ theo giai đoạn (2 kỳ 2014–2018, 2019–2023)

Phân tích so sánh giữa hai giai đoạn hoạt động của ngân hàng SHB, cụ thể:

  • Giai đoạn 2014–2018: thời kỳ đầu trong chu kỳ nghiên cứu, quy mô tài sản còn nhỏ, tốc độ tăng trưởng ban đầu;

  • Giai đoạn 2019–2023: thời kỳ mở rộng nhanh hơn, phản ánh sự phát triển gần đây.

Việc chia dữ liệu theo giai đoạn giúp: So sánh sự thay đổi về quy mô trung bình (Tổng tài sản bình quân);Đánh giá tốc độ tăng trưởng bình quân từng giai đoạn;Làm rõ xu hướng thay đổi trong chiến lược phát triển hoặc chính sách mở rộng của ngân hàng.

Đây là một kỹ thuật thống kê mô tả nâng cao thường dùng trong các báo cáo tài chính để nhận diện xu thế qua từng giai đoạn cụ thể thay vì chỉ nhìn toàn kỳ.

shb1 <- shb1 %>%
  mutate(giai_doan = ifelse(year <= 2018, "2014-2018", "2019-2023"))

grp_gd <- shb1 %>%
  group_by(giai_doan) %>%
  summarise(
    so_nam = n(),
    tong_ts_bq = mean(tong_tai_san, na.rm = TRUE),
    g_tong_ts_bq = mean(g_Tong_TS, na.rm = TRUE)
  )

mutate(…) → Tạo biến mới “giai_doan” để chia dữ liệu thành hai nhóm:

Nếu năm ≤ 2018 → gán giá trị “2014-2018”

Nếu năm > 2018 → gán “2019-2023”

Kết quả: mỗi dòng dữ liệu (mỗi năm) được gắn nhãn giai đoạn tương ứng.

group_by(giai_doan) → Gom nhóm dữ liệu theo từng giai đoạn để tính toán riêng cho từng nhóm.

summarise(…) → Tính toán các chỉ tiêu thống kê tổng hợp cho mỗi giai đoạn.

so_nam = n()

→ Đếm số lượng năm trong từng giai đoạn. → Dự kiến: mỗi giai đoạn có 5 năm (2014–2018 và 2019–2023).

tong_ts_bq = mean(tong_tai_san, na.rm = TRUE) → Tính Tổng tài sản bình quân trong từng giai đoạn.

mean() = hàm trung bình cộng.

na.rm = TRUE = bỏ qua giá trị NA (nếu có).

g_tong_ts_bq = mean(g_Tong_TS, na.rm = TRUE)→ Tính tốc độ tăng trưởng bình quân năm (%) của Tổng tài sản trong từng giai đoạn.

2.2.11 Phân tổ theo quy mô Tổng tài sản (tứ phân vị)

Phân loại các năm theo quy mô Tổng tài sản của ngân hàng SHB, nhằm:

  • So sánh đặc điểm của các nhóm quy mô tài sản khác nhau (nhỏ – trung bình – lớn – rất lớn);

  • Phân tích mối quan hệ giữa quy mô ngân hàng và cơ cấu tài sản (ví dụ: tỷ trọng góp vốn dài hạn thay đổi ra sao khi tài sản tăng);

  • Giúp nhận diện xu hướng phát triển: khi quy mô tài sản tăng, cơ cấu đầu tư có thay đổi hợp lý không?

Đây là một kỹ thuật gọi là phân tổ theo tứ phân vị (quartile grouping) — tức chia dữ liệu thành 4 nhóm bằng nhau dựa trên giá trị Tổng tài sản, để so sánh giữa các nhóm quy mô.

cuts <- quantile(shb1$tong_tai_san, probs = c(0, .25, .5, .75, 1), na.rm = TRUE)
shb1 <- shb1 %>%
  mutate(quy_mo_ts = cut(tong_tai_san, breaks = unique(cuts), include.lowest = TRUE))

grp_quymo <- shb1 %>%
  group_by(quy_mo_ts) %>%
  summarise(
    so_nam = n(),
    tong_ts_tb = mean(tong_tai_san, na.rm = TRUE),
    ty_trong_gop_von_tb = mean(pct_GopVon, na.rm = TRUE)
  )

Hàm quantile() chia dải dữ liệu thành các mốc phần trăm (percentile) theo thứ tự tăng dần.

0 = giá trị nhỏ nhất;

.25 = tứ phân vị thứ nhất (Q1) – 25% thấp nhất;

.5 = trung vị (Q2);

.75 = tứ phân vị thứ ba (Q3) – 75% thấp nhất;

1 = giá trị lớn nhất.

Kết quả được lưu vào biến cuts, tạo ra 4 khoảng giá trị đại diện cho 4 mức quy mô Tổng tài sản.

Hàm mutate() tạo biến mới quy_mo_ts để phân loại mỗi năm vào nhóm quy mô tương ứng.

cut() chia dữ liệu thành các nhóm (bins) dựa trên các mốc breaks = cuts.

include.lowest = TRUE đảm bảo giá trị nhỏ nhất cũng được đưa vào nhóm đầu tiên.

unique(cuts) loại bỏ trùng lặp (phòng khi có giá trị trùng nhau ở biên).

group_by(quy_mo_ts) → Gom nhóm dữ liệu theo từng mức quy mô tài sản.

summarise() → Tính các chỉ tiêu thống kê cho từng nhóm:

so_nam = n() → Số năm thuộc nhóm đó.

tong_ts_tb = mean(tong_tai_san, na.rm = TRUE) → Tổng tài sản trung bình của nhóm.

ty_trong_gop_von_tb = mean(pct_GopVon, na.rm = TRUE) → Tỷ trọng góp vốn dài hạn trung bình (%) trong nhóm.

2.2.12 Phân tổ theo tốc độ tăng Tổng tài sản (YoY)

Phân loại các năm theo tốc độ tăng trưởng Tổng tài sản (YoY – Year over Year) của ngân hàng SHB, để:

  • Đánh giá mức độ biến động tăng trưởng hằng năm của tài sản;

  • So sánh đặc điểm cơ cấu tài sản giữa các nhóm năm có tốc độ tăng khác nhau;

  • Phát hiện xu hướng: khi tốc độ tăng cao, SHB có xu hướng đầu tư mạnh hơn vào các hạng mục nào (ví dụ: chứng khoán đầu tư).

Việc phân nhóm này giúp nhà phân tích nhận biết các chu kỳ tăng trưởng và điều chỉnh trong hoạt động mở rộng tài sản của SHB.

shb1 <- shb1 %>%
  mutate(nhom_tang_truong = case_when(
    is.na(g_Tong_TS) ~ NA_character_,
    g_Tong_TS < 0 ~ "Giảm",
    g_Tong_TS <= 10 ~ "Tăng nhẹ (0-10%)",
    TRUE ~ "Tăng mạnh (>10%)"
  ))

grp_yoy <- shb1 %>%
  group_by(nhom_tang_truong) %>%
  summarise(
    so_nam = n(),
    tong_ts_tb = mean(tong_tai_san, na.rm = TRUE),
    ck_dau_tu_tb = mean(chung_khoan_dau_tu, na.rm = TRUE)
  )

mutate(…): Tạo biến mới nhom_tang_truong để phân loại từng năm theo mức tăng trưởng Tổng tài sản (g_Tong_TS).

is.na(g_Tong_TS) ~ NA_character_ → Nếu giá trị tăng trưởng bị thiếu (NA), gán nhóm NA (bỏ qua khi tính trung bình).

g_Tong_TS < 0 ~ “Giảm” → Nếu tốc độ tăng trưởng âm (nhỏ hơn 0%) ⇒ nhóm Giảm.

g_Tong_TS <= 10 ~ “Tăng nhẹ (0–10%)” → Nếu tốc độ tăng trưởng nằm trong khoảng 0–10% ⇒ nhóm Tăng nhẹ.

TRUE ~ “Tăng mạnh (>10%)” → Nếu lớn hơn 10% ⇒ nhóm Tăng mạnh.

ết quả: mỗi dòng dữ liệu (tương ứng với một năm) được gắn nhãn tăng trưởng: Giảm / Tăng nhẹ (0–10%) / Tăng mạnh (>10%)

roup_by(nhom_tang_truong): Gom nhóm dữ liệu theo từng loại tốc độ tăng trưởng (3 nhóm).

summarise(…): Tính các chỉ tiêu trung bình cho từng nhóm tăng trưởng:

so_nam = n() → Đếm số năm thuộc mỗi nhóm.

tong_ts_tb = mean(tong_tai_san, na.rm = TRUE) → Tính tổng tài sản bình quân trong nhóm (đại diện cho quy mô trung bình của giai đoạn tăng trưởng đó).

ck_dau_tu_tb = mean(chung_khoan_dau_tu, na.rm = TRUE) → Tính mức đầu tư bình quân vào chứng khoán trong nhóm. → Giúp xem khi tài sản tăng nhanh, đầu tư vào chứng khoán có tăng tương ứng không.

2.2.13 Phân tổ cơ cấu: tỷ trọng CK đầu tư

Phân loại các năm của ngân hàng SHB theo tỷ trọng chứng khoán đầu tư (tức phần trăm chứng khoán đầu tư trong tổng tài sản), nhằm:

  • Đánh giá mức độ đầu tư tài chính của ngân hàng qua các năm;

  • So sánh quy mô tài sản và các khoản cho vay TCTD khác giữa các nhóm tỷ trọng chứng khoán khác nhau;

  • Phân tích xu hướng cơ cấu tài sản: khi SHB tăng tỷ trọng đầu tư chứng khoán, các khoản mục khác (như cho vay, tiền gửi,…) có biến động ra sao.

Việc phân tổ theo tỷ trọng đầu tư chứng khoán giúp hiểu rõ chiến lược phân bổ tài sản của ngân hàng — tức xem họ đang ưu tiên đầu tư tài chính hay tập trung cho vay.

shb1 <- shb1 %>%
  mutate(nhom_ck = cut(pct_CK_dau_tu,
                       breaks = c(-Inf, 5, 10, 20, Inf),
                       labels = c("<5%", "5-10%", "10-20%", ">20%")))

grp_ck <- shb1 %>%
  group_by(nhom_ck) %>%
  summarise(
    so_nam = n(),
    tong_ts_tb = mean(tong_tai_san, na.rm = TRUE),
    cho_vay_tctd_tb = mean(cho_vay_cac_tctd_khac, na.rm = TRUE)
  )

mutate(nhom_ck = cut(…)): Tạo biến mới nhom_ck để phân loại từng năm theo mức tỷ trọng chứng khoán đầu tư (pct_CK_dau_tu).

Sử dụng hàm cut() để chia dữ liệu thành các khoảng giá trị (bins):

breaks = c(-Inf, 5, 10, 20, Inf)Xác định ranh giới các nhóm: <5%, 5–10%, 10–20%, >20%

labels = c(“<5%”, “5-10%”, “10-20%”, “>20%”) Đặt nhãn tên nhóm tương ứng

-Inf và Inf Đại diện cho giá trị nhỏ hơn 5% hoặc lớn hơn 20% (đảm bảo bao phủ toàn bộ dữ liệu)

ết quả: mỗi dòng dữ liệu (mỗi năm) được phân vào 1 trong 4 nhóm tỷ trọng đầu tư chứng khoán.

group_by(nhom_ck) Gom nhóm dữ liệu theo từng mức tỷ trọng chứng khoán đầu tư (nhom_ck), để tính trung bình riêng cho từng nhóm.

summarise(…) Tính các chỉ tiêu tổng hợp cho từng nhóm:

so_nam = n() Số năm thuộc nhóm tỷ trọng đó

tong_ts_tb = mean(tong_tai_san, na.rm = TRUE) Tổng tài sản bình quân của nhóm

cho_vay_tctd_tb = mean(cho_vay_cac_tctd_khac, na.rm = TRUE) Giá trị bình quân cho vay TCTD khác của nhóm

na.rm = TRUE giúp bỏ qua giá trị bị thiếu (NA) để kết quả không bị sai lệch.

2.2.14 Bảng tần suất nhanh (năm theo giai đoạn & nhóm tăng trưởng)

tab_gd <- table(shb1$giai_doan)
tab_yoy <- table(shb1$nhom_tang_truong)
tab_ck  <- table(shb1$nhom_ck)

grp_gd; grp_quymo; grp_yoy; grp_ck
## # A tibble: 2 × 4
##   giai_doan so_nam tong_ts_bq g_tong_ts_bq
##   <chr>      <int>      <dbl>        <dbl>
## 1 2014-2018      5    2.43e14         17.7
## 2 2019-2023      5    4.93e14         14.4
## # A tibble: 4 × 4
##   quy_mo_ts           so_nam tong_ts_tb ty_trong_gop_von_tb
##   <fct>                <int>      <dbl>               <dbl>
## 1 [1.69e+14,2.47e+14]      3    2.03e14              0.144 
## 2 (2.47e+14,3.44e+14]      2    3.05e14              0.0679
## 3 (3.44e+14,4.83e+14]      2    3.89e14              0.0344
## 4 (4.83e+14,6.31e+14]      3    5.63e14              0.0334
## # A tibble: 3 × 4
##   nhom_tang_truong so_nam tong_ts_tb ck_dau_tu_tb
##   <chr>             <int>      <dbl>        <dbl>
## 1 Tăng mạnh (>10%)      8    3.70e14      2.66e13
## 2 Tăng nhẹ (0-10%)      1    5.51e14      3.30e13
## 3 <NA>                  1    1.69e14      1.35e13
## # A tibble: 3 × 4
##   nhom_ck so_nam tong_ts_tb cho_vay_tctd_tb
##   <fct>    <int>      <dbl>           <dbl>
## 1 <5%          1    5.07e14         6.91e12
## 2 5-10%        8    3.57e14         5.86e12
## 3 10-20%       1    3.23e14         9.13e10
tab_gd; tab_yoy; tab_ck
## 
## 2014-2018 2019-2023 
##         5         5
## 
## Tăng mạnh (>10%) Tăng nhẹ (0-10%) 
##                8                1
## 
##    <5%  5-10% 10-20%   >20% 
##      1      8      1      0

tab_gd <- table(shb1$giai_doan) Tạo bảng tần suất cho biến giai_doan. → Đếm xem có bao nhiêu quan sát (năm) trong mỗi giai đoạn (ví dụ “2014–2018”, “2019–2023”).

tab_yoy <- table(shb1$nhom_tang_truong) Tạo bảng tần suất cho nhóm tăng trưởng (nhom_tang_truong), → Đếm số năm rơi vào các nhóm “Giảm”, “Tăng nhẹ (0–10%)”, “Tăng mạnh (>10%)”.

tab_ck <- table(shb1$nhom_ck) Tạo bảng tần suất cho nhóm tỷ trọng chứng khoán đầu tư (nhom_ck), → Cho biết mỗi mức (<5%, 5–10%, 10–20%, >20%) xuất hiện bao nhiêu lần.

grp_gd; grp_quymo; grp_yoy; grp_ck Các biến này hiển thị lại những bảng đã tổng hợp trước đó bằng group_by() và summarise() (ở các bước bạn làm trước như phần giai đoạn, quy mô, tăng trưởng, cơ cấu CK đầu tư).

tab_gd; tab_yoy; tab_ck In ra ba bảng tần suất vừa tạo ở trên.

Nhận xét kết quả phân tích theo giai đoạn

Kết quả thống kê cho thấy bộ dữ liệu được chia thành hai giai đoạn: 2014–2018 và 2019–2023, mỗi giai đoạn gồm 5 năm quan sát.

Quy mô tài sản bình quân (tổng tài sản bình quân – tong_ts_bq) tăng mạnh từ khoảng 2,43 × 10¹⁴ đồng trong giai đoạn 2014–2018 lên 4,93 × 10¹⁴ đồng trong giai đoạn 2019–2023. Điều này phản ánh xu hướng mở rộng quy mô tổng tài sản của Ngân hàng SHB trong những năm gần đây, cho thấy năng lực huy động vốn và mở rộng hoạt động được cải thiện đáng kể.

Tuy nhiên, tốc độ tăng trưởng bình quân năm của tổng tài sản (g_tong_ts_bq) lại giảm nhẹ từ 17,67% xuống 14,38%. ➜ Dù quy mô tăng nhanh, tốc độ tăng trưởng có xu hướng chậm lại, thể hiện sự ổn định hơn trong giai đoạn sau khi SHB đã đạt được quy mô lớn, đồng thời chịu ảnh hưởng của bối cảnh kinh tế vĩ mô thắt chặt tín dụng và tăng cường quản trị rủi ro sau năm 2020.

Nhìn chung, SHB duy trì được đà tăng trưởng tích cực với quy mô tài sản ngày càng lớn, song bước sang giai đoạn 2019–2023, tốc độ tăng dần ổn định hơn — cho thấy sự chuyển dịch từ giai đoạn mở rộng sang giai đoạn củng cố và phát triển bền vững.

Nhận xét kết quả theo quy mô tổng tài sản

  • Phân bổ dữ liệu: Mỗi nhóm quy mô có từ 2–3 năm, đảm bảo dữ liệu được chia khá đều giữa các khoảng giá trị tổng tài sản.

  • Xu hướng quy mô: Tổng tài sản bình quân tăng dần qua các nhóm, phản ánh sự mở rộng quy mô hoạt động của SHB qua các năm.

  • Mối quan hệ giữa quy mô và tỷ trọng góp vốn dài hạn: Khi quy mô tổng tài sản tăng lên, tỷ trọng góp vốn dài hạn (ty_trong_gop_von_tb) lại giảm dần rõ rệt — từ 14,45% ở nhóm nhỏ nhất xuống chỉ còn 3,34% ở nhóm lớn nhất.

=> Điều này cho thấy SHB có xu hướng giảm tỷ trọng đầu tư dài hạn khi tổng tài sản tăng, tập trung nhiều hơn vào hoạt động tín dụng và tài sản sinh lợi ngắn hạn để tối ưu hiệu quả sử dụng vốn.

Mối quan hệ ngược chiều này phản ánh chiến lược chuyển dịch cơ cấu tài sản: từ việc đầu tư vốn dài hạn sang đa dạng hóa tài sản ngắn hạn và trung hạn nhằm cải thiện thanh khoản và hiệu quả sinh lời

Nhận xét kết quả theo tốc độ tăng trưởng tổng tài sản (YoY)

  • Phân bổ dữ liệu: Trong 10 năm nghiên cứu, phần lớn các năm (8/10) thuộc nhóm tăng mạnh, thể hiện xu hướng mở rộng tích cực của SHB qua các giai đoạn.

  • So sánh quy mô bình quân: Năm thuộc nhóm tăng nhẹ (0–10%) có quy mô tổng tài sản bình quân cao nhất, đạt khoảng 550 nghìn tỷ đồng, cao hơn đáng kể so với nhóm tăng mạnh.

=> Điều này phản ánh rằng khi quy mô đã lớn, tốc độ tăng trưởng có xu hướng chậm lại, do nền tảng tài sản đã mở rộng đáng kể.

  • Chứng khoán đầu tư: Bình quân giá trị chứng khoán đầu tư cũng cao hơn rõ rệt ở các nhóm có tăng trưởng thấp, cho thấy trong các giai đoạn ổn định hơn, SHB có xu hướng tăng đầu tư vào tài sản tài chính an toàn thay vì mở rộng cho vay.

Phân tích theo tốc độ tăng trưởng tổng tài sản cho thấy giai đoạn 2014–2023 của SHB chủ yếu thuộc nhóm tăng mạnh (>10%), phản ánh xu thế mở rộng nhanh. Tuy nhiên, năm có mức tăng nhẹ (0–10%) lại ghi nhận quy mô tài sản và đầu tư chứng khoán cao nhất, cho thấy khi ngân hàng đạt đến quy mô lớn, tốc độ tăng trưởng chậm lại nhưng cơ cấu tài sản chuyển dần sang hướng an toàn và ổn định hơn.

Nhận xét kết quả theo cơ cấu tỷ trọng chứng khoán đầu tư

  • Phân bố dữ liệu: Nhóm 5–10% chiếm phần lớn (8/10 năm), thể hiện tỷ trọng chứng khoán đầu tư của SHB qua các năm tập trung chủ yếu ở mức trung bình — không quá thấp cũng không quá cao so với tổng tài sản.

  • Quy mô tài sản: Mức tổng tài sản bình quân cao nhất lại rơi vào nhóm <5% với hơn 506 nghìn tỷ đồng, trong khi nhóm 10–20% (tỷ trọng đầu tư cao nhất) lại có quy mô tài sản thấp nhất.

=> Điều này phản ánh xu hướng ngân hàng có quy mô càng lớn thì tỷ trọng đầu tư vào chứng khoán càng nhỏ, do ưu tiên hoạt động tín dụng và dịch vụ tài chính cốt lõi hơn là đầu tư gián tiếp.

  • Cho vay các TCTD khác: Giá trị cho vay TCTD khác bình quân cũng cao nhất ở nhóm <5%, tương đồng với quy mô tài sản.

➜ Gợi ý rằng SHB có xu hướng phân bổ vốn vào các khoản cho vay liên ngân hàng và tài sản ngắn hạn nhiều hơn khi quy mô tổng tài sản tăng.

Khi phân tổ theo tỷ trọng chứng khoán đầu tư, SHB chủ yếu duy trì tỷ trọng ở mức trung bình (5–10%) trong 8/10 năm quan sát. Quy mô tài sản lớn thường đi kèm với tỷ trọng đầu tư thấp, cho thấy ngân hàng tập trung vốn vào các hoạt động cốt lõi như cho vay và huy động, thay vì đầu tư tài chính. Điều này phản ánh chiến lược quản lý rủi ro an toàn và hướng đến tăng trưởng bền vững.

Nhận xét kết quả bảng tần suất nhanh

  • Phân bố thời gian: Dữ liệu được chia đều giữa hai giai đoạn (2014–2018 và 2019–2023), đảm bảo tính cân bằng và cho phép so sánh xu hướng qua thời kỳ.

  • Tăng trưởng tài sản: Phần lớn các năm (8/10) thuộc nhóm tăng mạnh trên 10%, phản ánh giai đoạn tăng trưởng tích cực của SHB. Chỉ 1 năm có mức tăng nhẹ (0–10%) và 1 năm không tính được (năm đầu chuỗi).

➜ Điều này cho thấy ngân hàng duy trì tốc độ tăng tài sản cao và ổn định trong phần lớn thời gian nghiên cứu.

  • Tỷ trọng chứng khoán đầu tư: 8/10 năm nằm trong nhóm 5–10%, nghĩa là tỷ trọng đầu tư chứng khoán của SHB tương đối ổn định ở mức trung bình. Chỉ có 1 năm thấp hơn 5% và 1 năm cao hơn 10%, không có năm nào vượt 20%.

➜ Điều này thể hiện chính sách đầu tư thận trọng, tập trung vào hoạt động tín dụng và tài sản cốt lõi thay vì đầu tư rủi ro.

Kết quả thống kê tần suất cho thấy dữ liệu phân bố cân đối giữa hai giai đoạn 2014–2018 và 2019–2023. Hầu hết các năm nằm trong nhóm tăng mạnh về tổng tài sản (>10%) và duy trì tỷ trọng chứng khoán đầu tư ổn định ở mức trung bình (5–10%). Điều này phản ánh chiến lược tăng trưởng nhanh nhưng an toàn của SHB, với định hướng đầu tư ổn định và quản trị rủi ro hiệu quả.

2.2.15 Lưu dữ liệu sạch

Sau khi hoàn tất việc xử lý, tạo biến và phân tổ dữ liệu, việc lưu lại dữ liệu sạch (clean data) là bước quan trọng để: Lưu trữ bộ dữ liệu đã qua xử lý, đảm bảo không cần chạy lại toàn bộ quy trình ở các lần sau; Giúp dễ dàng sử dụng cho các phân tích tiếp theo (thống kê, mô hình ARIMA, GARCH, v.v.); Giữ tính nhất quán và truy xuất được nguồn dữ liệu cuối cùng đã được làm sạch.

write_csv(shb1, "SHB_taisan_clean_updated.csv")
glimpse(shb1); cagr_TongTS
## Rows: 10
## Columns: 28
## $ year                   <dbl> 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021,…
## $ tong_tai_san           <dbl> 169035546000000, 204704140000000, 2339477400000…
## $ chung_khoan_dau_tu     <dbl> 13471098000000, 17316651000000, 18846623000000,…
## $ gop_von_dai_han        <dbl> 321032000000, 303409000000, 222949000000, 21546…
## $ tien_gui_khach_hang    <dbl> 123227619000000, 148828876000000, 1665762170000…
## $ cho_vay_cac_tctd_khac  <dbl> 8841433000000, 10651971000000, 8592759000000, 9…
## $ chung_khoan_kinh_doanh <dbl> 31828000000, 54378000000, 40899000000, 10500000…
## $ tien_vang_ngoai_te     <dbl> 801433000000, 1917860000000, 1291694000000, 144…
## $ tien_gui_nhnn          <dbl> 3346049000000, 4362518000000, 2718757000000, 37…
## $ ts_huu_hinh            <dbl> 383906000000, 361018000000, 424046000000, 42542…
## $ ts_vo_hinh             <dbl> 3721844000000, 3695248000000, 3538006000000, 35…
## $ tai_san_khac           <dbl> 14382821000000, 16876587000000, 16088151000000,…
## $ pct_GopVon             <dbl> 0.189919817, 0.148218302, 0.095298634, 0.075334…
## $ pct_tvnt               <dbl> 0.4741210, 0.9368936, 0.5521293, 0.5057682, 0.5…
## $ pct_Tien_gui_NHNN      <dbl> 1.979494, 2.131133, 1.162122, 1.308576, 1.33902…
## $ pct_ChoVay_TCTD_khac   <dbl> 5.23051702, 5.20359334, 3.67293952, 0.03376629,…
## $ pct_CK_kinh_doanh      <dbl> 0.0188291757, 0.0265641916, 0.0174821095, 0.000…
## $ pct_CK_dau_tu          <dbl> 7.969388, 8.459356, 8.055912, 7.415111, 14.8563…
## $ pct_TS_huu_hinh        <dbl> 0.22711554, 0.17636087, 0.18125672, 0.14874441,…
## $ pct_TS_vo_hinh         <dbl> 2.2018114, 1.8051652, 1.5123061, 1.2331359, 1.3…
## $ pct_TS_khac            <dbl> 8.508755, 8.244380, 6.876814, 9.213922, 6.40364…
## $ g_Tong_TS              <dbl> NA, 21.101239, 14.285788, 22.253834, 13.029585,…
## $ g_GopVon               <dbl> NA, -5.489484, -26.518660, -3.356822, -9.142088…
## $ g_CK                   <dbl> NA, 28.546693, 8.835265, 12.529253, 126.457028,…
## $ giai_doan              <chr> "2014-2018", "2014-2018", "2014-2018", "2014-20…
## $ quy_mo_ts              <fct> "[1.69e+14,2.47e+14]", "[1.69e+14,2.47e+14]", "…
## $ nhom_tang_truong       <chr> NA, "Tăng mạnh (>10%)", "Tăng mạnh (>10%)", "Tă…
## $ nhom_ck                <fct> 5-10%, 5-10%, 5-10%, 5-10%, 10-20%, 5-10%, 5-10…
## [1] 15.75055

write_csv(shb1, “SHB_taisan_clean_updated.csv”) → Ghi (xuất) toàn bộ dữ liệu khung shb1 ra file CSV có tên “SHB_taisan_clean_updated.csv”. Đây là phiên bản dữ liệu đã được làm sạch, tính toán, phân nhóm và thêm các biến tỷ trọng, tăng trưởng, CAGR, v.v.

glimpse(shb1) → Hiển thị nhanh cấu trúc và tóm tắt dữ liệu:

Có 10 dòng (Rows) tương ứng với 10 năm dữ liệu (2014–2023).

Có 28 cột (Columns), bao gồm các biến gốc và biến mới tạo (như pct_GopVon, g_Tong_TS, g_CK, nhom_ck, giai_doan…).

cagr_TongTS → Hiển thị giá trị CAGR (Compound Annual Growth Rate) của tổng tài sản giai đoạn 2014–2023.

Nhận xét

Cấu trúc dữ liệu Dữ liệu có 10 năm (2014–2023), 28 biến, đã bao gồm các biến tỷ trọng, tăng trưởng và phân nhóm.

Giá trị CAGR Tổng tài sản SHB có tốc độ tăng trưởng kép bình quân ≈ 15,75%/năm, phản ánh xu hướng mở rộng ổn định và mạnh mẽ trong giai đoạn 10 năm.

Các biến tăng trưởng (g_Tong_TS, g_GopVon, g_CK) Xuất hiện giá trị NA ở năm đầu tiên (2014) do không có năm trước để so sánh — đây là hiện tượng hợp lý trong tính toán tăng trưởng.

Chất lượng dữ liệu Không còn giá trị thiếu (NA) quan trọng, dữ liệu được chuẩn hóa và sẵn sàng cho phân tích mô hình hoặc trực quan hóa.

2.3 Thống kê mô tả

2.3.1 Số quan sát theo năm

Câu lệnh này đếm số lượng quan sát (record, dòng dữ liệu) tương ứng với mỗi năm trong cột year của bộ dữ liệu shb1 và kết quả sẽ là bảng tần số (frequency table) thể hiện số lần mỗi năm xuất hiện trong biến year.

table(shb1$year)
## 
## 2014 2015 2016 2017 2018 2019 2020 2021 2022 2023 
##    1    1    1    1    1    1    1    1    1    1

table(shb1\(year)** được sử dụng để đếm số lượng quan sát xuất hiện trong từng năm của bộ dữ liệu shb1. Trong đó, **shb1** là tên của bộ dữ liệu, ký hiệu **\) dùng để truy cập đến một cột cụ thể bên trong bộ dữ liệu. Hàm table() trong R có chức năng tạo bảng tần số, nghĩa là đếm số lần xuất hiện của từng giá trị khác nhau trong một biến.

Khi kết hợp lại, câu lệnh này giúp ta xác định có bao nhiêu dòng dữ liệu (quan sát) cho mỗi năm, qua đó kiểm tra được dữ liệu có liên tục hay bị thiếu năm nào không.

Kết quả cho thấy dữ liệu có 10 quan sát, tương ứng với các năm từ 2014-2023, mỗi năm xuất hiện đúng một lần. Bộ dữ liệu được thu thập theo chuỗi thời gian hàng năm và không có năm nào bị thiếu hoặc trùng lặp. Nhờ đó, người phân tích có thể đánh giá sự phát triển của ngân hàng SHB trong giai đoạn 2014–2023.

2.3.2 Thống kê cơ bản biến chính

Đây là bước mô tả thống kê cốt lõi để nắm mức điển hình (trung vị, trung bình) và độ phân tán (tứ phân vị, độ lệch chuẩn) của Tổng tài sản theo năm. Với chuỗi năm chỉ có 10 quan sát, bộ chỉ tiêu này giúp ta: kiểm tra quy mô điển hình của tài sản, đo độ biến động qua các năm, phát hiện lệch (qua min–max và khoảng tứ phân vị). Đây là nền tảng trước khi sang trực quan hóa xu hướng và phân tích cơ cấu.

desc_main <- shb1 %>%
  summarise(
    n = n(),
    tong_ts_min = min(tong_tai_san, na.rm=TRUE),
    tong_ts_q1  = quantile(tong_tai_san, .25, na.rm=TRUE),
    tong_ts_med = median(tong_tai_san, na.rm=TRUE),
    tong_ts_q3  = quantile(tong_tai_san, .75, na.rm=TRUE),
    tong_ts_max = max(tong_tai_san, na.rm=TRUE),
    tong_ts_mean= mean(tong_tai_san, na.rm=TRUE),
    tong_ts_sd  = sd(tong_tai_san, na.rm=TRUE)
  )
desc_main
## # A tibble: 1 × 8
##       n tong_ts_min tong_ts_q1 tong_ts_med tong_ts_q3 tong_ts_max tong_ts_mean
##   <int>       <dbl>      <dbl>       <dbl>      <dbl>       <dbl>        <dbl>
## 1    10     1.69e14    2.47e14     3.44e14    4.83e14     6.31e14      3.68e14
## # ℹ 1 more variable: tong_ts_sd <dbl>

Giải thích code

desc_main <- shb1 %>% summarise(…) tạo một bảng tóm tắt (1 dòng) từ dữ liệu shb1.

n = n() đếm số quan sát (số năm).

min(…, na.rm=TRUE)max(…) lấy giá trị nhỏ nhất/lớn nhất của tong_tai_san; na.rm=TRUE bỏ qua NA để không làm hỏng phép tính.

quantile(…, .25/.75, na.rm=TRUE) trả Q1 (25%) và Q3 (75%), dùng để tính IQR = Q3 − Q1 phản ánh độ phân tán phần “giữa” của dữ liệu, ít nhạy cảm với ngoại lệ.

median(…, na.rm=TRUE) là trung vị, mức điển hình vững chắc trước ngoại lệ, rất phù hợp với dữ liệu tài chính có thể lệch phải.

mean(…, na.rm=TRUE) là trung bình, hữu ích để so với trung vị nhằm nhận diện lệch.

sd(…, na.rm=TRUE) là độ lệch chuẩn, đo mức biến động quanh trung bình, có thể dùng để tính hệ số biến thiên CV = sd/mean.

Nhận xét: Các mức min–max và việc mean > median cho thấy Tổng tài sản tăng đáng kể theo thời gian; giá trị cực đại (~630.5 nghìn tỷ) nhiều khả năng rơi vào các năm cuối chuỗi, phản ánh mở rộng quy mô bảng cân đối của SHB. IQR lớn và CV ~42% hàm ý quá trình mở rộng không đồng đều giữa các năm (có giai đoạn tăng nhanh hơn, ví dụ sau COVID-19 2022–2023). Với bức tranh quy mô như vậy, các phân tích tiếp theo nên: (i) vẽ xu hướng Tổng tài sản qua năm (đường thời gian), (ii) chuẩn hóa theo tỷ trọng để xem cơ cấu dịch chuyển giữa Chứng khoán đầu tư, Cho vay TCTD khác, Tiền gửi NHNN, Tài sản hữu hình/vô hình và (iii) đối chiếu bối cảnh vĩ mô – quy định an toàn vốn, thanh khoản – để lý giải các thay đổi trong cơ cấu tài sản (ví dụ giai đoạn ưu tiên thanh khoản sẽ thấy tỷ trọng Tiền gửi NHNN/Tiền-Vàng-Ngoại tệ tăng; giai đoạn mở rộng tín dụng sẽ thấy cho vay và chứng khoán tăng tỷ trọng).

2.3.3 IQR & hệ số biến thiên (CV)

Đoạn code này được sử dụng để tính hai chỉ tiêu đo mức độ biến động của biến “Tổng tài sản” theo thời gian: IQR (khoảng tứ phân vị): đo độ phân tán của nhóm 50% dữ liệu trung tâm, giúp xem tài sản của SHB dao động mạnh hay yếu.

CV (hệ số biến thiên): đo mức độ dao động tương đối so với giá trị trung bình, cho biết tổng tài sản của ngân hàng biến động bao nhiêu phần trăm quanh mức trung bình qua các năm.

Hai chỉ tiêu này rất quan trọng trong phân tích xu hướng tài chính, vì chúng cho biết mức độ ổn định và tốc độ tăng trưởng của quy mô tài sản — giúp đánh giá xem ngân hàng tăng trưởng bền vững hay biến động thất thường.

iqr_ts <- IQR(shb1$tong_tai_san, na.rm = TRUE)
cv_ts  <- sd(shb1$tong_tai_san, na.rm = TRUE) / mean(shb1$tong_tai_san, na.rm = TRUE)
iqr_ts
## [1] 236159819000000
cv_ts
## [1] 0.4211487

Giải thích code

Hàm IQR() tính khoảng tứ phân vị (Q3 - Q1) của biến tong_tai_san (Tổng tài sản).

Tham số na.rm = TRUE yêu cầu bỏ qua giá trị bị thiếu (NA) khi tính toán.

sd() tính độ lệch chuẩn của tổng tài sản (mức dao động tuyệt đối quanh trung bình).

mean() tính giá trị trung bình của tổng tài sản. Khi chia sd / mean, ta được hệ số biến thiên (CV) thể hiện tỷ lệ biến động tương đối của dữ liệu.

IQR = 236.16 nghìn tỷ VND cho thấy phần lớn giá trị tổng tài sản của SHB tập trung trong khoảng 247–483 nghìn tỷ đồng (giữa Q1 và Q3).

→ Điều này chứng tỏ mức tăng quy mô tài sản khá lớn, phản ánh giai đoạn mở rộng nhanh của ngân hàng trong 10 năm qua.

CV ≈ 42.1% là mức dao động tương đối cao, nghĩa là quy mô tổng tài sản thay đổi mạnh qua các năm, không ổn định tuyệt đối.

→ Về mặt kinh tế, điều này phản ánh chiến lược tăng trưởng mạnh mẽ nhưng tiềm ẩn biến động, đặc biệt trong các năm chịu ảnh hưởng bởi yếu tố vĩ mô (như dịch COVID-19 2020–2021 hoặc giai đoạn phục hồi 2022–2023).

Tổng hợp hai chỉ tiêu: IQR lớn và CV > 30% cho thấy SHB có quá trình mở rộng quy mô tài sản nhanh, nhưng chưa ổn định, phù hợp với bối cảnh một ngân hàng thương mại cổ phần đang tăng trưởng mạnh mẽ.

Kết quả này giúp củng cố cho phân tích xu hướng ở các phần sau — cho thấy tổng tài sản SHB có xu hướng tăng đáng kể, phản ánh chiến lược mở rộng hoạt động, tăng đầu tư và nâng cao năng lực tài chính trong giai đoạn 2014–2023.

2.3.4 Top 3 năm có Tổng tài sản cao nhất

Để xác định 3 năm có Tổng tài sản lớn nhất trong giai đoạn 2014–2023. Kết quả giúp: kiểm tra đỉnh quy mô tài sản, xác nhận xu hướng tăng cuối chuỗi, chọn mốc năm tiêu biểu để phân tích sâu cơ cấu tài sản.

top_ts <- shb1 %>% arrange(desc(tong_tai_san)) %>% select(year, tong_tai_san) %>% head(3)
top_ts
## # A tibble: 3 × 2
##    year tong_tai_san
##   <dbl>        <dbl>
## 1  2023      6.31e14
## 2  2022      5.51e14
## 3  2021      5.07e14

Giải thích code

arrange(desc(…)): sắp xếp giảm dần để các giá trị lớn nhất nằm trên cùng.

select(…): chọn cột cần trình bày.

head(3): lấy Top 3 giá trị lớn nhất.

Nhận xét: Top 3 năm có Tổng tài sản cao nhất: 2023 ≈ 630.5 nghìn tỷ, 2022 ≈ 550.9 nghìn tỷ, 2021 ≈ 506.6 nghìn tỷ → ba vị trí cao nhất đều rơi vào các năm cuối chuỗi, khẳng định xu hướng tăng quy mô mạnh mẽ của SHB. Tốc độ tăng giữa các đỉnh liên tiếp khá đáng kể (2021→2022→2023), phù hợp với bức tranh mở rộng bảng cân đối sau giai đoạn COVID-19, khi hệ thống ngân hàng cải thiện tăng trưởng tín dụng và thanh khoản. Về ý nghĩa kinh tế: đỉnh tài sản tập trung ở 2021–2023 thường đi kèm (i) gia tăng cho vay và đầu tư chứng khoán/giấy tờ có giá, (ii) tăng dự trữ thanh khoản như tiền gửi NHNN/tiền-vàng-ngoại tệ, (iii) có thể kéo theo dịch chuyển cơ cấu (tỷ trọng tài sản sinh lời tăng). Đây là các năm nên đào sâu cơ cấu tỷ trọng để xem nhóm nào đóng góp chính cho mức kỷ lục 2023.

2.3.5 Top 3 năm có tỷ trọng CK đầu tư cao nhất

Đoạn code này được dùng để xác định 3 năm có tỷ trọng Chứng khoán đầu tư cao nhất trong tổng tài sản của SHB. Mục đích là:

Xem năm nào ngân hàng tăng mạnh đầu tư vào chứng khoán, đánh giá sự thay đổi cơ cấu tài sản qua thời gian, giúp nhận diện chiến lược đầu tư của SHB trong giai đoạn nhất định. Việc chọn Top 3 năm cho thấy giai đoạn đỉnh điểm về đầu tư tài chính, từ đó phân tích nguyên nhân, xu hướng và bối cảnh kinh tế của các năm đó.

top_ck <- shb1 %>% arrange(desc(pct_CK_dau_tu)) %>% select(year, pct_CK_dau_tu) %>% head(3)
top_ck
## # A tibble: 3 × 2
##    year pct_CK_dau_tu
##   <dbl>         <dbl>
## 1  2018         14.9 
## 2  2015          8.46
## 3  2016          8.06

Giải thích code

arrange(desc(pct_CK_dau_tu)) là sắp xếp các năm theo thứ tự giảm dần của biến pct_CK_dau_tu (tỷ trọng chứng khoán đầu tư).

select(year, pct_CK_dau_tu) là giữ lại hai cột cần hiển thị: năm và tỷ trọng chứng khoán đầu tư.

head(3) là lấy 3 dòng đầu tiên, tức là 3 năm có tỷ trọng chứng khoán đầu tư cao nhất.

Nhận xét:

Tỷ trọng chứng khoán đầu tư cao vào năm 2018 có thể phản ánh chiến lược đầu tư mở rộng danh mục tài chính của SHB trong bối cảnh thị trường chứng khoán Việt Nam tăng trưởng mạnh, lãi suất liên ngân hàng ổn định, và môi trường đầu tư thuận lợi.

Giai đoạn 2015–2016 cũng cho thấy xu hướng đa dạng hóa tài sản, khi SHB tăng đầu tư vào chứng khoán để tận dụng cơ hội sinh lời ngắn hạn thay vì chỉ tập trung vào tín dụng truyền thống. Sau giai đoạn đó, khi nền kinh tế biến động mạnh (như dịch COVID-19, kiểm soát tín dụng, biến động lãi suất), ngân hàng có thể giảm dần đầu tư tài chính để ưu tiên an toàn vốn và thanh khoản.

2.3.6 Tỷ trọng bình quân các cấu phần

Đoạn code này giúp tổng hợp tỷ trọng trung bình của từng nhóm tài sản, qua đó mô tả được bức tranh tổng thể về cơ cấu tài sản của SHB giai đoạn 2014–2023: tập trung chính vào hoạt động cho vay, duy trì một phần đáng kể trong đầu tư chứng khoán và tài sản khác, đồng thời đảm bảo thanh khoản và an toàn tài chính qua các khoản tiền gửi tại NHNN.

w_avg <- shb1 %>%
  summarise(
    w_gopvon = mean(pct_GopVon, na.rm=TRUE),
    w_ckdt   = mean(pct_CK_dau_tu, na.rm=TRUE),
    w_ckkd   = mean(pct_CK_kinh_doanh, na.rm=TRUE),
    w_tvnt   = mean(pct_tvnt, na.rm=TRUE),
    w_tgnhnn = mean(pct_Tien_gui_NHNN, na.rm=TRUE),
    w_tshh   = mean(pct_TS_huu_hinh, na.rm=TRUE),
    w_tsvh   = mean(pct_TS_vo_hinh, na.rm=TRUE),
    w_tskhac = mean(pct_TS_khac, na.rm=TRUE)
  )
w_avg
## # A tibble: 1 × 8
##   w_gopvon w_ckdt w_ckkd w_tvnt w_tgnhnn w_tshh w_tsvh w_tskhac
##      <dbl>  <dbl>  <dbl>  <dbl>    <dbl>  <dbl>  <dbl>    <dbl>
## 1   0.0738   7.56  0.130  0.486     2.86  0.146   1.27     7.77

Giải thích code

summarise() giúp tạo một bảng tóm tắt với các chỉ tiêu trung bình.

mean(…, na.rm = TRUE) là tính giá trị trung bình của mỗi biến (bỏ qua giá trị NA nếu có).

w_gopvon, w_ckdt, … là tên viết tắt cho tỷ trọng bình quân của từng nhóm tài sản.

Nhận xét: Tỷ trọng chứng khoán đầu tư (7.56%) và tài sản khác (7.77%) là hai nhóm lớn nhất trong các khoản mục phụ, cho thấy đây là hai cấu phần phụ quan trọng trong danh mục tài sản. Tiền gửi tại NHNN (2.86%) chiếm tỷ trọng ổn định, phản ánh nhu cầu duy trì thanh khoản và dự trữ bắt buộc. Các nhóm như góp vốn dài hạn, chứng khoán kinh doanh, tài sản hữu hình – vô hình chiếm tỷ trọng nhỏ (dưới 2%), thể hiện mức đầu tư hạn chế vào các tài sản cố định và tài sản tài chính rủi ro cao.

Nhận xét kinh tế: Cơ cấu này cho thấy SHB tập trung tài sản chủ yếu vào hoạt động tín dụng, còn các khoản đầu tư tài chính và tài sản phi tài chính chỉ đóng vai trò hỗ trợ. Tỷ trọng chứng khoán đầu tư ở mức khá cao phản ánh chiến lược duy trì danh mục đầu tư ổn định, sinh lãi an toàn bên cạnh hoạt động cho vay. Tỷ trọng tiền gửi NHNN hợp lý cho thấy SHB tuân thủ tốt các yêu cầu thanh khoản và an toàn vốn của Ngân hàng Nhà nước. Tài sản khác chiếm tỷ trọng tương đối lớn (7.77%) có thể bao gồm các khoản phải thu, tài sản chờ xử lý hoặc công cụ phái sinh, cần được theo dõi kỹ vì đây là nhóm có rủi ro tiềm ẩn cao nếu tăng nhanh qua các năm.

2.3.7 Tốc độ tăng trưởng YoY bình quân (một số cấu phần)

Đoạn code này được sử dụng để tính tốc độ tăng trưởng trung bình hằng năm của một số cấu phần tài sản quan trọng của Ngân hàng SHB trong giai đoạn 2014–2023, gồm: Tổng tài sản (g_Tong_TS), Góp vốn dài hạn (g_GopVon), Chứng khoán đầu tư (g_CK).

Mục đích là để đo lường tốc độ tăng trưởng bình quân của từng nhóm tài sản qua các năm, giúp nhận diện xu hướng phát triển và sự thay đổi trong cơ cấu tài sản của ngân hàng.

yoy_avg <- shb1 %>%
  summarise(
    yoy_ts   = mean(g_Tong_TS, na.rm=TRUE),
    yoy_gv   = mean(g_GopVon, na.rm=TRUE),
    yoy_ck   = mean(g_CK, na.rm=TRUE)
  )
yoy_avg
## # A tibble: 1 × 3
##   yoy_ts yoy_gv yoy_ck
##    <dbl>  <dbl>  <dbl>
## 1   15.8   71.7   18.9

Giải thích code

Hàm summarise() được dùng để tạo một bảng tóm tắt chỉ gồm ba biến đại diện cho tốc độ tăng trưởng bình quân của từng cấu phần. Bên trong hàm, mean() được sử dụng để tính giá trị trung bình của từng biến tăng trưởng.

Tham số na.rm = TRUE có chức năng loại bỏ các giá trị bị thiếu (NA) nhằm đảm bảo kết quả tính toán chính xác. Toàn bộ các thao tác được nối với nhau bằng toán tử %>%, giúp cho việc viết các bước xử lý dữ liệu trở nên gọn gàng và dễ hiểu hơn.

Nhận xét:

Tổng tài sản của SHB tăng trung bình 15.84%/năm, mức tăng khá cao và ổn định cho một ngân hàng thương mại, thể hiện xu hướng mở rộng quy mô liên tục.

Góp vốn dài hạn có tốc độ tăng đột biến (≈ 71.7%/năm), cao gấp nhiều lần so với các cấu phần khác, cho thấy ngân hàng có giai đoạn tăng mạnh đầu tư vào các công ty con, công ty liên kết hoặc góp vốn chiến lược.

Chứng khoán đầu tư tăng trung bình ≈ 18.9%/năm, thể hiện sự tăng cường đầu tư tài chính bên cạnh hoạt động tín dụng truyền thống.

2.3.8 Tốc độ tăng trưởng kép (CAGR) của các cấu phần tài sản

Đoạn code này được viết nhằm tính toán tốc độ tăng trưởng kép hằng năm (CAGR) của một số cấu phần tài sản của Ngân hàng SHB trong giai đoạn 2014–2023.

CAGR giúp phản ánh tốc độ tăng trưởng trung bình mỗi năm của một chỉ tiêu trong suốt giai đoạn, có tính đến yếu tố lũy tiến qua các năm, từ đó thể hiện xu hướng phát triển ổn định hơn so với tốc độ tăng trưởng năm đơn lẻ (YoY).

Trong bối cảnh phân tích cơ cấu và xu hướng tài sản, việc tính CAGR giúp xác định mức độ tăng trưởng dài hạn của các khoản mục như Tổng tài sản, Góp vốn dài hạn, và Chứng khoán đầu tư, qua đó đánh giá khả năng mở rộng quy mô cũng như chiến lược đầu tư của ngân hàng.

cagr <- function(x){
  x <- na.omit(x); if(length(x)<2) return(NA_real_)
  n <- length(x); (x[n]/x[1])^(1/(n-1)) - 1
}
cagr_ts   <- cagr(shb1$tong_tai_san)
cagr_gv   <- cagr(shb1$gop_von_dai_han)
cagr_ckdt <- cagr(shb1$chung_khoan_dau_tu)
cagr_ts
## [1] 0.1575055
cagr_gv
## [1] 0.02878503
cagr_ckdt
## [1] 0.1011477

Giải thích code

Hàm cagr <- function(x){…} được tạo ra để tính tốc độ tăng trưởng kép cho một chuỗi dữ liệu bất kỳ.

Dòng x <- na.omit(x) có tác dụng loại bỏ các giá trị bị thiếu (NA) để tránh lỗi khi tính toán.

Câu lệnh if(length(x)<2) return(NA_real_) dùng để kiểm tra điều kiện tối thiểu: nếu chuỗi dữ liệu có ít hơn hai giá trị thì không thể tính CAGR và hàm sẽ trả về NA. Tiếp theo, n <- length(x) xác định số năm (hay số quan sát) trong chuỗi dữ liệu. Biểu thức chính (x[n]/x[1])^(1/(n-1)) - 1 là công thức chuẩn của CAGR, trong đó x[1] là giá trị đầu kỳ và x[n] là giá trị cuối kỳ.

Về mặt thống kê:Các kết quả này cho thấy Tổng tài sản của SHB tăng trưởng mạnh mẽ và ổn định nhất, phản ánh quá trình mở rộng quy mô hoạt động của ngân hàng trong suốt giai đoạn 2014–2023. Tốc độ tăng trưởng kép của Chứng khoán đầu tư cũng khá cao (~10%), cho thấy ngân hàng có xu hướng gia tăng đầu tư tài chính song song với hoạt động tín dụng truyền thống. Ngược lại, Góp vốn dài hạn tăng rất chậm, chỉ khoảng 2,9%/năm, thể hiện sự thận trọng trong việc mở rộng đầu tư dài hạn vào công ty con hoặc lĩnh vực ngoài ngân hàng.

Về ý nghĩa kinh tế: Xu hướng này phù hợp với bối cảnh ngành ngân hàng giai đoạn 2014–2023, khi các ngân hàng thương mại (trong đó có SHB) tập trung tăng trưởng quy mô tài sản, mở rộng tín dụng, và đầu tư chứng khoán để đa dạng hóa thu nhập và đảm bảo thanh khoản, trong khi vẫn duy trì mức đầu tư dài hạn ở mức an toàn nhằm hạn chế rủi ro vốn.

2.3.9 Hệ số tương quan giữa các loại tài sản

Đoạn code này được sử dụng để tính hệ số tương quan giữa các loại tài sản trong cơ cấu tài sản của Ngân hàng SHB giai đoạn 2014–2023. Mục tiêu chính là xác định mối quan hệ tuyến tính giữa các nhóm tài sản, tức là khi một loại tài sản tăng, thì các loại tài sản khác có xu hướng tăng cùng chiều (tương quan dương) hay ngược chiều (tương quan âm).

Việc phân tích ma trận tương quan giúp nhận biết mức độ phụ thuộc và sự dịch chuyển cơ cấu tài sản của ngân hàng.

numset <- shb1 %>%
  select(tong_tai_san, gop_von_dai_han, chung_khoan_dau_tu,
         chung_khoan_kinh_doanh, tien_gui_nhnn, tien_vang_ngoai_te,
         cho_vay_cac_tctd_khac, ts_huu_hinh, ts_vo_hinh, tai_san_khac)
cor_mat <- cor(numset, use = "pairwise.complete.obs")
cor_mat
##                        tong_tai_san gop_von_dai_han chung_khoan_dau_tu
## tong_tai_san              1.0000000      -0.1909627          0.5109094
## gop_von_dai_han          -0.1909627       1.0000000         -0.2353983
## chung_khoan_dau_tu        0.5109094      -0.2353983          1.0000000
## chung_khoan_kinh_doanh    0.5892674       0.6552177          0.2105671
## tien_gui_nhnn             0.8020484       0.4046144          0.2888118
## tien_vang_ngoai_te        0.3591603      -0.5559915          0.4977523
## cho_vay_cac_tctd_khac    -0.2962594       0.3113043         -0.6647697
## ts_huu_hinh               0.8905725       0.1485433          0.4974447
## ts_vo_hinh                0.8444237      -0.3273695          0.6469484
## tai_san_khac              0.8667142      -0.5227235          0.3703854
##                        chung_khoan_kinh_doanh tien_gui_nhnn tien_vang_ngoai_te
## tong_tai_san                       0.58926743    0.80204841          0.3591603
## gop_von_dai_han                    0.65521771    0.40461442         -0.5559915
## chung_khoan_dau_tu                 0.21056706    0.28881177          0.4977523
## chung_khoan_kinh_doanh             1.00000000    0.94296183         -0.2117598
## tien_gui_nhnn                      0.94296183    1.00000000         -0.0374352
## tien_vang_ngoai_te                -0.21175984   -0.03743520          1.0000000
## cho_vay_cac_tctd_khac             -0.03244406   -0.06135768         -0.2508320
## ts_huu_hinh                        0.81494539    0.92990040          0.1022471
## ts_vo_hinh                         0.35008344    0.59246994          0.4822409
## tai_san_khac                       0.21594431    0.47749652          0.4595880
##                        cho_vay_cac_tctd_khac ts_huu_hinh ts_vo_hinh
## tong_tai_san                     -0.29625936   0.8905725  0.8444237
## gop_von_dai_han                   0.31130434   0.1485433 -0.3273695
## chung_khoan_dau_tu               -0.66476966   0.4974447  0.6469484
## chung_khoan_kinh_doanh           -0.03244406   0.8149454  0.3500834
## tien_gui_nhnn                    -0.06135768   0.9299004  0.5924699
## tien_vang_ngoai_te               -0.25083198   0.1022471  0.4822409
## cho_vay_cac_tctd_khac             1.00000000  -0.2781529 -0.2831440
## ts_huu_hinh                      -0.27815290   1.0000000  0.7594434
## ts_vo_hinh                       -0.28314405   0.7594434  1.0000000
## tai_san_khac                     -0.32558128   0.5663420  0.7206228
##                        tai_san_khac
## tong_tai_san              0.8667142
## gop_von_dai_han          -0.5227235
## chung_khoan_dau_tu        0.3703854
## chung_khoan_kinh_doanh    0.2159443
## tien_gui_nhnn             0.4774965
## tien_vang_ngoai_te        0.4595880
## cho_vay_cac_tctd_khac    -0.3255813
## ts_huu_hinh               0.5663420
## ts_vo_hinh                0.7206228
## tai_san_khac              1.0000000

Giải thích code

select(): chọn ra các cột (biến) định lượng cần tính tương quan, gồm: tổng tài sản, chứng khoán đầu tư, chứng khoán kinh doanh, cho vay, tiền gửi NHNN, tiền – vàng – ngoại tệ, tài sản hữu hình, tài sản vô hình, tài sản khác, và góp vốn dài hạn.

cor(): tính ma trận hệ số tương quan Pearson giữa các biến số trong numset.

use = “pairwise.complete.obs”: cho phép tính tương quan theo từng cặp quan sát đầy đủ, tức là nếu có giá trị bị thiếu (NA) thì chỉ bỏ qua trong cặp đó, không loại bỏ toàn bộ hàng.

Kết quả cor_mat là ma trận vuông hiển thị hệ số tương quan giữa tất cả các cặp biến (từ -1 đến +1):

  • +1 → tương quan thuận hoàn hảo,
  • -1 → tương quan nghịch hoàn hảo,
  • 0 → không có mối liên hệ tuyến tính.

Nhận xét thống kê:

Tổng tài sản có tương quan dương rất mạnh với tài sản vô hình (0.84), tài sản khác (0.87) và tiền gửi NHNN (0.80) → cho thấy khi quy mô tổng tài sản tăng, các khoản này cũng có xu hướng tăng theo, phản ánh mối liên hệ trực tiếp giữa mở rộng quy mô ngân hàng và tăng các khoản dự trữ, đầu tư tài sản phi tài chính.

Chứng khoán đầu tư có tương quan dương vừa với chứng khoán kinh doanh (0.65) nhưng tương quan âm đáng kể với cho vay các TCTD khác (-0.66) → gợi ý sự đánh đổi trong cơ cấu tài sản tài chính: khi ngân hàng tăng đầu tư chứng khoán thì thường giảm các khoản cho vay liên ngân hàng.

Góp vốn dài hạn có mối tương quan dương mạnh với chứng khoán kinh doanh (0.65) nhưng âm với tiền – vàng – ngoại tệ (-0.56) và tài sản khác (-0.52), cho thấy nhóm tài sản đầu tư dài hạn có xu hướng ngược chiều với các tài sản ngắn hạn hoặc dự trữ thanh khoản.

Nhìn chung, phần lớn các cặp biến có tương quan dương trung bình – mạnh, thể hiện sự tăng đồng thời của các nhóm tài sản khi tổng tài sản mở rộng.

Nhận xét ý nghĩa kinh tế: Kết quả này phản ánh rằng trong giai đoạn 2014–2023, SHB mở rộng quy mô tổng tài sản chủ yếu thông qua việc tăng đồng thời nhiều nhóm tài sản, đặc biệt là chứng khoán đầu tư, tiền gửi tại NHNN và tài sản khác.

Mối tương quan âm giữa cho vay liên ngân hàng và chứng khoán đầu tư cho thấy sự chuyển dịch linh hoạt trong chính sách đầu tư của SHB — ưu tiên danh mục có lợi suất cao hơn tùy theo điều kiện thị trường. Ngoài ra, tương quan mạnh giữa tổng tài sản và tài sản vô hình/hữu hình cho thấy sự đầu tư vào cơ sở hạ tầng, công nghệ, và thương hiệu cũng đi cùng quá trình tăng trưởng tổng thể của ngân hàng.

Tổng thể, ma trận tương quan này cho thấy các nhóm tài sản của SHB có mối quan hệ chặt chẽ và biến động cùng chiều, phản ánh cơ cấu tài sản phát triển đồng bộ, mở rộng theo quy mô hoạt động và nhu cầu tăng vốn dự trữ, đầu tư của ngân hàng.

2.3.10 Kiểm tra NA (đếm & tỷ lệ)

Đoạn code này được sử dụng để kiểm tra dữ liệu bị thiếu (NA) trong bộ dữ liệu shb1, bao gồm cả số lượng và tỷ lệ phần trăm giá trị bị thiếu của từng biến.

Mục tiêu chính là đảm bảo chất lượng dữ liệu trước khi thực hiện các bước phân tích thống kê hoặc mô hình hóa. Việc xác định biến nào có dữ liệu thiếu giúp người phân tích quyết định xử lý NA như thế nào, loại bỏ, thay thế bằng giá trị trung bình, hoặc giữ nguyên nếu tỷ lệ thiếu không đáng kể.

Đây là một bước tiền xử lý dữ liệu (data cleaning) quan trọng trong bất kỳ nghiên cứu tài chính hay kinh tế nào, giúp đảm bảo kết quả phân tích sau đó chính xác và đáng tin cậy.

na_count <- colSums(is.na(shb1))
na_rate  <- round(100 * colMeans(is.na(shb1)), 2)
na_count
##                   year           tong_tai_san     chung_khoan_dau_tu 
##                      0                      0                      0 
##        gop_von_dai_han    tien_gui_khach_hang  cho_vay_cac_tctd_khac 
##                      0                      0                      0 
## chung_khoan_kinh_doanh     tien_vang_ngoai_te          tien_gui_nhnn 
##                      0                      0                      0 
##            ts_huu_hinh             ts_vo_hinh           tai_san_khac 
##                      0                      0                      0 
##             pct_GopVon               pct_tvnt      pct_Tien_gui_NHNN 
##                      0                      0                      0 
##   pct_ChoVay_TCTD_khac      pct_CK_kinh_doanh          pct_CK_dau_tu 
##                      0                      0                      0 
##        pct_TS_huu_hinh         pct_TS_vo_hinh            pct_TS_khac 
##                      0                      0                      0 
##              g_Tong_TS               g_GopVon                   g_CK 
##                      1                      1                      1 
##              giai_doan              quy_mo_ts       nhom_tang_truong 
##                      0                      0                      1 
##                nhom_ck 
##                      0
na_rate
##                   year           tong_tai_san     chung_khoan_dau_tu 
##                      0                      0                      0 
##        gop_von_dai_han    tien_gui_khach_hang  cho_vay_cac_tctd_khac 
##                      0                      0                      0 
## chung_khoan_kinh_doanh     tien_vang_ngoai_te          tien_gui_nhnn 
##                      0                      0                      0 
##            ts_huu_hinh             ts_vo_hinh           tai_san_khac 
##                      0                      0                      0 
##             pct_GopVon               pct_tvnt      pct_Tien_gui_NHNN 
##                      0                      0                      0 
##   pct_ChoVay_TCTD_khac      pct_CK_kinh_doanh          pct_CK_dau_tu 
##                      0                      0                      0 
##        pct_TS_huu_hinh         pct_TS_vo_hinh            pct_TS_khac 
##                      0                      0                      0 
##              g_Tong_TS               g_GopVon                   g_CK 
##                     10                     10                     10 
##              giai_doan              quy_mo_ts       nhom_tang_truong 
##                      0                      0                     10 
##                nhom_ck 
##                      0

Giải thích code Hàm is.na(shb1) → tạo một bảng logic (TRUE/FALSE) cho biết từng ô dữ liệu có bị thiếu (NA) hay không.

Hàm colSums(is.na(shb1)) → tính tổng số giá trị bị thiếu (NA) trong từng cột → kết quả lưu vào biến na_count.

Hàm colMeans(is.na(shb1)) → tính tỷ lệ giá trị bị thiếu trung bình của từng cột, rồi nhân với 100 để đổi sang phần trăm → kết quả lưu vào na_rate.

Hàm round(…, 2) → làm tròn tỷ lệ NA đến hai chữ số thập phân để dễ đọc.

Cuối cùng, na_count và na_rate được in ra để xem số lượng và tỷ lệ NA của từng biến.

Nhận xét: Kết quả cho thấy hầu hết các biến trong bộ dữ liệu của Ngân hàng SHB giai đoạn 2014–2023 không có giá trị bị thiếu (NA = 0), ngoại trừ một vài biến như g_Tong_TS, g_GopVon, g_CK, nhom_tang_truong, nhom_ck có giá trị bị thiếu ở 1 quan sát, tương ứng với tỷ lệ rất nhỏ (≈ 10%) trong toàn bộ mẫu. Do có dữ liệu chỉ lấy từ năm 2014 nên không có dữ liệu cho năm trước đó nên bị NA (vì không thể tính tốc độ tăng so với năm trước).

2.3.11 Phân vị (10%, 90%) Tổng tài sản

Đoạn code này được sử dụng để xác định các giá trị phân vị (percentile) của biến Tổng tài sản (tong_tai_san) trong bộ dữ liệu SHB giai đoạn 2014–2023.

Cụ thể, ta tính phân vị thứ 10% (P10) và phân vị thứ 90% (P90), nhằm xác định ngưỡng dưới và ngưỡng trên của phân bố tổng tài sản. Việc tính hai phân vị này giúp hiểu rõ mức độ biến động và sự chênh lệch quy mô tài sản qua các năm, đồng thời phát hiện được biên giá trị thấp – cao bất thường (outliers) hoặc khoảng dao động chính của dữ liệu.

p10<- quantile(shb1$tong_tai_san, .10, na.rm = TRUE)
p90<- quantile(shb1$tong_tai_san, .90, na.rm = TRUE)
p10
##             10% 
## 201137280600000
p90
##             90% 
## 558863776500000

Giải thích code

quantile(): là hàm dùng để tính giá trị phân vị của một biến số.

shb1$tong_tai_san: lấy cột tổng tài sản trong bộ dữ liệu shb1.

.10 và .90: lần lượt đại diện cho phân vị 10% (P10) và phân vị 90% (P90).

na.rm = TRUE: loại bỏ các giá trị bị thiếu (NA) để đảm bảo phép tính chính xác.

Nhận xét thống kê: Khoảng chênh lệch giữa P90 và P10 khá lớn (hơn 35.000 tỷ đồng), cho thấy tổng tài sản của SHB có sự tăng trưởng mạnh và biến động rõ rệt theo thời gian. Các năm nằm trong khoảng giữa hai phân vị (10%–90%) đại diện cho phần lớn các giá trị trung tâm của phân bố dữ liệu, tức là phần hoạt động ổn định của ngân hàng. Năm có tổng tài sản dưới mức P10 (≈ 20 nghìn tỷ) thuộc giai đoạn đầu chuỗi (2014–2015), trong khi các năm vượt P90 (≈ 55 nghìn tỷ) là giai đoạn gần đây (2021–2023), phản ánh xu hướng mở rộng mạnh quy mô tổng tài sản.

Nhận xét kinh tế: Kết quả cho thấy SHB đã tăng quy mô tổng tài sản gấp nhiều lần trong 10 năm, đặc biệt sau năm 2020 khi ngân hàng đẩy mạnh cho vay và đầu tư chứng khoán. Việc khoảng phân vị cao (P90) cách xa đáng kể so với phân vị thấp (P10) thể hiện quá trình tăng trưởng nhanh, không đồng đều theo thời gian, phù hợp với xu hướng mở rộng mạnh trong giai đoạn hậu tái cấu trúc. => Đây là dấu hiệu tích cực, cho thấy SHB đã chuyển dịch từ nhóm ngân hàng quy mô trung bình lên nhóm có tổng tài sản lớn trong hệ thống, góp phần củng cố vị thế cạnh tranh.

2.3.12 Phát hiện outlier Tổng tài sản theo quy tắc IQR

Mục đích của đoạn code này là phát hiện các giá trị ngoại lai (outlier) của biến Tổng tài sản (tong_tai_san) trong bộ dữ liệu của Ngân hàng SHB, dựa trên quy tắc IQR (Interquartile Range Rule).

Việc kiểm tra này giúp đảm bảo dữ liệu ổn định và không bị sai lệch bởi những giá trị quá lớn hoặc quá nhỏ bất thường, vốn có thể ảnh hưởng đến kết quả thống kê và mô hình hóa sau này.

q1 <- quantile(shb1$tong_tai_san, .25, na.rm=TRUE)
q3 <- quantile(shb1$tong_tai_san, .75, na.rm=TRUE)
lower <- q1 - 1.5*(q3-q1); upper <- q3 + 1.5*(q3-q1)
out_ts <- shb1 %>% filter(tong_tai_san < lower | tong_tai_san > upper) %>% select(year, tong_tai_san)
out_ts
## # A tibble: 0 × 2
## # ℹ 2 variables: year <dbl>, tong_tai_san <dbl>

Giải thích code

q1 và q3: lần lượt là phân vị thứ 25% và 75% của biến tong_tai_san.

(q3 - q1)IQR (Interquartile Range) – khoảng tứ phân vị thể hiện mức độ phân tán của dữ liệu trung tâm.

Ngưỡng dưới & trên (lower, upper):

**lower = Q1 - 1.5*IQR**: giá trị thấp hơn ngưỡng này bị xem là outlier thấp.

**upper = Q3 + 1.5*IQR**: giá trị cao hơn ngưỡng này bị xem là outlier cao.

filter(tong_tai_san < lower | tong_tai_san > upper): lọc ra các giá trị ngoài khoảng [lower, upper].

select(year, tong_tai_san): chỉ hiển thị năm và tổng tài sản của các giá trị bị phát hiện là ngoại lai.

Nhận xét thống kê: Kết quả trả về 0 hàng (0 rows) → nghĩa là không có giá trị ngoại lai trong biến Tổng tài sản theo quy tắc IQR. Điều này cho thấy dữ liệu Tổng tài sản của SHB giai đoạn 2014–2023 có phân bố ổn định, không có biến động bất thường vượt xa xu hướng chung.

Nhận xét kinh tế: Việc không có outlier phản ánh quá trình tăng trưởng tổng tài sản của SHB diễn ra tương đối đều đặn, không có năm nào tăng/giảm đột biến vượt ngoài quy luật chung của chu kỳ phát triển. => Đây là tín hiệu tốt, cho thấy ngân hàng duy trì được sự ổn định tài chính và mở rộng quy mô hợp lý qua các năm, đặc biệt sau giai đoạn tái cấu trúc.

=> Nhờ đó, các phân tích sau (như hồi quy, xu hướng, hay dự báo ARIMA) có thể được thực hiện mà không cần loại bỏ hoặc điều chỉnh giá trị cực đoan.

2.3.13 Tỷ trọng trung vị của Góp vốn theo giai đoạn

Đoạn code này được sử dụng để so sánh tỷ trọng trung vị của khoản mục Góp vốn dài hạn trong tổng tài sản giữa hai giai đoạn: 2014–2018 và 2019–2023.

Mục tiêu là xem xét liệu tỷ trọng đầu tư góp vốn của Ngân hàng SHB có xu hướng thay đổi theo thời gian hay không. Việc sử dụng trung vị (median) thay vì trung bình giúp kết quả ít bị ảnh hưởng bởi giá trị cực đoan, phản ánh chính xác hơn xu hướng điển hình của mỗi giai đoạn.

med_gv_gd <- shb1 %>% group_by(giai_doan) %>%
  summarise(med_pct_gopvon = median(pct_GopVon, na.rm=TRUE))
med_gv_gd
## # A tibble: 2 × 2
##   giai_doan med_pct_gopvon
##   <chr>              <dbl>
## 1 2014-2018         0.0953
## 2 2019-2023         0.0323

Giải thích code

Hàm group_by(giai_doan): nhóm dữ liệu theo biến giai_doan, chia thành hai giai đoạn: 2014–2018 và 2019–2023.

Hàm summarise(): tạo bảng tóm tắt chứa kết quả tính toán cho từng nhóm.

Hàm median(pct_GopVon, na.rm = TRUE): tính giá trị trung vị của tỷ trọng Góp vốn dài hạn (pct_GopVon) trong mỗi giai đoạn; tham số na.rm = TRUE giúp bỏ qua giá trị bị thiếu nếu có.

Nhận xét thống kê: Tỷ trọng trung vị của góp vốn dài hạn giảm mạnh từ khoảng 9,5% trong giai đoạn 2014–2018 xuống còn 3,2% trong giai đoạn 2019–2023. Sự chênh lệch này cho thấy xu hướng thu hẹp đáng kể hoạt động đầu tư vốn dài hạn của SHB trong giai đoạn sau. Trung vị được dùng thay cho trung bình giúp loại bỏ ảnh hưởng của những năm có biến động bất thường (ví dụ: năm ngân hàng thoái vốn lớn hoặc tăng vốn đầu tư đột biến).

Nhận xét kinh tế: Giai đoạn 2014–2018, tỷ trọng góp vốn dài hạn cao hơn phản ánh chiến lược mở rộng đầu tư vào công ty con, công ty liên kết hoặc lĩnh vực ngoài ngân hàng — giai đoạn này SHB vẫn đang trong quá trình tái cấu trúc và mở rộng hoạt động. Sang giai đoạn 2019–2023, tỷ trọng này giảm xuống rõ rệt cho thấy ngân hàng đã chuyển hướng tập trung nhiều hơn vào hoạt động cốt lõi, đặc biệt là cho vay và đầu tư chứng khoán, thay vì giữ vốn lâu dài tại các tổ chức khác. Xu hướng giảm tỷ trọng góp vốn dài hạn phù hợp với định hướng chung của ngành ngân hàng Việt Nam giai đoạn sau 2018 — ưu tiên nâng cao hiệu quả sử dụng vốn, tăng tính thanh khoản và tuân thủ quy định an toàn vốn (Basel II).

2.3.14 Tỷ lệ năm có YoY Tổng tài sản âm

Đoạn code này nhằm xác định xem trong giai đoạn 2014–2023, có năm nào tổng tài sản của SHB giảm so với năm trước hay không, và tính tỷ lệ (%) các năm có tăng trưởng âm (YoY < 0).

Việc kiểm tra này giúp đánh giá tính ổn định trong tăng trưởng tài sản của ngân hàng — nếu không có năm nào âm, điều đó thể hiện tốc độ tăng trưởng bền vững và không bị suy giảm quy mô trong giai đoạn nghiên cứu.

neg_share <- mean(shb1$g_Tong_TS < 0, na.rm=TRUE)
neg_share
## [1] 0

Giải thích code

Hàm shb1\(g_Tong_TS**: là cột thể hiện tốc độ tăng trưởng tổng tài sản theo năm (YoY) của SHB. Hàm **shb1\)g_Tong_TS < 0: tạo một mảng logic, đánh dấu TRUE nếu tăng trưởng âm, FALSE nếu tăng trưởng dương hoặc bằng 0. mean(…, na.rm=TRUE): Khi áp dụng mean() lên một mảng logic, TRUE = 1, FALSE = 0. Do đó, giá trị trung bình chính là tỷ lệ (%) năm có YoY âm.

Nhận xét thống kê: Kết quả bằng 0 cho thấy không có năm nào trong giai đoạn 2014–2023 mà tổng tài sản của SHB giảm so với năm trước. Tất cả các năm đều có tốc độ tăng trưởng dương (YoY > 0).

Nhận xét kinh tế: Điều này phản ánh SHB duy trì được tốc độ tăng trưởng quy mô tài sản liên tục và bền vững trong suốt 10 năm qua. Không có năm nào sụt giảm tổng tài sản chứng tỏ: Ngân hàng mở rộng hoạt động ổn định, hiệu quả huy động vốn và đầu tư tăng đều đặn. Năng lực quản trị rủi ro tài sản được cải thiện đáng kể, không gặp cú sốc giảm quy mô.

Kết quả này cũng phù hợp với xu hướng chung của ngành ngân hàng Việt Nam, trong đó các ngân hàng thương mại cổ phần duy trì tăng trưởng tài sản đều đặn nhờ nhu cầu tín dụng và đầu tư gia tăng.

2.3.15 Năm có biến động YoY mạnh nhất (theo |g_Tong_TS|)

Đoạn code này được sử dụng để xác định năm có mức biến động mạnh nhất trong tốc độ tăng trưởng tổng tài sản (YoY) của Ngân hàng SHB giai đoạn 2014–2023. Cụ thể, mục tiêu là tìm năm có biến động tuyệt đối lớn nhất trong biến g_Tong_TS – tức là năm mà tốc độ tăng hoặc giảm quy mô tổng tài sản đạt mức cao nhất. Phân tích này giúp nhận diện thời điểm ngân hàng có sự thay đổi đột biến về quy mô tài sản, từ đó đánh giá nguyên nhân và bối cảnh kinh tế – tài chính dẫn đến sự biến động đó.

max_vol_year <- shb1 %>% filter(abs(g_Tong_TS) == max(abs(g_Tong_TS), na.rm=TRUE)) %>%
  select(year, g_Tong_TS)
max_vol_year
## # A tibble: 1 × 2
##    year g_Tong_TS
##   <dbl>     <dbl>
## 1  2021      22.8

Giải thích code

Hàm abs(g_Tong_TS): lấy giá trị tuyệt đối của tốc độ tăng trưởng tổng tài sản để xét độ lớn của biến động, không phân biệt tăng hay giảm.

Hàm max(abs(g_Tong_TS), na.rm = TRUE): tìm giá trị tuyệt đối lớn nhất trong toàn bộ giai đoạn (bỏ qua giá trị NA).

Hàm filter(abs(g_Tong_TS) == max(…)): lọc ra hàng (năm) có giá trị biến động lớn nhất.

Hàm select(year, g_Tong_TS): chỉ hiển thị hai cột cần thiết là năm và tốc độ tăng trưởng tổng tài sản.

Kết quả cho thấy: Năm 2021 là năm có biến động YoY mạnh nhất, với tốc độ tăng trưởng tổng tài sản đạt khoảng 22,76% so với năm trước. Mức tăng này là cao nhất toàn giai đoạn, thể hiện sự mở rộng đột biến về quy mô tài sản trong năm 2021. Biến động mạnh cho thấy đây là năm đặc biệt, có thể liên quan đến tăng vốn huy động, mở rộng tín dụng, hoặc tăng nắm giữ chứng khoán đầu tư.

Nhận xét kinh tế Năm 2021 trùng với giai đoạn hậu đại dịch COVID-19, khi Ngân hàng Nhà nước Việt Nam hạ lãi suất, nới lỏng chính sách tiền tệ, khuyến khích tăng tín dụng và hỗ trợ doanh nghiệp phục hồi. SHB trong giai đoạn này đã mở rộng mạnh danh mục cho vay và đầu tư tài chính, đồng thời tăng vốn điều lệ, giúp tổng tài sản tăng trưởng vượt trội. Mức tăng trưởng 22,76% phản ánh sức bật mạnh sau tái cơ cấu, đồng thời thể hiện năng lực mở rộng thị phần và tăng hiệu quả huy động vốn của SHB.

2.3.16 Tỷ trọng CK đầu tư trung bình theo nhóm quy mô

Đoạn code này được sử dụng để phân tích mối quan hệ giữa quy mô tổng tài sản và tỷ trọng chứng khoán đầu tư (CKĐT) của Ngân hàng SHB trong giai đoạn 2014–2023. Cụ thể, dữ liệu được chia thành các nhóm quy mô tổng tài sản khác nhau, và sau đó tính tỷ trọng trung bình của khoản mục chứng khoán đầu tư trong mỗi nhóm. Mục tiêu là xem xét liệu khi quy mô tài sản tăng, ngân hàng có xu hướng thay đổi tỷ trọng đầu tư chứng khoán hay không, từ đó đánh giá chính sách quản trị danh mục đầu tư tài sản theo từng giai đoạn phát triển của ngân hàng.

w_ck_quymo <- shb1 %>% group_by(quy_mo_ts) %>%
  summarise(w_ckdt_tb = mean(pct_CK_dau_tu, na.rm=TRUE))
w_ck_quymo
## # A tibble: 4 × 2
##   quy_mo_ts           w_ckdt_tb
##   <fct>                   <dbl>
## 1 [1.69e+14,2.47e+14]      8.16
## 2 (2.47e+14,3.44e+14]     11.1 
## 3 (3.44e+14,4.83e+14]      6.43
## 4 (4.83e+14,6.31e+14]      5.34

Giải thích code

Hàm group_by(quy_mo_ts): nhóm dữ liệu theo biến quy_mo_ts, tức là chia tổng tài sản thành các nhóm quy mô khác nhau (ví dụ: nhỏ, trung bình, lớn). → Các khoảng [1.69e+14, 2.47e+14], (2.47e+14, 3.44e+14], … là khoảng giá trị tổng tài sản (đơn vị đồng), được chia tự động thành 4 nhóm.

Hàm summarise(): tạo bảng tóm tắt với 1 dòng cho mỗi nhóm quy mô.

Hàm mean(pct_CK_dau_tu, na.rm = TRUE): tính tỷ trọng trung bình của chứng khoán đầu tư trong tổng tài sản cho từng nhóm; na.rm = TRUE giúp bỏ qua các giá trị bị thiếu.

Kết quả w_ck_quymo hiển thị hai cột:

  • quy_mo_ts: khoảng quy mô tổng tài sản.

  • w_ckdt_tb: tỷ trọng CKĐT trung bình tương ứng cho mỗi nhóm.

Nhận xét thống kê: Tỷ trọng CKĐT cao nhất ở nhóm quy mô trung bình thấp (≈11,14%), sau đó giảm dần khi quy mô tài sản tăng, chỉ còn khoảng 5,34% ở nhóm lớn nhất. Điều này cho thấy khi ngân hàng mở rộng quy mô tổng tài sản, tỷ trọng đầu tư chứng khoán có xu hướng giảm, thay vào đó ngân hàng có thể tập trung nhiều hơn vào cho vay và các tài sản sinh lời chính.

Nhận xét kinh tế: Ở giai đoạn quy mô nhỏ hơn, SHB có xu hướng phân bổ tỷ trọng cao hơn cho đầu tư chứng khoán nhằm đảm bảo thanh khoản và kiếm lợi nhuận an toàn từ các công cụ tài chính. Khi quy mô tăng, ngân hàng đa dạng hóa danh mục tài sản, ưu tiên tăng dư nợ cho vay và các tài sản dài hạn sinh lời cao hơn, do đó tỷ trọng CKĐT giảm tương đối. Xu hướng này phản ánh chiến lược phát triển điển hình của các ngân hàng thương mại Việt Nam: giai đoạn đầu dựa nhiều vào đầu tư tài chính, nhưng khi đạt quy mô lớn hơn thì chuyển trọng tâm sang tín dụng và dịch vụ tài chính.

2.3.17 So sánh trung bình Tổng tài sản giữa 2 giai đoạn

Đoạn code này được dùng để so sánh mức tổng tài sản trung bình của Ngân hàng SHB giữa hai giai đoạn:

2014–2018: giai đoạn đầu của quá trình tái cấu trúc và ổn định hoạt động.

2019–2023: giai đoạn tăng trưởng và mở rộng quy mô sau khi củng cố nền tảng tài chính.

=> Mục tiêu là đánh giá mức độ mở rộng quy mô tài sản theo thời gian, qua đó phản ánh sức tăng trưởng và hiệu quả sử dụng vốn của SHB.

tt_gd <- shb1 %>% group_by(giai_doan) %>%
  summarise(ts_tb = mean(tong_tai_san, na.rm=TRUE))
tt_gd
## # A tibble: 2 × 2
##   giai_doan   ts_tb
##   <chr>       <dbl>
## 1 2014-2018 2.43e14
## 2 2019-2023 4.93e14

Giải thích code

group_by(giai_doan): chia dữ liệu thành hai nhóm theo giai đoạn thời gian (2014–2018 và 2019–2023).

summarise(): tạo bảng tóm tắt, tính toán giá trị trung bình của tổng tài sản cho từng nhóm.

mean(tong_tai_san, na.rm = TRUE): tính giá trị trung bình tổng tài sản trong từng giai đoạn, loại bỏ các giá trị bị thiếu (NA).

Kết quả tt_gd hiển thị hai cột:

  • giai_doan: giai đoạn thời gian.

  • ts_tb: tổng tài sản trung bình tương ứng (đơn vị đồng)

Nhận xét thống kê: Tổng tài sản trung bình của SHB giai đoạn 2019–2023 tăng gấp đôi so với giai đoạn 2014–2018 (tăng khoảng 102,5%). Mức tăng này cho thấy quy mô hoạt động của ngân hàng mở rộng đáng kể, phản ánh xu hướng tăng trưởng ổn định và mở rộng thị phần.

Nhận xét kinh tế:

  • Giai đoạn 2014–2018, SHB tập trung xử lý nợ xấu và tái cấu trúc sau sáp nhập Habubank, nên quy mô tổng tài sản còn tương đối thấp.

  • Giai đoạn 2019–2023, SHB bước vào chu kỳ tăng trưởng mới, đẩy mạnh cho vay khách hàng, đầu tư chứng khoán và tăng vốn điều lệ, giúp tổng tài sản tăng mạnh. Mức tăng trưởng quy mô này phù hợp với xu hướng phát triển chung của hệ thống ngân hàng Việt Nam, đặc biệt sau năm 2020 khi môi trường lãi suất thấp và chính sách hỗ trợ tín dụng sau đại dịch giúp các ngân hàng mở rộng nhanh quy mô tài sản.

2.3.18 Tỷ trọng bình quân của từng nhóm tài sản theo năm (bảng dài)

Mục đích của đoạn code này là tính tỷ trọng trung bình của từng nhóm tài sản trong tổng tài sản theo từng năm của Ngân hàng SHB, giai đoạn 2014–2023. Việc này giúp theo dõi xu hướng biến động cơ cấu tài sản qua thời gian, xem những loại tài sản nào tăng/giảm tỷ trọng trong danh mục, từ đó đánh giá chiến lược đầu tư và quản trị danh mục tài sản của ngân hàng.

w_by_year <- shb1 %>%
  select(year, pct_GopVon, pct_CK_dau_tu, pct_CK_kinh_doanh, pct_tvnt,
         pct_Tien_gui_NHNN, pct_TS_huu_hinh, pct_TS_vo_hinh, pct_TS_khac) %>%
  pivot_longer(-year, names_to="part", values_to="w") %>%
  group_by(year, part) %>% summarise(w=mean(w, na.rm=TRUE), .groups="drop")
w_by_year
## # A tibble: 80 × 3
##     year part                   w
##    <dbl> <chr>              <dbl>
##  1  2014 pct_CK_dau_tu     7.97  
##  2  2014 pct_CK_kinh_doanh 0.0188
##  3  2014 pct_GopVon        0.190 
##  4  2014 pct_TS_huu_hinh   0.227 
##  5  2014 pct_TS_khac       8.51  
##  6  2014 pct_TS_vo_hinh    2.20  
##  7  2014 pct_Tien_gui_NHNN 1.98  
##  8  2014 pct_tvnt          0.474 
##  9  2015 pct_CK_dau_tu     8.46  
## 10  2015 pct_CK_kinh_doanh 0.0266
## # ℹ 70 more rows

Giải thích code

select(…): chỉ chọn các biến cần thiết — gồm year (năm) và các biến tỷ trọng từng loại tài sản (pct_…) → Đây là các tỷ trọng của các khoản mục tài sản như góp vốn, chứng khoán đầu tư, cho vay, tài sản hữu hình/vô hình,…

pivot_longer(-year, names_to=“part”, values_to=“w”): Chuyển bảng dữ liệu từ dạng “rộng” (nhiều cột) sang “dài” (2 cột: loại tài sản và tỷ trọng).

part: tên nhóm tài sản, w: tỷ trọng tương ứng.

group_by(year, part): nhóm dữ liệu theo năm và loại tài sản.

summarise(w = mean(w, na.rm = TRUE)): tính tỷ trọng trung bình của từng loại tài sản trong mỗi năm (bỏ qua giá trị NA).

Nhận xét thống kê: Mỗi năm, tổng tỷ trọng các nhóm tài sản phản ánh cách ngân hàng phân bổ danh mục tổng tài sản. Có thể thấy tỷ trọng chứng khoán đầu tư (pct_CK_dau_tu) và tài sản khác (pct_TS_khac) chiếm tỷ lệ cao nhất ở các năm đầu (2014–2016), trong khi cho vay và tài sản hữu hình có xu hướng tăng dần ở các năm sau.

Một số nhóm tài sản như góp vốn hoặc chứng khoán kinh doanh có tỷ trọng nhỏ, cho thấy SHB không tập trung đầu tư rủi ro ngắn hạn, mà ưu tiên các tài sản ổn định.

Nhận xét kinh tế: Giai đoạn 2014–2016, SHB vẫn trong thời kỳ tái cấu trúc sau sáp nhập, nên tỷ trọng tài sản tài chính (CKĐT, TS khác) còn cao để đảm bảo thanh khoản. Từ 2017 trở đi, ngân hàng đẩy mạnh cho vay khách hàng và đầu tư dài hạn, phản ánh sự chuyển dịch chiến lược sang hoạt động cốt lõi là tín dụng và đầu tư cơ sở hạ tầng. => Cơ cấu tài sản theo năm cho thấy quá trình tăng trưởng ổn định và đa dạng hóa danh mục, phù hợp với xu thế phát triển bền vững của các ngân hàng thương mại Việt Nam.

2.3.19 Hệ số biến thiên theo giai đoạn

Đoạn code này được sử dụng để đánh giá mức độ ổn định và biến động của Tổng tài sản (tong_tai_san) của SHB trong hai giai đoạn:

2014–2018: giai đoạn đầu sau tái cấu trúc.

2019–2023: giai đoạn tăng trưởng và mở rộng quy mô.

Hệ số biến thiên (CV) giúp xác định mức độ biến động tương đối của tổng tài sản so với giá trị trung bình. → CV càng nhỏ → dữ liệu càng ổn định, ít dao động → cho thấy ngân hàng quản lý tài sản hiệu quả và duy trì quy mô bền vững hơn

cv_by_gd <- shb1 %>%
  group_by(giai_doan) %>%
  summarise(cv_ts = sd(tong_tai_san, na.rm=TRUE)/mean(tong_tai_san, na.rm=TRUE))
cv_by_gd
## # A tibble: 2 × 2
##   giai_doan cv_ts
##   <chr>     <dbl>
## 1 2014-2018 0.254
## 2 2019-2023 0.216

Giải thích code

Hàm group_by(giai_doan): chia dữ liệu thành 2 nhóm theo giai đoạn (2014–2018, 2019–2023). Hàm summarise(): tính tóm tắt cho từng nhóm giai đoạn. Hàm sd(tong_tai_san): độ lệch chuẩn (mức biến động tuyệt đối). Hàm mean(tong_tai_san): giá trị trung bình của tổng tài sản trong giai đoạn. Hàm cv_ts = sd(…) / mean(…): công thức tính hệ số biến thiên (CV).

Nhận xét thống kê: CV giai đoạn 2014–2018 = 0.2542, cao hơn CV giai đoạn 2019–2023 = 0.2156.

→ Nghĩa là tổng tài sản của SHB trong giai đoạn sau (2019–2023) ổn định hơn, ít dao động quanh giá trị trung bình. Mức giảm CV ≈ 15%, thể hiện sự ổn định tăng dần trong quản trị và mở rộng tài sản.

Nhận xét kinh tế: Giai đoạn 2014–2018 là thời kỳ SHB tập trung xử lý nợ xấu, sáp nhập và tái cơ cấu, nên tổng tài sản còn biến động khá lớn. Sang 2019–2023, ngân hàng đã củng cố nền tảng tài chính, mở rộng tín dụng và huy động vốn ổn định hơn → quy mô tài sản tăng đều, ít biến động mạnh.

Việc CV giảm thể hiện chất lượng tài sản và tốc độ tăng trưởng của SHB ngày càng bền vững, giảm rủi ro hoạt động và phản ánh sự trưởng thành trong quản trị tài chính.

2.3.20 Bảng tổng hợp gọn để báo cáo

Đoạn code trên được sử dụng để tạo bảng tổng hợp (summary report) chứa các chỉ tiêu thống kê chính của biến Tổng tài sản và một số cấu phần tài sản khác của Ngân hàng SHB trong giai đoạn 2014–2023.

sum_report <- tibble::tibble(
  n_year = nrow(shb1),
  CAGR_TS = round(100*cagr_ts, 2),
  YOY_TS_avg = round(yoy_avg$yoy_ts, 2),
  share_ckdt_avg = round(w_avg$w_ckdt, 2)
)

desc_main; iqr_ts; cv_ts; top_ts; top_ck
## # A tibble: 1 × 8
##       n tong_ts_min tong_ts_q1 tong_ts_med tong_ts_q3 tong_ts_max tong_ts_mean
##   <int>       <dbl>      <dbl>       <dbl>      <dbl>       <dbl>        <dbl>
## 1    10     1.69e14    2.47e14     3.44e14    4.83e14     6.31e14      3.68e14
## # ℹ 1 more variable: tong_ts_sd <dbl>
## [1] 236159819000000
## [1] 0.4211487
## # A tibble: 3 × 2
##    year tong_tai_san
##   <dbl>        <dbl>
## 1  2023      6.31e14
## 2  2022      5.51e14
## 3  2021      5.07e14
## # A tibble: 3 × 2
##    year pct_CK_dau_tu
##   <dbl>         <dbl>
## 1  2018         14.9 
## 2  2015          8.46
## 3  2016          8.06
cor_mat[1:6,1:6]; na_count; na_rate
##                        tong_tai_san gop_von_dai_han chung_khoan_dau_tu
## tong_tai_san              1.0000000      -0.1909627          0.5109094
## gop_von_dai_han          -0.1909627       1.0000000         -0.2353983
## chung_khoan_dau_tu        0.5109094      -0.2353983          1.0000000
## chung_khoan_kinh_doanh    0.5892674       0.6552177          0.2105671
## tien_gui_nhnn             0.8020484       0.4046144          0.2888118
## tien_vang_ngoai_te        0.3591603      -0.5559915          0.4977523
##                        chung_khoan_kinh_doanh tien_gui_nhnn tien_vang_ngoai_te
## tong_tai_san                        0.5892674     0.8020484          0.3591603
## gop_von_dai_han                     0.6552177     0.4046144         -0.5559915
## chung_khoan_dau_tu                  0.2105671     0.2888118          0.4977523
## chung_khoan_kinh_doanh              1.0000000     0.9429618         -0.2117598
## tien_gui_nhnn                       0.9429618     1.0000000         -0.0374352
## tien_vang_ngoai_te                 -0.2117598    -0.0374352          1.0000000
##                   year           tong_tai_san     chung_khoan_dau_tu 
##                      0                      0                      0 
##        gop_von_dai_han    tien_gui_khach_hang  cho_vay_cac_tctd_khac 
##                      0                      0                      0 
## chung_khoan_kinh_doanh     tien_vang_ngoai_te          tien_gui_nhnn 
##                      0                      0                      0 
##            ts_huu_hinh             ts_vo_hinh           tai_san_khac 
##                      0                      0                      0 
##             pct_GopVon               pct_tvnt      pct_Tien_gui_NHNN 
##                      0                      0                      0 
##   pct_ChoVay_TCTD_khac      pct_CK_kinh_doanh          pct_CK_dau_tu 
##                      0                      0                      0 
##        pct_TS_huu_hinh         pct_TS_vo_hinh            pct_TS_khac 
##                      0                      0                      0 
##              g_Tong_TS               g_GopVon                   g_CK 
##                      1                      1                      1 
##              giai_doan              quy_mo_ts       nhom_tang_truong 
##                      0                      0                      1 
##                nhom_ck 
##                      0
##                   year           tong_tai_san     chung_khoan_dau_tu 
##                      0                      0                      0 
##        gop_von_dai_han    tien_gui_khach_hang  cho_vay_cac_tctd_khac 
##                      0                      0                      0 
## chung_khoan_kinh_doanh     tien_vang_ngoai_te          tien_gui_nhnn 
##                      0                      0                      0 
##            ts_huu_hinh             ts_vo_hinh           tai_san_khac 
##                      0                      0                      0 
##             pct_GopVon               pct_tvnt      pct_Tien_gui_NHNN 
##                      0                      0                      0 
##   pct_ChoVay_TCTD_khac      pct_CK_kinh_doanh          pct_CK_dau_tu 
##                      0                      0                      0 
##        pct_TS_huu_hinh         pct_TS_vo_hinh            pct_TS_khac 
##                      0                      0                      0 
##              g_Tong_TS               g_GopVon                   g_CK 
##                     10                     10                     10 
##              giai_doan              quy_mo_ts       nhom_tang_truong 
##                      0                      0                     10 
##                nhom_ck 
##                      0
p10; p90; out_ts
##             10% 
## 201137280600000
##             90% 
## 558863776500000
## # A tibble: 0 × 2
## # ℹ 2 variables: year <dbl>, tong_tai_san <dbl>
med_gv_gd; neg_share; max_vol_year
## # A tibble: 2 × 2
##   giai_doan med_pct_gopvon
##   <chr>              <dbl>
## 1 2014-2018         0.0953
## 2 2019-2023         0.0323
## [1] 0
## # A tibble: 1 × 2
##    year g_Tong_TS
##   <dbl>     <dbl>
## 1  2021      22.8
w_ck_quymo; tt_gd; cv_by_gd; sum_report
## # A tibble: 4 × 2
##   quy_mo_ts           w_ckdt_tb
##   <fct>                   <dbl>
## 1 [1.69e+14,2.47e+14]      8.16
## 2 (2.47e+14,3.44e+14]     11.1 
## 3 (3.44e+14,4.83e+14]      6.43
## 4 (4.83e+14,6.31e+14]      5.34
## # A tibble: 2 × 2
##   giai_doan   ts_tb
##   <chr>       <dbl>
## 1 2014-2018 2.43e14
## 2 2019-2023 4.93e14
## # A tibble: 2 × 2
##   giai_doan cv_ts
##   <chr>     <dbl>
## 1 2014-2018 0.254
## 2 2019-2023 0.216
## # A tibble: 1 × 4
##   n_year CAGR_TS YOY_TS_avg share_ckdt_avg
##    <int>   <dbl>      <dbl>          <dbl>
## 1     10    15.8       15.8           7.56

Giải thích code

Cụ thể, hàm tibble::tibble() được dùng để tạo một bảng dữ liệu nhỏ gọn có cấu trúc hàng–cột rõ ràng, trong đó:

  • n_year = nrow(shb1) đếm số năm quan sát trong bộ dữ liệu (ở đây là 10 năm, từ 2014 đến 2023).

  • CAGR_TS = round(100 * cagr_ts, 2) tính tốc độ tăng trưởng kép bình quân năm (CAGR) của Tổng tài sản, sau đó nhân 100 để chuyển sang phần trăm và làm tròn 2 chữ số thập phân.

  • YOY_TS_avg = round(yoy_avg$yoy_ts, 2) thể hiện tốc độ tăng trưởng bình quân hàng năm (YoY trung bình) của Tổng tài sản, phản ánh tốc độ tăng đều qua các năm.

  • share_ckdt_avg = round(w_avg$w_ckdt, 2) là tỷ trọng bình quân của Chứng khoán đầu tư trong cơ cấu tài sản của ngân hàng, giúp xem xét mức độ đầu tư tài chính trong tổng quy mô tài sản.

=> Sau khi tạo bảng sum_report, các đối tượng khác như desc_main, iqr_ts, cv_ts, top_ts, cor_mat, p10, p90, med_gv_gd, cv_by_gd,… được liệt kê ra để hiển thị hoặc lưu kết quả của toàn bộ phân tích trước đó (bao gồm thống kê mô tả, hệ số biến thiên, phân vị, tương quan, tỷ lệ NA, và các kết quả nhóm theo giai đoạn).

Kết quả của bảng sum_report mang ý nghĩa như báo cáo tóm tắt về sự tăng trưởng và cấu trúc tài sản của SHB trong 10 năm qua.

Các chỉ tiêu CAGR và YoY cho thấy tốc độ mở rộng quy mô tài sản ổn định và tích cực, trong khi tỷ trọng bình quân của chứng khoán đầu tư phản ánh mức độ đa dạng hóa danh mục tài sản của ngân hàng. Việc tổng hợp này giúp đánh giá nhanh hiệu quả tăng trưởng, cơ cấu đầu tư và mức ổn định của tổng tài sản, phục vụ trực tiếp cho phần phân tích xu hướng và đề xuất trong báo cáo tài chính.