PHẦN 1: BỘ DỮ LIỆU CRIME

CHƯƠNG 1: MỞ ĐẦU

1. Lý do chọn đề tài

Trong bối cảnh xã hội hiện đại, tội phạm không chỉ là vấn đề pháp lý mà còn là một hiện tượng xã hội phản ánh nhiều chiều cạnh của đời sống kinh tế, chính trị và văn hóa. Los Angeles – một trong những đô thị lớn nhất và đa dạng nhất của Hoa Kỳ – luôn được xem là “tấm gương phản chiếu” của các biến động xã hội, đặc biệt trong giai đoạn 2020–2023.

Đây là giai đoạn chịu ảnh hưởng nặng nề bởi đại dịch COVID-19, khủng hoảng kinh tế – việc làm, thay đổi chính sách an ninh công cộng và sự phát triển nhanh của công nghệ. Những yếu tố này tác động mạnh đến hành vi con người, mô hình cư trú, cũng như cấu trúc xã hội từ đó làm thay đổi bản chất và xu hướng của tội phạm.

Việc phân tích đa chiều về tội phạm tại Los Angeles không chỉ giúp nhận diện được xu hướng biến động theo thời gian và không gian, mà còn góp phần đánh giá các yếu tố xã hội – kinh tế – chính sách có ảnh hưởng đến tình hình an ninh đô thị. Đề tài này mang tính thực tiễn cao, hỗ trợ cơ quan quản lý, nhà nghiên cứu, và cộng đồng trong việc hoạch định các giải pháp phòng ngừa tội phạm và đảm bảo an ninh xã hội bền vững.

2. Mục tiêu nghiên cứu

Đề tài hướng tới việc cung cấp một phân tích toàn diện và có hệ thống về tình hình tội phạm tại Los Angeles giai đoạn 2020–2023, với các mục tiêu cụ thể như sau:

  • Phân tích xu hướng biến động tội phạm theo thời gian (năm) và không gian (khu vực, quận).

  • Xác định đặc điểm tội phạm theo loại hình, giới tính, độ tuổi, thời điểm xảy ra và các yếu tố nhân khẩu học khác.

  • Đánh giá mối liên hệ giữa các yếu tố xã hội (kinh tế, dịch bệnh, chính sách an ninh, dân cư) và mức độ tội phạm.

  • Đề xuất các nhận định, khuyến nghị về giải pháp quản lý và phòng ngừa tội phạm hiệu quả trong bối cảnh đô thị hóa nhanh và biến động toàn cầu.

3. Đối tượng và phạm vi nghiên cứu

Đối tượng nghiên cứu: Tình hình tội phạm tại thành phố Los Angeles, bao gồm các loại hình tội phạm (bạo lực, trộm cắp, xâm hại, ma túy, gian lận, v.v.) và các yếu tố xã hội có ảnh hưởng.

Phạm vi nghiên cứu:

Không gian: Thành phố Los Angeles, bang California, Hoa Kỳ.

Thời gian: Từ năm 2020 đến năm 2023 – giai đoạn có biến động mạnh về kinh tế – xã hội do đại dịch và thay đổi chính sách.

Phạm vi dữ liệu: Bộ dữ liệu Los Angeles Crime Data do LAPD (Los Angeles Police Department) công bố công khai, kết hợp với các nguồn dữ liệu phụ trợ như dân số, thu nhập bình quân, tỷ lệ thất nghiệp và dữ liệu COVID-19.

4. Phương pháp nghiên cứu

Nghiên cứu được thực hiện dựa trên phương pháp phân tích dữ liệu bằng ngôn ngữ R. Toàn bộ quy trình xử lý và khai thác dữ liệu tuân theo 4 giai đoạn chính:

Giới thiệu bộ dữ liệu: Trình bày nguồn gốc, cấu trúc, quy mô, và các biến quan trọng trong bộ dữ liệu Crime in Los Angeles (2020–2023).

Xử lý dữ liệu thô và mã hóa dữ liệu: Tiến hành làm sạch dữ liệu, loại bỏ ngoại lệ, chuẩn hóa định dạng, và mã hóa các biến định tính.

Thực hiện thống kê cơ bản: Ứng dụng các công cụ thống kê mô tả và suy luận (mean, median, sd, proportion, t-test, correlation, v.v.) để xác định xu hướng và mối quan hệ giữa các biến.

Trực quan hóa dữ liệu: Sử dụng ggplot2 để minh họa xu hướng, đặc điểm và mối liên hệ giữa các biến.

5. Kết cấu của đề tài

CHƯƠNG 2: TỔNG QUAN VỀ BỘ DỮ LIỆU

1. Tổng quan về bộ dữ liệu

Bộ dữ liệu “Crime Data from 2020 to Present” được thu thập từ Sở Cảnh sát Los Angeles (LAPD), phản ánh tình hình tội phạm tại thành phố Los Angeles trong giai đoạn từ năm 2020 đến năm 2024.

Dữ liệu được công khai thông qua cổng dữ liệu mở của thành phố, cung cấp thông tin chi tiết về từng vụ án hình sự đã được ghi nhận.

Sau khi kiểm tra và làm sạch, bộ dữ liệu còn lại 760.049 quan sát (dòng dữ liệu), tương ứng với các vụ phạm tội riêng lẻ và 12 biến (cột dữ liệu) chính phục vụ phân tích, bao gồm: date, area, district, severity, crime_type, age, sex, premise_type, status, lalat, lon, year.

Mỗi dòng trong bộ dữ liệu thể hiện một sự kiện phạm tội cụ thể bao gồm thông tin về loại tội phạm, khu vực xảy ra, thời gian, giới tính và độ tuổi của nạn nhân, cùng với toạ độ địa lý của vụ án.

2. Giới thiệu về bộ dữ liệu

2.1. Đọc file dữ liệu

library(readxl)
df <- read_excel("C:/Users/USER/Downloads/Crime_Data_from_2020_to_Present.xlsx")

library(readxl): nạp gói readxl – một thư viện trong R dùng để đọc dữ liệu từ file Excel (.xls hoặc .xlsx).

read_excel(“…”): đọc toàn bộ dữ liệu từ đường dẫn được chỉ định và lưu vào đối tượng df.

Hàm này tự động nhận diện kiểu dữ liệu của từng cột (số, ký tự, ngày, v.v.).

Kết quả là một bảng dữ liệu (data frame) chứa toàn bộ thông tin về các vụ phạm tội tại Los Angeles.

2.2. Quan sát dữ liệu ban đầu

head(df)
tail(df)

head(df) hiển thị 6 dòng đầu tiên trong bộ dữ liệu df.

tail(df) hiển thị 6 dòng cuối cùng trong cùng bộ dữ liệu.

Hai hàm này giúp ta xem nhanh cấu trúc dữ liệu — gồm các biến có trong tập dữ liệu, tên cột, kiểu dữ liệu (số, ký tự, ngày tháng) và cách dữ liệu được lưu trữ. Đây là bước cơ bản để đảm bảo dữ liệu đã được đọc đúng định dạng.

2.3. Kiểm tra kích thước dữ liệu

nrow(df)   # Số dòng (vụ án)
## [1] 944235
ncol(df)   # Số cột (biến)
## [1] 28

nrow(df) trả về số lượng dòng trong bộ dữ liệu df, tức là tổng số vụ án được ghi nhận.

ncol(df) trả về số lượng cột, tức là tổng số biến mô tả thông tin của mỗi vụ án.

Hai hàm này giúp ta nắm quy mô dữ liệu, biết được số quan sát (records) và số đặc trưng (variables). Đây là thông tin quan trọng trước khi thực hiện các bước làm sạch hoặc phân tích, nhằm xác định khối lượng xử lý và khả năng lưu trữ.

Ý nghĩa thống kê:

Kết quả cho thấy bộ dữ liệu ghi nhận 944.235 vụ án với 28 đặc trưng mô tả cho mỗi vụ (chẳng hạn như thời gian, địa điểm, loại tội phạm, độ tuổi, giới tính,…).

2.4. Xem tên biến

names(df)
##  [1] "DR_NO"          "Date Rptd"      "DATE OCC"       "TIME OCC"      
##  [5] "AREA"           "AREA NAME"      "Rpt Dist No"    "Part 1-2"      
##  [9] "Crm Cd"         "Crm Cd Desc"    "Mocodes"        "Vict Age"      
## [13] "Vict Sex"       "Vict Descent"   "Premis Cd"      "Premis Desc"   
## [17] "Weapon Used Cd" "Weapon Desc"    "Status"         "Status Desc"   
## [21] "Crm Cd 1"       "Crm Cd 2"       "Crm Cd 3"       "Crm Cd 4"      
## [25] "LOCATION"       "Cross Street"   "LAT"            "LON"

Lệnh names(df) giúp liệt kê toàn bộ tên các biến (cột) trong bộ dữ liệu.

Điều này cho phép người phân tích nắm được cấu trúc tổng thể của bộ dữ liệu và xác định các trường thông tin có sẵn, chẳng hạn như DATE OCC, AREA NAME, Crm Cd Desc, Vict Age, Vict Sex, LAT, LON,…

Việc nhận diện tên biến giúp dễ dàng tham chiếu khi xử lý, mã hóa hoặc lọc dữ liệu ở các bước tiếp theo.

2.5. Cấu trúc tổng quát của bộ dữ liệu

str(df)
## tibble [944,235 × 28] (S3: tbl_df/tbl/data.frame)
##  $ DR_NO         : num [1:944235] 1.90e+08 2.00e+08 2.00e+08 2.01e+08 2.21e+08 ...
##  $ Date Rptd     : POSIXct[1:944235], format: "2020-01-03" "2020-09-02" ...
##  $ DATE OCC      : POSIXct[1:944235], format: "2020-01-03" "2020-08-02" ...
##  $ TIME OCC      : num [1:944235] 2130 1800 1700 2037 1200 ...
##  $ AREA          : num [1:944235] 7 1 3 9 6 18 1 3 13 19 ...
##  $ AREA NAME     : chr [1:944235] "Wilshire" "Central" "Southwest" "Van Nuys" ...
##  $ Rpt Dist No   : num [1:944235] 784 182 356 964 666 ...
##  $ Part 1-2      : num [1:944235] 1 1 1 1 2 2 2 2 2 2 ...
##  $ Crm Cd        : num [1:944235] 510 330 480 343 354 354 354 354 354 624 ...
##  $ Crm Cd Desc   : chr [1:944235] "VEHICLE - STOLEN" "BURGLARY FROM VEHICLE" "BIKE - STOLEN" "SHOPLIFTING-GRAND THEFT ($950.01 & OVER)" ...
##  $ Mocodes       : chr [1:944235] NA "1822 1402 0344" "0344 1251" "0325 1501" ...
##  $ Vict Age      : num [1:944235] 0 47 19 19 28 41 25 27 24 26 ...
##  $ Vict Sex      : chr [1:944235] "M" "M" "X" "M" ...
##  $ Vict Descent  : chr [1:944235] "O" "O" "X" "O" ...
##  $ Premis Cd     : num [1:944235] 101 128 502 405 102 501 502 248 750 502 ...
##  $ Premis Desc   : chr [1:944235] "STREET" "BUS STOP/LAYOVER (ALSO QUERY 124)" "MULTI-UNIT DWELLING (APARTMENT, DUPLEX, ETC)" "CLOTHING STORE" ...
##  $ Weapon Used Cd: num [1:944235] NA NA NA NA NA NA NA NA NA 400 ...
##  $ Weapon Desc   : chr [1:944235] NA NA NA NA ...
##  $ Status        : chr [1:944235] "AA" "IC" "IC" "IC" ...
##  $ Status Desc   : chr [1:944235] "Adult Arrest" "Invest Cont" "Invest Cont" "Invest Cont" ...
##  $ Crm Cd 1      : num [1:944235] 510 330 480 343 354 354 354 354 354 624 ...
##  $ Crm Cd 2      : num [1:944235] 998 998 NA NA NA NA NA NA NA NA ...
##  $ Crm Cd 3      : num [1:944235] NA NA NA NA NA NA NA NA NA NA ...
##  $ Crm Cd 4      : logi [1:944235] NA NA NA NA NA NA ...
##  $ LOCATION      : chr [1:944235] "1900 S  LONGWOOD                     AV" "1000 S  FLOWER                       ST" "1400 W  37TH                         ST" "14000    RIVERSIDE                    DR" ...
##  $ Cross Street  : chr [1:944235] NA NA NA NA ...
##  $ LAT           : chr [1:944235] "340375" "340444" "34021" "341576" ...
##  $ LON           : chr [1:944235] "-1183506" "-1182628" "-1183002" "-1184387" ...

Hàm str() hiển thị cấu trúc chi tiết của từng biến, bao gồm tên, kiểu dữ liệu, và một vài giá trị mẫu.

Về mặt kỹ thuật, đây là một bước khám phá cấu trúc nội bộ của đối tượng dữ liệu trong R.

Về mặt thống kê, việc hiểu rõ loại dữ liệu (số, chuỗi ký tự, ngày tháng,..) giúp chọn đúng phương pháp phân tích và mô hình hóa.

2.6. Nhận diện kiểu dữ liệu

sapply(df, class)
## $DR_NO
## [1] "numeric"
## 
## $`Date Rptd`
## [1] "POSIXct" "POSIXt" 
## 
## $`DATE OCC`
## [1] "POSIXct" "POSIXt" 
## 
## $`TIME OCC`
## [1] "numeric"
## 
## $AREA
## [1] "numeric"
## 
## $`AREA NAME`
## [1] "character"
## 
## $`Rpt Dist No`
## [1] "numeric"
## 
## $`Part 1-2`
## [1] "numeric"
## 
## $`Crm Cd`
## [1] "numeric"
## 
## $`Crm Cd Desc`
## [1] "character"
## 
## $Mocodes
## [1] "character"
## 
## $`Vict Age`
## [1] "numeric"
## 
## $`Vict Sex`
## [1] "character"
## 
## $`Vict Descent`
## [1] "character"
## 
## $`Premis Cd`
## [1] "numeric"
## 
## $`Premis Desc`
## [1] "character"
## 
## $`Weapon Used Cd`
## [1] "numeric"
## 
## $`Weapon Desc`
## [1] "character"
## 
## $Status
## [1] "character"
## 
## $`Status Desc`
## [1] "character"
## 
## $`Crm Cd 1`
## [1] "numeric"
## 
## $`Crm Cd 2`
## [1] "numeric"
## 
## $`Crm Cd 3`
## [1] "numeric"
## 
## $`Crm Cd 4`
## [1] "logical"
## 
## $LOCATION
## [1] "character"
## 
## $`Cross Street`
## [1] "character"
## 
## $LAT
## [1] "character"
## 
## $LON
## [1] "character"

Hàm sapply(df, class) trả về kiểu dữ liệu của từng cột trong data frame.

Kết quả cho biết biến nào thuộc dạng numeric, character, Date, hay factor.

Điều này đặc biệt quan trọng trước khi xử lý, vì nếu một biến số bị đọc nhầm thành chuỗi, các phép tính thống kê (như trung bình, độ lệch chuẩn) sẽ bị sai lệch.

Dựa trên kết quả kiểm tra kiểu dữ liệu, ta có thể phân loại các biến trong bộ dữ liệu như sau:

  • Biến định lượng: TIME OCC, Vict Age, AREA, Crm Cd, Crm Cd 1, Crm Cd 2, Crm Cd 3, Premis Cd, Weapon Used Cd.

  • Biến định tính: AREA NAME, Crm Cd Desc, Vict Sex, Vict Descent, Premis Desc, Weapon Desc, Status, Status Desc, LOCATION, Cross Street.

  • Biến thời gian: Date Rptd, DATE OCC.

  • Biến vị trí địa lý: LAT, LON.

  • Biến logic: Crm Cd 4.

2.8. Trích xuất dữ liệu cần phân tích

Từ bộ dữ liệu gốc, chúng em sẽ tiến hành lựa chọn và đổi tên 11 biến quan trọng cần cho bài nghiên cứu. Các biến được đặt lại tên ngắn gọn, dễ hiểu.

library(dplyr)
## 
## Attaching package: 'dplyr'
## The following objects are masked from 'package:stats':
## 
##     filter, lag
## The following objects are masked from 'package:base':
## 
##     intersect, setdiff, setequal, union
crime_data <- df %>%
  select(`DATE OCC`,`AREA NAME`,`Rpt Dist No`,`Part 1-2`,`Crm Cd Desc`,`Vict Age`,`Vict Sex`,`Premis Desc`,`Status Desc`,`LAT`,`LON`) %>%
  rename(
    date = `DATE OCC`,
    area = `AREA NAME`,
    district = `Rpt Dist No`,
    severity = `Part 1-2`,
    crime_type = `Crm Cd Desc`,
    age = `Vict Age`,
    sex = `Vict Sex`,
    premise_type = `Premis Desc`,
    status = `Status Desc`,
    lat = `LAT`,
    lon = `LON`
  )

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

Hàm select() trong dplyr được dùng để lựa chọn các biến cần thiết từ bộ dữ liệu ban đầu.

Hàm rename() giúp đổi tên các biến sang dạng ngắn gọn, thống nhất với quy tắc đặt tên.

Kết quả thu được là một bảng dữ liệu nhỏ gọn hơn, chỉ chứa 11 biến quan trọng nhất.

Ý nghĩa thống kê:

Các biến được giữ lại bao gồm:

date: thời điểm xảy ra vụ án (biến thời gian).

area: khu vực xảy ra vụ án (biến định tính).

district: mã quận hoặc vùng quản lý (biến định lượng rời rạc).

severity: mức độ nghiêm trọng của vụ án (định tính thứ bậc).

crime_type: mô tả loại tội phạm (định tính).

age: tuổi nạn nhân (định lượng liên tục).

sex: giới tính nạn nhân (định tính).

premise_type: địa điểm xảy ra vụ án (định tính).

status: tình trạng xử lý vụ án (định tính).

lat, lon: tọa độ địa lý nơi xảy ra vụ án (biến định lượng).

2.9. Kiểm tra dữ liệu thiếu

colSums(is.na(crime_data)) 
##         date         area     district     severity   crime_type          age 
##            0            0            0            0            0            0 
##          sex premise_type       status          lat          lon 
##       126595          567            0            0            0
sum(is.na(crime_data)) 
## [1] 127162

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

Hàm is.na(): kiểm tra giá trị bị thiếu (NA) trong từng ô dữ liệu, trả về TRUE/FALSE

Hàm colSums(): tính tổng số TRUE (tức là số NA) theo từng cột

=> colSums(is.na(crime_data)) cho biết mỗi biến có bao nhiêu giá trị bị thiếu

Hàm sum(): tính tổng tất cả các giá trị TRUE trong toàn bộ bảng

=> sum(is.na(crime_data)) cho biết tổng số giá trị thiếu trong toàn bộ dataset

Ý nghĩa thống kê:

Biến age và sex có số lượng giá trị thiếu 126.595 dòng.

Biến premise_type có 567 giá trị thiếu.

Các biến còn lại không có dữ liệu bị thiếu.

Tổng cộng có 127162 ô dữ liệu bị trống.

2.10. Kiểm tra dữ liệu bị trùng lặp

sum(duplicated(crime_data))
## [1] 9386

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

Hàm duplicated(): xác định các dòng bị trùng lặp trong bộ dữ liệu, trả về TRUE/FALSE

Hàm sum(): tính tổng số dòng TRUE, tức là tổng số dòng bị trùng

=> sum(duplicated(crime_data)) cho biết có bao nhiêu quan sát trùng lặp trong dữ liệu

Ý nghĩa thống kê:

Kết quả cho thấy 9.386 quan sát bị trùng lặp.

Trong bộ dữ liệu, mỗi dòng (một quan sát) không nhất thiết tương ứng với một vụ án duy nhất, có thể có nhiều nạn nhân trong cùng một sự việc, các biến như date, area, crime_type… sẽ giống nhau, khiến hệ thống nhận diện là trùng lặp về mặt kỹ thuật, dù thực tế chúng phản ánh các đối tượng khác nhau trong cùng vụ án.

Nếu loại bỏ các bản ghi này, dữ liệu sẽ mất thông tin về số lượng nạn nhân, giới tính hoặc độ tuổi — những yếu tố quan trọng trong phân tích hành vi tội phạm.

Vì vậy, nhóm giữ nguyên toàn bộ các bản ghi trùng lặp để đảm bảo tính toàn vẹn, đầy đủ và chính xác của bộ dữ liệu, đặc biệt cho các phân tích liên quan đến nhân khẩu học của nạn nhân.

CHƯƠNG 3: XỬ LÝ DỮ LIỆU

1. Làm sạch dữ liệu

crime <- na.omit(crime_data)

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

na.omit() là hàm trong R dùng để loại bỏ toàn bộ các dòng (quan sát) có chứa giá trị thiếu (NA) trong bất kỳ biến nào.

Biến crime được tạo mới từ crime_data sau khi đã loại bỏ các dòng có dữ liệu thiếu, đảm bảo bộ dữ liệu đầu vào cho các bước phân tích sau đầy đủ và không bị gián đoạn bởi NA.

2. Chuyển đổi định dạng ngày tháng năm

crime$date <- as.Date(crime$date)
class(crime$date)
## [1] "Date"

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

Hàm as.Date() dùng để chuyển đổi dữ liệu thời gian từ dạng ký tự hoặc dạng thời gian phức hợp (như POSIXct/POSIXt) sang định dạng ngày (Date) trong R.

Lệnh class(crime$date) giúp kiểm tra kiểu dữ liệu của biến date sau khi chuyển đổi, đảm bảo rằng biến này đã được nhận diện đúng là kiểu Date.

3. Tạo thêm biến year từ cột date

crime$year <- format(as.Date(crime$date), "%Y")

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

Hàm format() được dùng để trích xuất thông tin cụ thể từ biến ngày tháng, ở đây là năm (“%Y”).

Lệnh as.Date(crime$date) đảm bảo rằng biến date được định dạng đúng kiểu ngày trước khi trích xuất.

Sau câu lệnh này, cột mới tên là year sẽ được tạo ra, lưu trữ năm xảy ra vụ án dưới dạng ký tự (character).

4. Lọc dữ liệu theo điều kiện

crime <- crime %>%
  filter(age >= 0, year >= 2020 , year <= 2023) 

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

%>% là toán tử pipe trong dplyr, giúp nối các thao tác xử lý dữ liệu một cách trực quan.

filter() là hàm dùng để lọc các quan sát theo điều kiện nhất định.

Ở đây: age >= 0: loại bỏ các giá trị tuổi âm, đảm bảo tuổi của nạn nhân hợp lệ. year >= 2020 & year <= 2023: giữ lại các quan sát xảy ra trong giai đoạn nghiên cứu (2020–2023).

5. Điều chỉnh tọa độ địa lý bị sai lệch

crime <- crime %>%
  mutate(
    lat = as.numeric(lat),
    lon = as.numeric(lon)
  ) %>%
  mutate(
    lat = ifelse(lat > 100, lat / 10, lat),
    lon = ifelse(lon > -200, lon * 100, lon)
  )

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

mutate() là hàm trong dplyr dùng để tạo mới hoặc chỉnh sửa các biến.

as.numeric(lat) và as.numeric(lon) chuyển các cột lat (vĩ độ) và lon (kinh độ) từ kiểu ký tự (character) sang kiểu số (numeric) để thực hiện các phép toán.

ifelse(lat > 100, lat / 10, lat): nếu giá trị vĩ độ lớn hơn 100 (không hợp lý cho Los Angeles, vì LA nằm khoảng 33–34 độ vĩ bắc), sẽ chia cho 10 để chuẩn hóa về đúng giá trị thực.

ifelse(lon > -200, lon * 100, lon): nếu giá trị kinh độ lớn hơn -200 (nhận dạng các giá trị bị sai định dạng âm hoặc thiếu dấu), nhân 100 để điều chỉnh về tọa độ hợp lý.

Ý nghĩa thống kê:

Dữ liệu ban đầu có một số giá trị lat và lon bị sai lệch do lỗi nhập liệu hoặc định dạng, nằm ngoài phạm vi thực tế của Los Angeles (LAT ≈ 33.7–34.4, LON ≈ -118.6 đến -118.1). Các giá trị này bị lệch một bậc thập phân nên được hiệu chỉnh để đảm bảo tọa độ chính xác trên bản đồ.

Việc chuẩn hóa các tọa độ giúp các thao tác trực quan hóa không gặp lỗi, đảm bảo bản đồ tội phạm hiển thị đúng vị trí các vụ án.

6. Xem lại cấu trúc dữ liệu sau khi làm sạch

str(crime)
## tibble [760,049 × 12] (S3: tbl_df/tbl/data.frame)
##  $ date        : Date[1:760049], format: "2020-01-03" "2020-08-02" ...
##  $ area        : chr [1:760049] "Wilshire" "Central" "Southwest" "Van Nuys" ...
##  $ district    : num [1:760049] 784 182 356 964 666 ...
##  $ severity    : num [1:760049] 1 1 1 1 2 2 2 2 2 2 ...
##  $ crime_type  : chr [1:760049] "VEHICLE - STOLEN" "BURGLARY FROM VEHICLE" "BIKE - STOLEN" "SHOPLIFTING-GRAND THEFT ($950.01 & OVER)" ...
##  $ age         : num [1:760049] 0 47 19 19 28 41 25 27 24 26 ...
##  $ sex         : chr [1:760049] "M" "M" "X" "M" ...
##  $ premise_type: chr [1:760049] "STREET" "BUS STOP/LAYOVER (ALSO QUERY 124)" "MULTI-UNIT DWELLING (APARTMENT, DUPLEX, ETC)" "CLOTHING STORE" ...
##  $ status      : chr [1:760049] "Adult Arrest" "Invest Cont" "Invest Cont" "Invest Cont" ...
##  $ lat         : num [1:760049] 34038 34044 3402 34158 34094 ...
##  $ lon         : num [1:760049] -1183506 -1182628 -1183002 -1184387 -1183277 ...
##  $ year        : chr [1:760049] "2020" "2020" "2020" "2020" ...
##  - attr(*, "na.action")= 'omit' Named int [1:127152] 14 25 28 34 35 56 77 82 83 95 ...
##   ..- attr(*, "names")= chr [1:127152] "14" "25" "28" "34" ...

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

str() là hàm cơ bản trong R dùng để hiển thị cấu trúc tổng quan của dữ liệu: bao gồm số dòng, số cột, tên biến, kiểu dữ liệu từng cột và một số giá trị đầu tiên.

Ý nghĩa thống kê:

Sau quá trình làm sạch, bộ dữ liệu “Crime Data from 2020 to Present” thu thập từ Sở Cảnh sát Los Angeles (LAPD) còn lại 760.049 quan sát và 12 biến.

7. Phân nhóm độ tuổi nạn nhân

crime <- crime %>%
  mutate(age_group = case_when(
    age < 18 ~ "Vị thành niên",
    age >= 18 & age < 40 ~ "Thanh niên",
    age >= 40 & age < 60 ~ "Trung niên",
    age >= 60 ~ "Cao tuổi",
    TRUE ~ NA_character_
  ))
print(crime)
## # A tibble: 760,049 × 13
##    date       area  district severity crime_type   age sex   premise_type status
##    <date>     <chr>    <dbl>    <dbl> <chr>      <dbl> <chr> <chr>        <chr> 
##  1 2020-01-03 Wils…      784        1 VEHICLE -…     0 M     STREET       Adult…
##  2 2020-08-02 Cent…      182        1 BURGLARY …    47 M     BUS STOP/LA… Inves…
##  3 2020-04-11 Sout…      356        1 BIKE - ST…    19 X     MULTI-UNIT … Inves…
##  4 2020-10-03 Van …      964        1 SHOPLIFTI…    19 M     CLOTHING ST… Inves…
##  5 2020-08-17 Holl…      666        2 THEFT OF …    28 M     SIDEWALK     Inves…
##  6 2020-01-12 Sout…     1826        2 THEFT OF …    41 M     SINGLE FAMI… Inves…
##  7 2020-03-07 Cent…      182        2 THEFT OF …    25 M     MULTI-UNIT … Inves…
##  8 2020-12-05 Sout…      303        2 THEFT OF …    27 F     CELL PHONE … Inves…
##  9 2020-09-12 Newt…     1375        2 THEFT OF …    24 F     CYBERSPACE   Inves…
## 10 2020-12-31 Miss…     1974        2 BATTERY -…    26 M     MULTI-UNIT … Inves…
## # ℹ 760,039 more rows
## # ℹ 4 more variables: lat <dbl>, lon <dbl>, year <chr>, age_group <chr>

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

mutate() (dplyr) dùng để tạo biến mới hoặc chỉnh sửa biến hiện có.

case_when() cho phép tạo nhiều điều kiện logic: mỗi điều kiện đi kèm với nhãn kết quả tương ứng.

Trong phần này:

age < 18 ~ “Vị thành niên”: nếu tuổi nhỏ hơn 18, gán nhãn “Vị thành niên”.

age >= 18 & age < 40 ~ “Thanh niên”: tuổi từ 18 đến dưới 40 là “Thanh niên”.

age >= 40 & age < 60 ~ “Trung niên”: tuổi từ 40 đến dưới 60 là “Trung niên”.

age >= 60 ~ “Cao tuổi”: tuổi từ 60 trở lên là “Cao tuổi”.

TRUE ~ NA_character_: tất cả các giá trị khác (không xác định) sẽ gán NA.

8. Chuẩn hóa dữ liệu biến mức độ phạm tội

crime <- crime %>% mutate(severity_label = ifelse(severity == 1, "Nghiêm trọng", "Nhẹ"))

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

mutate() (dplyr) được dùng để tạo biến mới hoặc cập nhật biến hiện có.

ifelse(condition, value_if_true, value_if_false) là hàm điều kiện cơ bản trong R:

condition: severity == 1 nghĩa là nếu mức độ tội phạm = 1.

value_if_true: gán nhãn “Nghiêm trọng” nếu điều kiện đúng.

value_if_false: gán nhãn “Nhẹ” nếu điều kiện sai (tức mức độ = 2)

9. Chuẩn hóa giới tính nạn nhân

crime <- crime %>%
  mutate(sex = toupper(sex))
crime <- crime %>%
  mutate(
    sex = case_when(
      sex == "M" ~ "Nam",
      sex == "F" ~ "Nữ",
      TRUE ~ "Không xác định"
    )
  )

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

toupper(sex): Chuyển toàn bộ giá trị trong cột sex thành chữ hoa, để đồng nhất hóa dữ liệu (“m”, “M” → “M”; “f”, “F” → “F”).

case_when(): Dùng để gán nhãn định tính dễ hiểu cho dữ liệu đã đồng nhất: “M” → “Nam”, “F” → “Nữ”. Các giá trị khác (không xác định, trống, hoặc bất thường) → “Không xác định”.

10. Tạo biến mới xác định tình trạng bắt giữ

crime <- crime %>%
  mutate(arrested = ifelse(status %in% c("Adult Arrest", "Juv Arrest"), "đã bắt giữ", "chưa bắt giữ"))

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

mutate() (dplyr) được dùng để tạo biến mới hoặc cập nhật biến hiện có.

ifelse(condition, value_if_true, value_if_false):

condition: status %in% c(“Adult Arrest”, “Juv Arrest”) nghĩa là kiểm tra xem giá trị trong cột status có nằm trong danh sách “Adult Arrest” (người lớn bị bắt) hoặc “Juv Arrest” (vị thành niên bị bắt) hay không.

value_if_true: nếu đúng, gán nhãn “đã bắt giữ”.

value_if_false: nếu sai, gán nhãn “chưa bắt giữ”.

11. Thu gọn loại tội phạm

unique(crime$crime_type)
##   [1] "VEHICLE - STOLEN"                                        
##   [2] "BURGLARY FROM VEHICLE"                                   
##   [3] "BIKE - STOLEN"                                           
##   [4] "SHOPLIFTING-GRAND THEFT ($950.01 & OVER)"                
##   [5] "THEFT OF IDENTITY"                                       
##   [6] "BATTERY - SIMPLE ASSAULT"                                
##   [7] "SODOMY/SEXUAL CONTACT B/W PENIS OF ONE PERS TO ANUS OTH" 
##   [8] "CRM AGNST CHLD (13 OR UNDER) (14-15 & SUSP 10 YRS OLDER)"
##   [9] "SEX,UNLAWFUL(INC MUTUAL CONSENT, PENETRATION W/ FRGN OBJ"
##  [10] "ASSAULT WITH DEADLY WEAPON, AGGRAVATED ASSAULT"          
##  [11] "LETTERS, LEWD  -  TELEPHONE CALLS, LEWD"                 
##  [12] "THEFT-GRAND ($950.01 & OVER)EXCPT,GUNS,FOWL,LIVESTK,PROD"
##  [13] "CRIMINAL THREATS - NO WEAPON DISPLAYED"                  
##  [14] "CHILD ANNOYING (17YRS & UNDER)"                          
##  [15] "CONTEMPT OF COURT"                                       
##  [16] "THEFT PLAIN - PETTY ($950 & UNDER)"                      
##  [17] "INTIMATE PARTNER - SIMPLE ASSAULT"                       
##  [18] "LEWD CONDUCT"                                            
##  [19] "THEFT PLAIN - ATTEMPT"                                   
##  [20] "BURGLARY"                                                
##  [21] "THEFT FROM MOTOR VEHICLE - GRAND ($950.01 AND OVER)"     
##  [22] "ROBBERY"                                                 
##  [23] "BUNCO, GRAND THEFT"                                      
##  [24] "BATTERY WITH SEXUAL CONTACT"                             
##  [25] "INTIMATE PARTNER - AGGRAVATED ASSAULT"                   
##  [26] "ORAL COPULATION"                                         
##  [27] "UNAUTHORIZED COMPUTER ACCESS"                            
##  [28] "VIOLATION OF RESTRAINING ORDER"                          
##  [29] "SHOPLIFTING - PETTY THEFT ($950 & UNDER)"                
##  [30] "VANDALISM - FELONY ($400 & OVER, ALL CHURCH VANDALISMS)" 
##  [31] "BRANDISH WEAPON"                                         
##  [32] "DOCUMENT FORGERY / STOLEN FELONY"                        
##  [33] "SEX OFFENDER REGISTRANT OUT OF COMPLIANCE"               
##  [34] "EMBEZZLEMENT, GRAND THEFT ($950.01 & OVER)"              
##  [35] "RAPE, FORCIBLE"                                          
##  [36] "VANDALISM - MISDEAMEANOR ($399 OR UNDER)"                
##  [37] "OTHER MISCELLANEOUS CRIME"                               
##  [38] "CHILD ABUSE (PHYSICAL) - SIMPLE ASSAULT"                 
##  [39] "CREDIT CARDS, FRAUD USE ($950.01 & OVER)"                
##  [40] "THREATENING PHONE CALLS/LETTERS"                         
##  [41] "SEXUAL PENETRATION W/FOREIGN OBJECT"                     
##  [42] "EXTORTION"                                               
##  [43] "OTHER ASSAULT"                                           
##  [44] "PICKPOCKET"                                              
##  [45] "ARSON"                                                   
##  [46] "DISTURBING THE PEACE"                                    
##  [47] "BUNCO, ATTEMPT"                                          
##  [48] "HUMAN TRAFFICKING - INVOLUNTARY SERVITUDE"               
##  [49] "PEEPING TOM"                                             
##  [50] "VIOLATION OF COURT ORDER"                                
##  [51] "FALSE POLICE REPORT"                                     
##  [52] "CONTRIBUTING"                                            
##  [53] "FALSE IMPRISONMENT"                                      
##  [54] "THEFT FROM MOTOR VEHICLE - PETTY ($950 & UNDER)"         
##  [55] "CHILD ABUSE (PHYSICAL) - AGGRAVATED ASSAULT"             
##  [56] "ATTEMPTED ROBBERY"                                       
##  [57] "CREDIT CARDS, FRAUD USE ($950 & UNDER"                   
##  [58] "CHILD STEALING"                                          
##  [59] "LEWD/LASCIVIOUS ACTS WITH CHILD"                         
##  [60] "INDECENT EXPOSURE"                                       
##  [61] "CHILD NEGLECT (SEE 300 W.I.C.)"                          
##  [62] "STALKING"                                                
##  [63] "DISHONEST EMPLOYEE - GRAND THEFT"                        
##  [64] "TRESPASSING"                                             
##  [65] "BURGLARY, ATTEMPTED"                                     
##  [66] "RAPE, ATTEMPTED"                                         
##  [67] "DISCHARGE FIREARMS/SHOTS FIRED"                          
##  [68] "PIMPING"                                                 
##  [69] "HUMAN TRAFFICKING - COMMERCIAL SEX ACTS"                 
##  [70] "VEHICLE - ATTEMPT STOLEN"                                
##  [71] "PANDERING"                                               
##  [72] "FIREARMS RESTRAINING ORDER (FIREARMS RO)"                
##  [73] "RESISTING ARREST"                                        
##  [74] "BURGLARY FROM VEHICLE, ATTEMPTED"                        
##  [75] "THEFT, PERSON"                                           
##  [76] "BATTERY POLICE (SIMPLE)"                                 
##  [77] "THEFT FROM PERSON - ATTEMPT"                             
##  [78] "FAILURE TO YIELD"                                        
##  [79] "BOMB SCARE"                                              
##  [80] "VEHICLE, STOLEN - OTHER (MOTORIZED SCOOTERS, BIKES, ETC)"
##  [81] "ASSAULT WITH DEADLY WEAPON ON POLICE OFFICER"            
##  [82] "BUNCO, PETTY THEFT"                                      
##  [83] "SHOTS FIRED AT INHABITED DWELLING"                       
##  [84] "DEFRAUDING INNKEEPER/THEFT OF SERVICES, $950 & UNDER"    
##  [85] "KIDNAPPING - GRAND ATTEMPT"                              
##  [86] "SHOTS FIRED AT MOVING VEHICLE, TRAIN OR AIRCRAFT"        
##  [87] "TILL TAP - GRAND THEFT ($950.01 & OVER)"                 
##  [88] "VIOLATION OF TEMPORARY RESTRAINING ORDER"                
##  [89] "THROWING OBJECT AT MOVING VEHICLE"                       
##  [90] "DOCUMENT WORTHLESS ($200.01 & OVER)"                     
##  [91] "KIDNAPPING"                                              
##  [92] "CRIMINAL HOMICIDE"                                       
##  [93] "PURSE SNATCHING"                                         
##  [94] "THEFT FROM MOTOR VEHICLE - ATTEMPT"                      
##  [95] "DISHONEST EMPLOYEE - PETTY THEFT"                        
##  [96] "EMBEZZLEMENT, PETTY THEFT ($950 & UNDER)"                
##  [97] "CHILD PORNOGRAPHY"                                       
##  [98] "WEAPONS POSSESSION/BOMBING"                              
##  [99] "DRIVING WITHOUT OWNER CONSENT (DWOC)"                    
## [100] "REPLICA FIREARMS(SALE,DISPLAY,MANUFACTURE OR DISTRIBUTE)"
## [101] "LYNCHING"                                                
## [102] "RECKLESS DRIVING"                                        
## [103] "SHOPLIFTING - ATTEMPT"                                   
## [104] "COUNTERFEIT"                                             
## [105] "DEFRAUDING INNKEEPER/THEFT OF SERVICES, OVER $950.01"    
## [106] "BATTERY ON A FIREFIGHTER"                                
## [107] "CRUELTY TO ANIMALS"                                      
## [108] "ILLEGAL DUMPING"                                         
## [109] "PROWLER"                                                 
## [110] "DRUGS, TO A MINOR"                                       
## [111] "THEFT, COIN MACHINE - PETTY ($950 & UNDER)"              
## [112] "DOCUMENT WORTHLESS ($200 & UNDER)"                       
## [113] "MANSLAUGHTER, NEGLIGENT"                                 
## [114] "PETTY THEFT - AUTO REPAIR"                               
## [115] "THEFT, COIN MACHINE - ATTEMPT"                           
## [116] "TILL TAP - PETTY ($950 & UNDER)"                         
## [117] "PURSE SNATCHING - ATTEMPT"                               
## [118] "LYNCHING - ATTEMPTED"                                    
## [119] "BIKE - ATTEMPTED STOLEN"                                 
## [120] "GRAND THEFT / AUTO REPAIR"                               
## [121] "CONSPIRACY"                                              
## [122] "BRIBERY"                                                 
## [123] "GRAND THEFT / INSURANCE FRAUD"                           
## [124] "DRUNK ROLL"                                              
## [125] "CHILD ABANDONMENT"                                       
## [126] "THEFT, COIN MACHINE - GRAND ($950.01 & OVER)"            
## [127] "DISRUPT SCHOOL"                                          
## [128] "PICKPOCKET, ATTEMPT"                                     
## [129] "TELEPHONE PROPERTY - DAMAGE"                             
## [130] "BEASTIALITY, CRIME AGAINST NATURE SEXUAL ASSLT WITH ANIM"
## [131] "BIGAMY"                                                  
## [132] "FAILURE TO DISPERSE"                                     
## [133] "FIREARMS EMERGENCY PROTECTIVE ORDER (FIREARMS EPO)"      
## [134] "INCEST (SEXUAL ACTS BETWEEN BLOOD RELATIVES)"            
## [135] "BLOCKING DOOR INDUCTION CENTER"                          
## [136] "INCITING A RIOT"                                         
## [137] "DISHONEST EMPLOYEE ATTEMPTED THEFT"
crime <- crime %>%
  mutate(
    crime_group = case_when(
      grepl("THEFT|BURGLARY|ROBBERY|SHOPLIFT|LARCENY|VEHICLE|AUTO|CAR", crime_type, ignore.case = TRUE) ~ "trộm cắp - cướp giật",
      grepl("ASSAULT|BATTERY|KIDNAPPING|THREAT|STALKING", crime_type, ignore.case = TRUE) ~ "bạo lực - hành hung",
      grepl("RAPE|SEX|HARASS|LEWD", crime_type, ignore.case = TRUE) ~ "tình dục - quấy rối",
      grepl("HOMICIDE|MURDER|MANSLAUGHTER", crime_type, ignore.case = TRUE) ~ "giết người",
      grepl("DRUG|NARCOTIC", crime_type, ignore.case = TRUE) ~ "ma túy",
      grepl("FRAUD|FORGERY|IDENTITY|SCAM|EMBEZZLEMENT|BRIBERY", crime_type, ignore.case = TRUE) ~ "gian lận - tài chính",
      grepl("ARSON|FIRE", crime_type, ignore.case = TRUE) ~ "phóng hỏa",
      TRUE ~ "khác"
    )
  )

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

unique() là hàm dùng để liệt kê các giá trị duy nhất trong một vector hoặc cột dữ liệu. Ở đây, crime$crime_type là cột chứa tên loại tội phạm cụ thể. Kết quả trả về là danh sách tất cả các loại tội phạm xuất hiện trong dữ liệu.

mutate() (dplyr) dùng để tạo biến mới crime_group.

case_when() là hàm điều kiện nhiều nhánh, gán giá trị khác nhau cho crime_group dựa trên nội dung của crime_type.

grepl(pattern, x, ignore.case = TRUE):

pattern: các từ khóa đại diện cho nhóm tội phạm.

x: cột crime_type.

ignore.case = TRUE: không phân biệt chữ hoa – chữ thường.

TRUE ~ “khác”: nếu crime_type không khớp bất kỳ nhóm nào, gán nhãn “khác”.

CHƯƠNG 4: CÁC THỐNG KÊ CƠ BẢN

1. Thống kê mô tả độ tuổi nạn nhân

summary(crime$age)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##    0.00   23.00   34.00   34.23   48.00   99.00

Giải thích kỹ thuật

Lệnh summary(crime$age) trả về các chỉ số tóm tắt cơ bản của biến số (age):

Min.: giá trị nhỏ nhất.

1st Qu.: giá trị phần tư thứ nhất.

Median: trung vị.

Mean: giá trị trung bình.

3rd Qu.: phần tư thứ ba.

Max.: giá trị lớn nhất.

Ý nghĩa thống kê:

Dữ liệu cho thấy độ tuổi nạn nhân dao động từ 0 đến 99 tuổi, với trung vị là 34 tuổi và giá trị trung bình xấp xỉ 34,23 tuổi, chứng tỏ phần lớn nạn nhân nằm trong nhóm người trưởng thành đang trong độ tuổi lao động.

Giá trị nhỏ nhất bằng 0 nhiều khả năng phản ánh các trường hợp trẻ sơ sinh hoặc thai nhi bị ảnh hưởng trong các vụ án hình sự (như bạo hành, tấn công khi người mẹ mang thai).

Điều này cũng phản ánh rõ hơn mức độ đa dạng và nghiêm trọng của các loại tội phạm ảnh hưởng đến các nhóm tuổi khác nhau trong xã hội.

2. Tổng hợp theo giới tính

sex_summary <- crime %>%
  count(sex, name = "Tần số") %>%
  mutate(
    `Tần suất (%)` = round(`Tần số` / sum(`Tần số`) * 100, 2)) %>%
  arrange(desc(`Tần số`))

sex_summary

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

crime %>%: Dùng toán tử pipe (%>%) để thực hiện các bước xử lý liên tiếp mà không cần tạo biến trung gian.

count(sex, name = “Tần số”): Đếm số lượng bản ghi cho từng giới tính (sex). Tạo cột kết quả là Tần số (số lần xuất hiện của mỗi giới tính).

mutate(): Thêm một cột mới là Tần suất (%).

Hàm round(…, 2) giúp làm tròn đến 2 chữ số thập phân.

arrange(desc(Tần số)): Sắp xếp kết quả giảm dần theo tần số.

Ý nghĩa thống kê:

Bảng trên cho thấy dữ liệu có 359.890 trường hợp nam (47,4%), 321.773 nữ (42,3%) và 78.386 trường hợp không xác định giới tính (10,3%).

Điều này chứng tỏ nam giới chiếm tỷ trọng cao hơn, phản ánh xu hướng phổ biến trong tội phạm – nam giới thường tham gia hoặc bị bắt giữ trong các vụ án nhiều hơn nữ giới.

3. Tổng hợp số vụ án theo khu vực

crime_by_area <- crime %>%
  group_by(area) %>%
  summarise(`tổng số vụ` = n()) %>%
  arrange(desc(`tổng số vụ`))
print(crime_by_area)
## # A tibble: 21 × 2
##    area        `tổng số vụ`
##    <chr>              <int>
##  1 Central            54856
##  2 77th Street        46669
##  3 Pacific            43834
##  4 Southwest          42286
##  5 Hollywood          42089
##  6 N Hollywood        38385
##  7 Olympic            38357
##  8 Wilshire           37458
##  9 Southeast          37277
## 10 West LA            36573
## # ℹ 11 more rows

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

group_by(area): Gom nhóm dữ liệu theo từng khu vực (area).

summarise(tổng số vụ = n()): Hàm n() đếm số dòng (bản ghi) trong mỗi nhóm — tương ứng với tổng số vụ phạm tội xảy ra trong từng khu vực. Kết quả được gán vào cột mới tên là “tổng số vụ”.

arrange(desc(tổng số vụ)): Sắp xếp bảng theo thứ tự giảm dần.

Ý nghĩa thống kê:

Khu vực “Central” có số vụ án cao nhất (54.856 vụ) – đây là trung tâm thành phố, mật độ dân cư và hoạt động kinh tế cao, nên dễ phát sinh các vụ việc như trộm cắp, bạo lực, hoặc xung đột xã hội cao hơn.

Tiếp theo là các khu 77th Street, Pacific, Southwest, Hollywood có số vụ phạm tội dao động từ 42.000 – 46.000 vụ cũng là những nơi có lưu lượng người và du khách lớn.

Các khu Foothill (24.512 vụ), Hollenbeck (26.781 vụ), Mission (29.004 vụ) có số vụ thấp hơn, có thể do vị trí ngoại ô hoặc ít hoạt động thương mại hơn, cho thấy đây là nơi bình yên hơn.

4. Tổng hợp theo loại tội phạm

crime_by_group <- crime %>%
  group_by(crime_group) %>%
  summarise(`tổng số vụ` = n()) %>%
  arrange(desc(`tổng số vụ`))
print(crime_by_group)
## # A tibble: 8 × 2
##   crime_group          `tổng số vụ`
##   <chr>                       <int>
## 1 trộm cắp - cướp giật       368839
## 2 bạo lực - hành hung        210061
## 3 khác                       154048
## 4 tình dục - quấy rối         16035
## 5 phóng hỏa                    6473
## 6 gian lận - tài chính         3111
## 7 giết người                   1471
## 8 ma túy                         11

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

group_by(crime_group): Gom nhóm dữ liệu theo loại tội phạm (đã được bạn phân loại ở bước trước: trộm cắp, bạo lực, tình dục,..).

summarise(tổng số vụ = n()): Hàm n() đếm số dòng trong mỗi nhóm, tức là số vụ phạm tội thuộc từng loại. Kết quả được lưu trong cột “tổng số vụ”.

arrange(desc(tổng số vụ)): Sắp xếp dữ liệu theo thứ tự giảm dần.

print(crime_by_group): In kết quả ra màn hình dưới dạng bảng thống kê tổng hợp.

Ý nghĩa thống kê:

“Trộm cắp – cướp giật” là nhóm chiếm tỷ trọng áp đảo với 368.839 vụ, phản ánh thực tế đây là loại tội phạm phổ biến nhất, thường xảy ra tại các khu vực đông dân hoặc thương mại.

“Bạo lực – hành hung” xếp thứ hai với 210.061 vụ, cho thấy mức độ đáng lo ngại về xung đột và tấn công cá nhân trong cộng đồng.

Nhóm “Khác” (154.048 vụ) gồm các tội phạm không nằm trong các nhóm chính, thể hiện sự đa dạng về hành vi phạm pháp.

Các nhóm còn lại như “Tình dục – quấy rối” (16.035 vụ), “Phóng hỏa” (6.473 vụ) và “Gian lận – tài chính” (3.111 vụ) có quy mô nhỏ hơn nhưng vẫn cần chú ý do tính nghiêm trọng.

“Giết người” (1.471 vụ) và “Ma túy” (11 vụ) chiếm tỷ lệ rất nhỏ, có thể do giới hạn dữ liệu ghi nhận hoặc cách mã hóa ban đầu.

Nhóm tội phạm tại Los Angeles nghiêng mạnh về tội phạm tài sản, tiếp đến là tội phạm bạo lực, phản ánh đặc điểm đô thị đông dân, nhiều hoạt động thương mại và chênh lệch kinh tế – xã hội.

5. Thống kê theo độ tuổi trung bình của nạn nhân ở từng nhóm tội phạm

crime %>%
  group_by(crime_group) %>%
  summarise(`tuổi trung bình` = mean(age, na.rm = TRUE))

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

crime %>%: dùng toán tử pipe từ dplyr để chuyền dữ liệu crime vào chuỗi thao tác tiếp theo.

group_by(crime_group): gộp (nhóm) các bản ghi theo biến crime_group.

summarise(tuổi trung bình = mean(age, na.rm = TRUE)): tính giá trị trung bình của cột age cho mỗi nhóm. mean(age, na.rm = TRUE) tính trung bình, loại bỏ NA (tham số na.rm = TRUE) để tránh lỗi hoặc làm sai lệch kết quả khi có giá trị thiếu. Kết quả được lưu vào cột mới đặt tên tuổi trung bình.

Ý nghĩa thống kê:

Kết quả phân tích cho thấy độ tuổi trung bình của nạn nhân khác biệt rõ rệt giữa các nhóm tội phạm, phản ánh đặc trưng xã hội — hành vi của từng loại tội phạm:

Cao nhất là nhóm gian lận – tài chính (≈37 tuổi). Nạn nhân thường là người trưởng thành, có công việc và tài sản, tội phạm kinh tế thường nhắm đến những đối tượng có khả năng tài chính và tham gia giao dịch.

Bạo lực – hành hung (≈36 tuổi) & Giết người (≈36 tuổi). Hai nhóm này có độ tuổi trung bình tương đồng, cho thấy các vụ việc nghiêm trọng thường xảy ra giữa người trưởng thành (mâu thuẫn cá nhân, gia đình hoặc xã hội).

Trộm cắp – cướp giật (≈34 tuổi). Nạn nhân chủ yếu là người trong độ tuổi lao động — thường xuyên di chuyển và mang theo tài sản cá nhân.

Tình dục – quấy rối (≈29 tuổi). Nạn nhân có xu hướng trẻ hơn, có thể gồm học sinh, sinh viên hoặc người trẻ đang lao động; nhóm này cần được bảo vệ đặc biệt.

Phóng hỏa (≈21 tuổi). Độ tuổi trung bình khá trẻ, gợi ý liên quan đến các vụ phá hoại hoặc tai nạn nơi tập thể, khu dân cư hoặc hành vi của thanh thiếu niên.

Ma túy (≈8 tuổi). Giá trị trung bình bất thường thấp — nhiều khả năng phản ánh trường hợp nạn nhân là trẻ em (hoặc thai nhi bị ảnh hưởng gián tiếp), là tín hiệu cảnh báo về tác động lan tỏa của ma túy đến nhóm tuổi nhỏ.

Khác (≈32,32 tuổi). Nhóm bao gồm nhiều loại tội không thuộc các nhóm chính; độ tuổi trung bình cho thấy đa số nạn nhân vẫn là người trưởng thành.

6. Bảng tần số và tỷ lệ nhóm tuổi qua các năm

crime %>%
group_by(year, age_group) %>%
summarise(`tần số` = n()) %>%
group_by(year) %>%
mutate(`tỷ lệ (%)` = round(`tần số` / sum(`tần số`) * 100, 2))
## `summarise()` has grouped output by 'year'. You can override using the
## `.groups` argument.

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

crime %>%: dùng toán tử pipe từ dplyr để chuyền dữ liệu crime vào chuỗi thao tác tiếp theo.

group_by(year, age_group): nhóm dữ liệu theo năm (year) và nhóm tuổi (age_group), để mỗi nhóm đại diện cho một tổ hợp năm – độ tuổi.

summarise(tần số = n()): tính tần số (số lượng bản ghi) trong từng nhóm (tức là tổng số nạn nhân mỗi năm – mỗi độ tuổi).

group_by(year): tiếp tục nhóm theo năm để chuẩn bị tính tỷ lệ phần trăm trong từng năm.

mutate(tỷ lệ (%) = round(tần số / sum(tần số) * 100, 2)): tính tỷ lệ phần trăm (%) của từng nhóm tuổi trong mỗi năm, làm tròn 2 chữ số thập phân.

Ý nghĩa thống kê:

Kết quả thống kê cho thấy sự thay đổi đáng chú ý trong cơ cấu độ tuổi nạn nhân từ năm 2020–2023:

Nhóm Thanh niên (18–39 tuổi) chiếm tỷ lệ cao nhất (≈44–47%) trong toàn giai đoạn. Đây là nhóm hoạt động xã hội, di chuyển nhiều, thường xuyên tiếp xúc với môi trường có rủi ro tội phạm — đặc biệt là trộm cắp và bạo lực.

Nhóm Trung niên (40–59 tuổi) duy trì ổn định quanh mức 26–28%, phản ánh nhóm có mức độ tham gia xã hội cao nhưng ít rủi ro hơn thanh niên.

Nhóm Cao tuổi (≥60 tuổi) chiếm khoảng 10–11%, tỷ lệ tương đối ổn định giữa các năm. Tuy nhiên, đây là nhóm dễ tổn thương do hạn chế thể chất và phụ thuộc kinh tế.

Nhóm Vị thành niên (<18 tuổi) có xu hướng tăng dần, từ 16,2% (2020) lên 19,13% (2023) — dấu hiệu đáng lo ngại, cho thấy độ tuổi nạn nhân đang bị trẻ hóa, đặc biệt trong bối cảnh môi trường mạng phát triển và gia tăng hành vi xâm hại.

7. Tổng hợp theo năm và khu vực

crime_by_area_year <- crime %>%
  group_by(year, area) %>%
  summarise(`tổng số vụ` = n()) %>%
  arrange(area, year)
## `summarise()` has grouped output by 'year'. You can override using the
## `.groups` argument.
print(crime_by_area_year)
## # A tibble: 84 × 3
## # Groups:   year [4]
##    year  area        `tổng số vụ`
##    <chr> <chr>              <int>
##  1 2020  77th Street        11515
##  2 2021  77th Street        11091
##  3 2022  77th Street        12492
##  4 2023  77th Street        11571
##  5 2020  Central            10869
##  6 2021  Central            12202
##  7 2022  Central            16334
##  8 2023  Central            15451
##  9 2020  Devonshire          7027
## 10 2021  Devonshire          7321
## # ℹ 74 more rows

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

group_by(year, area): gom dữ liệu theo từng năm và khu vực, đảm bảo mỗi nhóm thể hiện đúng số vụ xảy ra trong khu vực đó ở từng năm.

summarise() đếm tổng số vụ bằng hàm n().

arrange(area, year) sắp xếp kết quả theo tên khu vực, sau đó theo năm.

Ý nghĩa thống kê:

Khi so sánh các năm, có thể thấy sự phân bố tội phạm không đồng đều giữa các khu vực.

Những nơi như Central, 77th Street, và Hollywood thường xuyên nằm trong nhóm có tổng số vụ cao, cho thấy đây là các khu vực trung tâm, đông dân cư và nhiều hoạt động xã hội – yếu tố dễ kéo theo phạm pháp.

Ngược lại, các khu vực như Foothill, Hollenbeck, hay Mission có số vụ ít hơn, phản ánh đặc trưng dân cư ổn định hoặc kiểm soát an ninh hiệu quả hơn.

Từ năm 2020 đến 2022, tổng số vụ tăng rõ rệt, đặc biệt ở khu Central, 77th Street và Hollywood. Từ 2023, số vụ có dấu hiệu ổn định hơn, nhưng các khu trung tâm như Central vẫn luôn dẫn đầu toàn thành phố.

Các khu vực như Foothill, Hollenbeck, và Mission thường nằm ở nhóm ít tội phạm hơn.

8. Phân tích theo khu vực và giới tính

crime_area_sex <- crime %>%
  group_by(area, sex) %>%
  summarise(`tổng số vụ` = n()) %>%
  arrange(area)
## `summarise()` has grouped output by 'area'. You can override using the
## `.groups` argument.
print(crime_area_sex)
## # A tibble: 63 × 3
## # Groups:   area [21]
##    area        sex            `tổng số vụ`
##    <chr>       <chr>                 <int>
##  1 77th Street Không xác định         4483
##  2 77th Street Nam                   17889
##  3 77th Street Nữ                    24297
##  4 Central     Không xác định         6047
##  5 Central     Nam                   29780
##  6 Central     Nữ                    19029
##  7 Devonshire  Không xác định         5338
##  8 Devonshire  Nam                   13418
##  9 Devonshire  Nữ                    12598
## 10 Foothill    Không xác định          873
## # ℹ 53 more rows

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

group_by(area, sex): Gom nhóm dữ liệu theo hai biến định tính: khu vực (area) và giới tính (sex).

summarise(tổng số vụ = n()): Tính tần suất (số lượng bản ghi) trong từng nhóm – tức là số vụ tội phạm xảy ra trong từng tổ hợp area–sex.

arrange(area): Sắp xếp kết quả theo tên khu vực.

Ý nghĩa thống kê:

Từ kết quả, ta thấy xu hướng:

Nam giới thường có số vụ cao hơn nữ, đặc biệt ở các khu như Central (Nam: 29,780 và Nữ: 19,029) hay Hollywood (Nam: 19,702 và Nữ: 15,709).

Tuy nhiên, ở khu vực 77th Street: Nữ cao hơn (24.297 so với Nam 17.889). Southeast có 20.004 nữ so với 13.658 nam. Điều này cho thấy các khu vực này có nguy cơ cao đối với phụ nữ, đặc biệt là tội phạm trộm cắp, quấy rối hoặc bạo lực.

9. Số vụ mỗi năm theo từng nhóm tội phạm.

crime %>%
  group_by(year, crime_group) %>%
  summarise(total = n()) %>%
  arrange(crime_group, year)
## `summarise()` has grouped output by 'year'. You can override using the
## `.groups` argument.

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

group_by(year, crime_group): Gom nhóm dữ liệu theo năm và loại tội phạm.

summarise(total = n()): Đếm số lượng bản ghi (số vụ) trong mỗi nhóm.

arrange(crime_group, year): Sắp xếp theo tên nhóm tội phạm, sau đó theo năm.

Ý nghĩa thống kê:

Kết quả cho thấy nhóm “trộm cắp – cướp giật” chiếm tỷ trọng lớn nhất, dao động từ 79.173 vụ năm 2020 lên 105.176 vụ năm 2022, rồi giảm nhẹ còn 100.172 vụ năm 2023. Nhóm “bạo lực – hành hung” đứng thứ hai, tăng nhẹ từ 50.313 lên 54.277 vụ, phản ánh xu hướng gia tăng xung đột xã hội hậu đại dịch.

Các nhóm khác như “gian lận – tài chính”, “tình dục – quấy rối”, “phóng hỏa” và “giết người” có quy mô nhỏ hơn, nhìn chung ổn định hoặc giảm nhẹ. Nhóm “ma túy” ghi nhận rất ít vụ.

Tổng thể, giai đoạn 2020–2022 tội phạm tăng rõ rệt, sau đó chững lại vào năm 2023, cho thấy chính sách an ninh bắt đầu phát huy hiệu quả.

10. Nhóm loại tội phạm phổ biến nhất theo năm

crime %>%
group_by(year, crime_group) %>%
summarise(`tổng số vụ` = n()) %>%
slice_max(`tổng số vụ`, n = 3)
## `summarise()` has grouped output by 'year'. You can override using the
## `.groups` argument.

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

group_by(year, crime_group): Gom nhóm dữ liệu theo năm và loại tội phạm.

summarise(total = n()): Đếm số lượng bản ghi (số vụ) trong mỗi nhóm.

arrange(crime_group, year): Sắp xếp theo tên nhóm tội phạm, sau đó theo năm.

slice_max(…, n = 3): lấy 3 nhóm có số vụ cao nhất mỗi năm

Ý nghĩa thống kê:

Bảng kết quả cho thấy xu hướng ổn định trong cơ cấu tội phạm qua các năm:

“Trộm cắp – cướp giật” luôn là nhóm phổ biến nhất, với số vụ tăng từ 79.173 (2020) lên 105.176 (2022), sau đó giảm nhẹ còn 100.172 (2023).

“Bạo lực – hành hung” giữ vị trí thứ hai, tăng từ 50.313 lên 54.277 vụ, cho thấy xu hướng xung đột cá nhân – xã hội ngày càng rõ rệt.

Nhóm “Khác” (bao gồm các hành vi phạm pháp nhẹ, không phân loại cụ thể) dao động quanh 38.000–39.000 vụ, phản ánh mức độ ổn định.

Kết quả này cho thấy mức độ tập trung cao của tội phạm vào hai nhóm chính.

Giai đoạn 2020–2022 chứng kiến sự gia tăng mạnh, có thể liên quan đến tác động kinh tế – xã hội hậu COVID-19, tình trạng thất nghiệp hoặc thiếu an ninh đô thị. Đến năm 2023, dữ liệu cho thấy xu hướng ổn định trở lại, cho thấy hiệu quả của các biện pháp kiểm soát tội phạm hoặc tăng cường tuần tra.

11. Thống kê theo giới tính và nhóm tuổi

crime %>%
group_by(sex, age_group) %>%
summarise(`tổng số vụ` = n()) %>%
arrange(sex, desc(`tổng số vụ`))
## `summarise()` has grouped output by 'sex'. You can override using the `.groups`
## argument.

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

group_by(sex, age_group): chia dữ liệu thành từng nhóm theo giới tính (sex) và nhóm tuổi (age_group).

summarise(n()): đếm số lượng bản ghi (số vụ) trong mỗi nhóm → tạo cột tổng số vụ.

arrange(sex, desc(…)): sắp xếp kết quả theo giới tính, và trong từng giới thì xếp giảm dần theo số vụ.

ý nghĩa thống kê

Nhóm thanh niên có số vụ cao nhất ở cả nam (165.565 vụ) và nữ (174.133 vụ).

Nam giới có số vụ nhiều nhất ở nhóm trung niên với 110.892 vụ, cao hơn đáng kể so với nữ (93.805 vụ).

Nhóm vị thành niên thì số vụ ở nam gấp đôi nữ với 38.499 vụ so với nữ chỉ có 17.201 vụ

12. Tổng hợp số vụ theo khu vực và nhóm tuổi

crime %>%
group_by(area, age_group) %>%
summarise(`tổng số vụ` = n()) %>%
arrange(desc(`tổng số vụ`))
## `summarise()` has grouped output by 'area'. You can override using the
## `.groups` argument.

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

group_by(area, age_group): chia dữ liệu thành từng nhóm theo khu vực (area) và nhóm tuổi (age_group).

summarise(n()): đếm số lượng vụ việc (số bản ghi) trong mỗi nhóm → tạo biến tổng số vụ.

arrange(desc(…)): sắp xếp các nhóm có số vụ lớn nhất lên trước, giúp dễ nhận diện khu vực và độ tuổi có tần suất cao.

ý nghĩa thống kê:

Nhóm nạn nhân “Thanh niên” chiếm tỷ lệ cao nhất ở hầu hết khu vực, đặc biệt tại Central (≈28.000 vụ), Southwest, 77th Street, và Hollywood. Điều này phản ánh rằng thanh niên là nhóm dễ bị tổn thương nhất trong các vụ phạm pháp, có thể do thường xuyên hoạt động ngoài xã hội và tiếp xúc với nhiều yếu tố rủi ro.

Nhóm “Trung niên” đứng thứ hai, tập trung tại Central, 77th Street và Pacific, cho thấy mức độ rủi ro vẫn cao ở lứa tuổi lao động.

Nhóm “Vị thành niên” và “Cao tuổi” có số vụ thấp hơn đáng kể, tuy nhiên vẫn xuất hiện ở tất cả khu vực, chứng tỏ phạm vi tác động của tội phạm khá rộng.

Khu vực Central luôn dẫn đầu ở mọi nhóm tuổi. Đây có thể là vùng có mật độ dân cư cao hoặc tỉ lệ tội phạm trên đường phố lớn.

Thanh niên là nhóm nạn nhân phổ biến nhất, đặc biệt tại các khu vực đô thị trung tâm, trong khi người cao tuổi ít bị ảnh hưởng hơn nhưng không hoàn toàn an toàn.

13. Nhóm loại tội phạm phổ biến nhất ở từng khu vực

crime %>%
group_by(area, crime_group) %>%
summarise(`tổng số vụ` = n()) %>%
slice_max(`tổng số vụ`, n = 1)
## `summarise()` has grouped output by 'area'. You can override using the
## `.groups` argument.

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

group_by(area, crime_group): Gom nhóm dữ liệu theo khu vực và loại tội phạm.

summarise(tổng số vụ = n()): Đếm số vụ của từng loại tội phạm trong mỗi khu vực.

slice_max(tổng số vụ, n = 1): Lấy loại tội phạm chiếm nhiều vụ nhất trong từng khu vực.

Ý nghĩa thống kê:

Phần lớn các khu vực của Los Angeles có loại tội phạm phổ biến nhất là “trộm cắp - cướp giật”.

Các khu vực có số vụ cao nhất gồm Central (26.751 vụ), Pacific (25.438 vụ), West LA (22.298 vụ), Wilshire (21.053 vụ) và Hollywood (20.704 vụ). Đây đều là các trung tâm thương mại, du lịch và dân cư đông đúc.

Ngược lại, các khu 77th Street (17.661 vụ) và Southeast (14.836 vụ) có tội phạm phổ biến là “bạo lực - hành hung”, phản ánh đặc điểm xã hội phức tạp, mức độ xung đột và tội phạm đường phố cao hơn.

Nhìn tổng thể, tội phạm về tài sản chiếm ưu thế rõ rệt, trong khi tội phạm bạo lực chỉ nổi bật ở một số khu vực đặc thù.

14. Số vụ trung bình theo nhóm tuổi và năm

crime %>%
group_by(age_group, year) %>%
summarise(`trung bình vụ` = mean(n()))
## `summarise()` has grouped output by 'age_group'. You can override using the
## `.groups` argument.

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

group_by(age_group, year): gom nhóm dữ liệu theo nhóm tuổi và năm xảy ra tội phạm.

summarise(…): tính tóm tắt (ở đây là trung bình số vụ) cho mỗi nhóm.

mean(n()): ở đây bị sai về mặt công thức, vì n() chỉ là hàm đếm số bản ghi (số vụ), chứ không thể tính trung bình.

Ý nghĩa thống kê:

Kết quả cho thấy “Thanh niên” luôn chiếm số vụ trung bình cao nhất qua các năm (trên 78.000–95.000 vụ), phản ánh nhóm tuổi này có mức độ tham gia tội phạm cao nhất.

“Trung niên” đứng thứ hai, dao động quanh 48.000–55.000 vụ/năm.

“Vị thành niên” có xu hướng tăng dần từ 28.000 (2020) lên 38.000 vụ (2023), cho thấy nạn nhân trẻ hóa đang trở nên đáng chú ý.

“Cao tuổi” duy trì mức thấp nhất, chỉ khoảng 18.000–21.000 vụ/năm.

Giai đoạn 2020–2022, hầu hết các nhóm đều tăng nhẹ. Năm 2023, có dấu hiệu chững hoặc giảm nhẹ ở nhóm “Thanh niên” và “Trung niên”, nhưng “Vị thành niên” lại tiếp tục tăng.

15. Tỷ lệ giới tính trong từng nhóm tội phạm

crime %>%
group_by(crime_group, sex) %>%
summarise(`số vụ` = n()) %>%
group_by(crime_group) %>%
mutate(`tỷ lệ (%)` = round(`số vụ` / sum(`số vụ`) * 100, 2))
## `summarise()` has grouped output by 'crime_group'. You can override using the
## `.groups` argument.

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

group_by(crime_group, sex): Gom nhóm dữ liệu theo loại tội phạm và giới tính.

summarise(‘số vụ’ = n()): Đếm số vụ phạm tội trong từng tổ hợp crime_group – sex.

group_by(crime_group): Gom lại theo từng loại tội phạm để tính tỷ lệ nội bộ trong nhóm.

mutate(‘tỷ lệ (%)’ = round(số vụ / sum(số vụ) * 100, 2)): Tính tỷ lệ phần trăm số vụ theo giới tính trong mỗi nhóm tội phạm, làm tròn 2 chữ số.

Ý nghĩa thống kê:

“Bạo lực – hành hung”: nạn nhân nữ chiếm 50,75%, nam 47,64%. Cán cân khá cân bằng, nhưng nữ có khả năng trở thành nạn nhân của hành vi bạo lực cao hơn nhẹ — có thể bao gồm các vụ hành hung gia đình hoặc mâu thuẫn xã hội.

“Gian lận – tài chính”: nạn nhân nam chiếm 47,35%, nữ 33,37%, còn lại không xác định. Nam giới có xu hướng bị lừa đảo hoặc gian lận nhiều hơn, có thể do tham gia giao dịch tài chính, đầu tư, hoặc kinh doanh cao hơn.

“Giết người”: nạn nhân nam chiếm 86,5%, nữ 13,3%. Phản ánh thực tế rằng nam giới thường là mục tiêu trong các vụ bạo lực nghiêm trọng, đặc biệt là trong xung đột, băng nhóm, hoặc tranh chấp xã hội.

“Trộm cắp – cướp giật”: nam 49,8%, nữ 37,1%, còn lại 13% không xác định. Cả hai giới đều có thể là nạn nhân, tuy nhiên nam giới chiếm tỷ lệ cao hơn, do thường mang tài sản có giá trị (xe, điện thoại, ví).

“Tình dục – quấy rối”: nạn nhân nữ chiếm 74,03%, nam chỉ 20,8%. Đây là đặc trưng nổi bật: phụ nữ là nhóm nạn nhân chính trong tội phạm tình dục — phản ánh đúng xu hướng chung toàn cầu.

“Phóng hỏa” và “ma túy”: tỷ lệ giới tính khá cân bằng hoặc dao động mạnh.

16. Top 5 nhóm tội phạm có tỷ lệ nạn nhân nữ cao nhất

crime %>%
group_by(crime_group, sex) %>%
summarise(`tổng số vụ` = n()) %>%
mutate(`tỷ lệ (%)` = round(`tổng số vụ` / sum(`tổng số vụ`) * 100, 2)) %>%
filter(sex == "Nữ") %>%
arrange(desc(`tỷ lệ (%)`)) %>%
head(5)
## `summarise()` has grouped output by 'crime_group'. You can override using the
## `.groups` argument.

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

group_by(crime_group, sex): Gom nhóm theo loại tội phạm và giới tính nạn nhân.

summarise(‘tổng số vụ’ = n()): Đếm tổng số vụ (nạn nhân) trong từng nhóm.

mutate(‘tỷ lệ (%)’ = round(…)): Tính tỷ lệ nạn nhân nữ trong từng loại tội phạm, làm tròn đến 2 chữ số.

filter(sex == “Nữ”): Chỉ lấy những hàng có giới tính là nữ (tức là lọc riêng nạn nhân nữ).

arrange(desc(‘tỷ lệ (%)’)): Sắp xếp các loại tội phạm theo tỷ lệ nữ giảm dần.

head(5): Lấy 5 nhóm tội phạm có tỷ lệ nạn nhân nữ cao nhất.

Ý nghĩa thống kê:

Kết quả cho thấy nhóm “tình dục – quấy rối” có tỷ lệ nạn nhân nữ cao nhất (74,03%), thể hiện rõ đặc trưng giới tính trong các vụ việc liên quan đến xâm hại và quấy rối tình dục, khi phụ nữ thường là đối tượng chịu thiệt thòi nhiều hơn.

Tiếp theo là nhóm “bạo lực – hành hung” với tỷ lệ 50,75%, cho thấy tình trạng bạo lực đối với phụ nữ vẫn còn phổ biến trong xã hội.

Các nhóm “khác”, “trộm cắp – cướp giật” và “ma túy” có tỷ lệ nạn nhân nữ dao động từ 36–41%, phản ánh rằng dù không mang tính đặc thù giới mạnh, nhưng phụ nữ vẫn chiếm một phần đáng kể trong các vụ phạm tội.

Nhìn chung, kết quả cho thấy yếu tố giới tính có ảnh hưởng rõ rệt đến một số loại tội phạm, đặc biệt là các hành vi mang tính xâm hại thân thể và danh dự.

17. Tỷ lệ vụ án theo tình trạng mỗi năm

crime %>%
  group_by(year, arrested) %>%
  summarise(`tổng số vụ` = n()) %>%
  group_by(year) %>%
  mutate(`tỷ lệ (%)` = round(`tổng số vụ` / sum(`tổng số vụ`) * 100, 2))
## `summarise()` has grouped output by 'year'. You can override using the
## `.groups` argument.

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

group_by(year, arrested): gom nhóm dữ liệu theo năm và tình trạng vụ án.

summarise(tổng số vụ = n()): đếm tổng số vụ trong từng nhóm.

group_by(year) và mutate(tỷ lệ (%) = round(tổng số vụ / sum(tổng số vụ) * 100, 2)): dùng để tính tỷ lệ phần trăm của từng loại tình trạng trong tổng số vụ mỗi năm, làm tròn 2 chữ số thập phân.

Ý nghĩa thống kê:

Qua bảng thống kê, dễ thấy tỷ lệ vụ án chưa bắt giữ luôn chiếm ưu thế rất cao, dao động từ 88,5% đến hơn 90% trong giai đoạn 2020–2023. Ngược lại, số vụ đã bắt giữ chỉ chiếm khoảng 9–11% và xu hướng này còn giảm dần qua các năm. Điều này cho thấy công tác điều tra, truy bắt tội phạm vẫn còn hạn chế hoặc có độ trễ nhất định trong việc xử lý các vụ việc. Mặc dù tổng số vụ tăng theo từng năm, nhưng tỷ lệ bắt giữ lại giảm, phản ánh nhu cầu cần cải thiện hiệu quả điều tra và phối hợp giữa các cơ quan thực thi pháp luật.

18. Số vụ theo loại hiện trường

crime %>%
  group_by(premise_type) %>%
  summarise(`tổng số vụ` = n()) %>%
  arrange(desc(`tổng số vụ`))

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

group_by(premise_type) để gom nhóm theo loại hiện trường nơi xảy ra vụ án.

summarise(tổng số vụ = n()) dùng để đếm số lượng vụ án trong từng loại hiện trường.

arrange(desc(tổng số vụ)) sắp xếp kết quả theo thứ tự giảm dần.

Ý nghĩa thống kê:

Các vụ án xảy ra nhiều nhất tại nhà riêng (Single Family Dwelling) với 148.602 vụ, chiếm tỷ trọng lớn nhất, cho thấy khu dân cư là nơi có nguy cơ tội phạm cao. Tiếp theo là đường phố (Street) với 132.855 vụ, phản ánh tình trạng tội phạm công cộng, đặc biệt là trộm cắp và cướp giật. Các khu chung cư, bãi đỗ xe, cửa hàng kinh doanh cũng ghi nhận số vụ đáng kể, chứng tỏ phạm vi tội phạm khá đa dạng và không chỉ giới hạn trong khu dân cư.

Từ kết quả này có thể thấy tội phạm thường tập trung ở những nơi có mật độ người qua lại cao hoặc ít được giám sát, như khu phố, bãi xe và khu thương mại – gợi ý rằng việc tăng cường an ninh tại các khu vực này sẽ có tác động đáng kể đến việc giảm số vụ phạm pháp.

19. Tỷ lệ giải quyết vụ án theo loại hiện trường

crime %>%
  group_by(premise_type, arrested) %>%
  summarise(`tổng số vụ` = n()) %>%
  group_by(premise_type) %>%
  mutate(`tỷ lệ (%)` = round(`tổng số vụ` / sum(`tổng số vụ`) * 100, 2)) %>%
  arrange(premise_type, desc(`tỷ lệ (%)`))
## `summarise()` has grouped output by 'premise_type'. You can override using the
## `.groups` argument.

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

group_by(premise_type, arrested): nhóm dữ liệu theo loại hiện trường và tình trạng vụ án.

summarise(tổng số vụ = n()): đếm số vụ theo từng nhóm.

group_by(premise_type) kết hợp với mutate(tỷ lệ (%) = round(tổng số vụ / sum(tổng số vụ) * 100, 2)) tính tỷ lệ phần trăm vụ đã và chưa bắt giữ trong mỗi loại hiện trường.

arrange(premise_type, desc(tỷ lệ (%))) sắp xếp giúp dễ so sánh trong từng nhóm hiện trường.

Ý nghĩa thống kê:

Kết quả cho thấy hầu hết các loại hiện trường đều có tỷ lệ chưa bắt giữ rất cao, thường trên 90%, điển hình như ATM (98,69%), ban công (98,33%), website (98,34%) và ngân hàng (96,10%). Điều này phản ánh rằng những địa điểm có tính riêng tư, giám sát hạn chế hoặc tội phạm công nghệ cao thường khó điều tra và truy bắt thủ phạm.

Ngược lại, các hiện trường mang tính mở, có camera hoặc nhân chứng như khu vực kinh doanh, bãi xe hoặc nhà xưởng, có tỷ lệ bắt giữ cao hơn (khoảng 8–20%). Nhìn chung, sự chênh lệch tỷ lệ bắt giữ giữa các loại hiện trường cho thấy hiệu quả điều tra phụ thuộc đáng kể vào điều kiện vật lý và khả năng giám sát của từng khu vực, đồng thời gợi ý rằng việc tăng cường camera an ninh và kiểm soát tại các điểm nhạy cảm có thể cải thiện khả năng xử lý tội phạm.

20. Mức độ vụ án ở các khu vực

crime %>%
  group_by(area, severity_label) %>%
  summarise(`số vụ` = n(), .groups = "drop") %>%
  arrange(area, desc(`số vụ`))

Giải thích kỹ thuật: Đoạn mã dùng các hàm trong dplyr như sau:

group_by(area, severity_label) để gom nhóm dữ liệu theo khu vực và mức độ nghiêm trọng của vụ án.

summarise(số vụ = n(), .groups = “drop”) để đếm tổng số vụ trong từng nhóm mức độ và khu vực.

arrange(area, desc(số vụ)) sắp xếp dữ liệu theo khu vực và giảm dần số vụ, giúp dễ so sánh mức độ vụ án giữa các khu vực.

Kết quả cho phép nhìn nhận tình hình tội phạm theo mức độ nghiêm trọng ở từng khu vực cụ thể.

Ý nghĩa thống kê:

Kết quả cho thấy tỷ lệ vụ án nghiêm trọng và nhẹ khá cân bằng ở nhiều khu vực, nhưng số vụ vẫn dao động đáng kể giữa các khu vực. Ví dụ, khu vực Central có tổng số vụ nghiêm trọng cao nhất (32.531 vụ), trong khi các khu vực như Foothill hay Hollenbeck có số vụ nhẹ cao hơn nhưng tổng số vụ thấp hơn. Một số khu vực như Southwest và Pacific ghi nhận số vụ nghiêm trọng gần tương đương số vụ nhẹ, cho thấy mức độ nguy cơ tương đối đồng đều.

Nhìn chung, khu vực trung tâm và các khu vực đông dân cư có xu hướng tỷ lệ vụ nghiêm trọng cao hơn, trong khi các khu ngoại ô hoặc ít dân cư hơn thường có số vụ nhẹ chiếm ưu thế. Điều này phản ánh rằng mật độ dân cư, mức độ giám sát và các yếu tố xã hội – kinh tế ảnh hưởng trực tiếp đến mức độ nghiêm trọng của các vụ án.

21. Tỷ lệ vụ án nghiêm trọng theo hiện trường

crime %>%
  group_by(premise_type) %>%
  summarise(
    nghiêm_trọng = sum(severity_label == "Nghiêm trọng"),
    tổng = n()
  ) %>%
  mutate(`Tỷ lệ nghiêm trọng (%)` = round(nghiêm_trọng / tổng * 100, 2)) %>%
  arrange(desc(`Tỷ lệ nghiêm trọng (%)`))

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

group_by(premise_type): gom nhóm theo loại hiện trường.

summarise(nghiêm_trọng = sum(severity_label == “Nghiêm trọng”): tổng = n()) tính số vụ nghiêm trọng và tổng số vụ tại mỗi loại hiện trường.

mutate(Tỷ lệ nghiêm trọng (%) = round(nghiêm_trọng / tổng * 100, 2)): tính tỷ lệ vụ nghiêm trọng trên tổng số vụ tại từng loại hiện trường, làm tròn 2 chữ số thập phân.

arrange(desc(Tỷ lệ nghiêm trọng (%))): sắp xếp giảm dần.

Ý nghĩa thống kê:

Kết quả cho thấy một số hiện trường đặc thù như cơ sở quân sự (Dept of Defense Facility), các tuyến MTA, cửa hàng sinh tồn (Surplus Survival Store) đều có 100% vụ án được xếp loại nghiêm trọng, thường là do số vụ rất nhỏ nhưng tính chất nghiêm trọng cao. Các hiện trường thương mại và dịch vụ công cộng như Public Storage, Department Store, Clothing Store, Garage/Carport cũng có tỷ lệ vụ nghiêm trọng cao (>80%), phản ánh rủi ro đáng kể tại các khu vực đông người hoặc tài sản giá trị cao.

Ngược lại, các hiện trường ngân hàng, ATM, website, cyberspace có tỷ lệ nghiêm trọng thấp (<10%), mặc dù tổng số vụ lớn, cho thấy mức độ nghiêm trọng trung bình thấp hoặc loại tội phạm chủ yếu mang tính trộm cắp, gian lận, không gây thiệt hại vật chất nghiêm trọng.

Nhìn chung, tỷ lệ vụ nghiêm trọng phụ thuộc cả vào loại hiện trường và tính chất tội phạm đặc thù, gợi ý rằng chiến lược phòng ngừa và giám sát cần điều chỉnh theo từng loại địa điểm để tối ưu hiệu quả.

CHƯƠNG 5: TRỰC QUAN HÓA DỮ LIỆU

1. Top 10 khu vực có số vụ phạm tội cao

library(dplyr)
library(ggplot2)
crime %>%
group_by(area) %>%
summarise(total_cases = n()) %>%
arrange(desc(total_cases)) %>%
slice_head(n=10) %>%
ggplot(aes(x=reorder(area, -total_cases), y=total_cases)) +
geom_col(fill='#5A9F68') +
geom_text(aes(label=total_cases), vjust=-0.5) +
labs(x='Khu vực', y='Số vụ', title ='Top 10 khu vực có số vụ nhiều nhất') +
theme(plot.title = element_text(hjust = 0.5))

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

library(dplyr) / library(ggplot2): Nạp hai package dplyr (xử lý dữ liệu) và ggplot2 (vẽ biểu đồ).

crime %>%: Bắt đầu một chuỗi thao tác (pipe) — truyền crime vào các hàm tiếp theo theo thứ tự.

group_by(area): Gom các bản ghi theo biến area (mỗi nhóm tương ứng một khu vực).

summarise(total_cases = n()): Tạo một bảng tổng hợp mới với một cột total_cases là kết quả đếm (n()) số bản ghi trong mỗi nhóm area. Kết quả là dữ liệu dạng (area, total_cases).

arrange(desc(total_cases)): Sắp xếp bảng tổng hợp giảm dần theo total_cases — các khu vực có số vụ lớn nhất lên trước.

slice_head(n=10): Lấy 10 hàng đầu tiên theo nhóm arrange.

ggplot(aes(x = reorder(area, -total_cases), y = total_cases)): Khởi tạo đồ họa: ánh xạ x là area, y là total_cases.

reorder(area, -total_cases) sắp xếp các nhãn area theo thứ tự giảm dần của total_cases khi vẽ (dấu - là giảm dần). Việc này đảm bảo cột cao nhất nằm bên trái khi biểu đồ đứng.

geom_col(fill=’ ’): Vẽ cột (bar chart) với chiều cao theo y và tô màu đỏ cho các cột.

geom_text(aes(label = total_cases), vjust = -0.5): Thêm nhãn số lên trên mỗi cột, hiển thị giá trị total_cases. vjust = -0.5 dịch nhãn cao hơn một chút so với đỉnh cột để không chồng lên cột.

labs(x=‘Khu vực’, y=‘Số vụ’, title =‘Top 10 khu vực có số vụ nhiều nhất’): Thiết lập nhãn trục và tiêu đề biểu đồ.

theme(plot.title = element_text(hjust = 0.5)): Căn giữa tiêu đề bằng thuộc tính hjust = 0.5.

Ý nghĩa thống kê:

Tổng số vụ ở Top 10: 417.784 vụ.

Central chiếm 54.856 vụ (≈13.13%) cao một cách rõ rệt so với khu vực đứng sau.

Các vị trí tiếp theo bao gồm 77th Street 46.669 vụ (≈11.17%), Pacific 43.834 vụ (≈10.49%), Southwest: 42.286 vụ (≈10.12%), Hollywood 42.089 vụ (≈10.07%)

Nhóm giữa (N Hollywood, Olympic, Wilshire, Southeast, West LA) mỗi khu vực chiếm khoảng 8.7%–9.2%.

Nhìn chung, 5 khu vực hàng đầu (Central → Hollywood) chiếm ~55% của tổng Top 10 (cộng lại khoảng 55% của 417k) — tức là số vụ tập trung nhiều vào các khu vực trung tâm thượng mại, giải trí, nơi có mật độ dân số cao.

2. Thống kê mức độ phạm tội theo nhóm tuổi

crime %>% 
  group_by(age_group, severity_label) %>% 
  summarise(total_cases = n(), .groups = "drop") %>% 
  ggplot(aes(x = age_group, y = total_cases, fill = severity_label)) +
  geom_col(position = "dodge", width = 0.8) +
  geom_text(aes(label = total_cases), 
            position = position_dodge(width = 0.8), 
            vjust = -0.5, size = 3, color = "black") +
  scale_fill_manual(values = c("Nghiêm trọng" = "#ff8fab","Nhẹ" = "#6a994e")) +        
  labs(x = "Nhóm tuổi", 
       y = "Số vụ", 
       fill = "Mức độ phạm tội",
       title = "Thống kê mức độ phạm tội theo nhóm tuổi") +
  theme_minimal(base_size = 13) +
  theme(plot.title = element_text(hjust = 0.5, face = "bold", color = "#3b3b3b"),
        legend.position = "right",
        legend.title = element_text(face = "bold"))

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

group_by(age_group, severity_label): gom dữ liệu theo 2 biến nhóm tuổi và nhãn mức độ phạm tội. Kết quả tóm tắt sẽ tính trên từng tổ hợp (age_group, severity_label).

summarise(total_cases = n(), .groups = “drop”): tạo cột total_cases là số quan sát trong mỗi tổ hợp; .groups = “drop” loại bỏ grouping metadata sau khi summarise (tránh cảnh báo của dplyr mới).

aes(…) định nghĩa ánh xạ: trục x là age_group, trục y là total_cases, màu (fill) theo severity_label.geom_col() vẽ cột, sử dụng giá trị y trực tiếp từ total_cases.

position = “dodge”: đặt các cột của cùng một age_group nhưng khác severity_label sát cạnh nhau (không xếp chồng). width = 0.8: bề rộng cột. vjust = -0.5: dịch nhãn lên phía trên đỉnh cột một chút (âm → lên trên). size = 3: kích thước chữ. color = “black”: màu chữ.

base_size = 13: kích thước font cơ bản.

plot.title = element_text(hjust = 0.5, …): căn giữa tiêu đề, bôi đậm, đổi màu.

legend.position = “right”: đưa legend (chú giải) ra bên phải biểu đồ.

legend.title = element_text(face = “bold”): in đậm tiêu đề legend

Ý nghĩa thống kê:

Nhóm “Thanh niên” có số vụ phạm tội cao nhất ở cả hai mức độ: nghiêm trọng (~184.612 vụ) và nhẹ (~162.413 vụ), cho thấy đây là độ tuổi tham gia và bị ảnh hưởng nhiều nhất trong các vụ án. Tiếp đến là “Trung niên”, với hơn 100.000 vụ ở mỗi mức độ – phản ánh tỷ lệ phạm tội vẫn đáng kể ở nhóm tuổi này, có thể do áp lực xã hội, kinh tế hoặc nghề nghiệp.

Trong khi đó, hai nhóm “Vị thành niên” và “Cao tuổi” có số vụ thấp hơn rõ rệt, chứng tỏ hành vi phạm tội hoặc khả năng trở thành nạn nhân giảm dần ở hai đầu độ tuổi.

Đáng chú ý, ở hầu hết các nhóm tuổi, số vụ nghiêm trọng thường cao hơn hoặc tương đương số vụ nhẹ, cho thấy tội phạm tại khu vực có xu hướng nghiêm trọng hóa hành vi phạm pháp.

Từ góc độ thống kê, biểu đồ phản ánh mối quan hệ rõ ràng giữa độ tuổi và mức độ nghiêm trọng của tội phạm, giúp xác định nhóm tuổi trọng điểm cần được can thiệp. Đặc biệt, “Thanh niên” là nhóm rủi ro cao nhất, cần được chú trọng trong các chương trình giáo dục pháp luật, định hướng nghề nghiệp, quản lý xã hội và hỗ trợ tâm lý, nhằm giảm thiểu khả năng phạm tội và tái phạm.

3. Số vụ theo nhóm tuổi qua các năm

crime %>%
  group_by(year, age_group) %>%
  summarise(total_cases = n()) %>%
  ggplot(aes(x=age_group, y=total_cases, fill=age_group)) +
  geom_col() +
  facet_wrap(~year) +
  labs(x='Nhóm tuổi', y='Số vụ', title='Số vụ theo nhóm tuổi qua các năm')+
  theme(axis.text.x = element_blank())+ 
  theme(plot.title = element_text(hjust = 0.5))
## `summarise()` has grouped output by 'year'. You can override using the
## `.groups` argument.

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

group_by(year, age_group): gom theo năm và nhóm tuổi để tính tổng số vụ cho mỗi tổ hợp.

summarise(total_cases = n(), .groups = “drop”): đếm số vụ và bỏ thông tin nhóm để tránh cảnh báo.

ggplot(aes(x = age_group, y = total_cases, fill = age_group)): ánh xạ trục x là nhóm tuổi, trục y là tổng số vụ; fill để tô màu phân biệt nhóm tuổi.

geom_col(): vẽ cột từ dữ liệu đã tóm tắt.

facet_wrap(~year): tách biểu đồ theo từng năm, thuận tiện so sánh theo thời gian.

theme(axis.text.x = element_blank()): ẩn nhãn trục x.

labs(…) và theme(plot.title = element_text(hjust = 0.5)): đặt nhãn trục/tiêu đề và căn giữa tiêu đề.

Ý nghĩa thống kê:

Nhìn chung, tổng số vụ thay đổi nhẹ theo từng năm, nhưng cơ cấu theo nhóm tuổi hầu như không thay đổi. Ở tất cả các năm, nhóm thanh niên luôn chiếm tỷ lệ nạn nhân cao nhất, dao động từ khoảng 90.000 đến hơn 100.000 vụ mỗi năm.

Nhóm trung niên đứng thứ hai với khoảng 50.000–60.000 vụ mỗi năm, cho thấy sự ổn định và ít biến động. Nhóm vị thành niên dao động quanh mức 30.000 vụ, thể hiện rủi ro đáng kể đối với trẻ em và thiếu niên, dù thấp hơn thanh niên nhưng vẫn cần được quan tâm đặc biệt.

Nhóm cao tuổi luôn có số vụ thấp nhất và gần như không thay đổi nhiều qua các năm (~20.000 vụ), điều này phản ánh việc người cao tuổi ít ra ngoài hoặc có lối sống ít rủi ro hơn, song vẫn là nhóm dễ bị tổn thương trong các tội phạm như lừa đảo hoặc bạo hành gia đình.

4. Cấu trúc giới tính trong từng nhóm tội phạm

crime %>%
  group_by(crime_group, sex) %>%
  summarise(total_cases = n(), .groups = "drop") %>%
  group_by(crime_group) %>%
  mutate(pct = total_cases / sum(total_cases) * 100) %>%
  ggplot(aes(x = reorder(crime_group, -pct), y = pct, fill = sex)) +
  geom_bar(stat = "identity", position = "stack", width = 0.7) +
  geom_text(aes(label = paste0(round(pct, 1), "%")),
            position = position_stack(vjust = 0.5), size = 3, color = "black") +
  scale_fill_brewer(palette = "Set4") +
  labs(
    title = "Cơ cấu giới tính trong từng nhóm tội phạm",
    x = "Nhóm tội phạm", y = "Tỷ lệ (%)", fill = "Giới tính"
  ) +
  theme_minimal(base_size = 13) +
  theme(
    plot.title = element_text(hjust = 0.5, face = "bold"),
    axis.text.x = element_text(angle = 45, hjust = 1)
  )
## Warning: Unknown palette: "Set4"

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

group_by(crime_group, sex): Gom dữ liệu theo 2 biến phân loại nhóm tội phạm và giới tính.

summarise(total_cases = n(), .groups = “drop”): Tạo bảng tóm tắt total_cases là số quan sát trong mỗi tổ hợp. .groups = “drop” loại bỏ metadata về grouping sau khi summarise, tránh cảnh báo và hành vi không mong muốn.

group_by(crime_group): Gom lại theo crime_group để tính tỷ lệ nội bộ trong từng nhóm tội phạm.

mutate(pct = total_cases / sum(total_cases) * 100): Tạo cột pct là tỷ lệ phần trăm của total_cases trong mỗi crime_group. sum(total_cases) tính tổng cho mỗi nhóm tội phạm, nên pct là tỷ lệ nội bộ (cộng lại = 100% cho mỗi crime_group).

ggplot(aes(x = reorder(crime_group, -pct), y = pct, fill = sex)): aes(…) ánh xạ thẩm mỹ trục x là crime_group (đã được reorder theo -pct), trục y là pct, màu cột theo sex.

geom_bar(stat = “identity”, position = “stack”, width = 0.7): stat = “identity”: dùng giá trị y (pct) có sẵn (không đếm lại). position = “stack”: các thanh theo sex được xếp chồng để cột cuối cùng thể hiện tổng 100% (tuy ở đây y đã là tỷ lệ, nên tổng ~100%). width = 0.7: điều chỉnh bề rộng cột.

geom_text(aes(label = paste0(round(pct, 1), “%”)), position = position_stack(vjust = 0.5), size = 3, color = “black”). Thêm nhãn phần trăm đặt ở giữa mỗi segment nhờ position_stack(vjust = 0.5). round(pct, 1) làm tròn 1 chữ số thập phân; paste0(…, “%”) thêm ký tự phần trăm.

scale_fill_brewer(palette = “Set3”)

labs(…): đặt tiêu đề, nhãn trục, tên chú giải.

theme_minimal(base_size = 13) + theme(…): thiết lập giao diện và bố cục; xoay nhãn x bằng angle = 45 để tránh chồng chữ.

Ý nghĩa thống kê:

Biểu đồ cho thấy giới tính có ảnh hưởng rõ rệt đến đặc điểm tội phạm. Trong nhóm tội giết người, nam giới chiếm tỷ lệ áp đảo (≈ 86,5%), cho thấy các hành vi phạm tội nghiêm trọng chủ yếu do nam giới thực hiện. Các nhóm “bạo lực – hành hung” và “trộm cắp – cướp giật” có phân bố tương đối cân bằng hơn, nhưng nam giới vẫn chiếm ưu thế (≈ 47–50%), phản ánh tính đặc trưng của các tội phạm sử dụng vũ lực.

Ngược lại, nhóm “tình dục – quấy rối” có tỷ lệ nữ giới rất cao (≈ 74%), cho thấy nữ giới thường là nạn nhân chính trong loại tội phạm này. “Gian lận – tài chính” có cơ cấu giới tương đối đồng đều (nam ~47%, nữ ~33%, không xác định ~19%), thể hiện đây là loại tội phạm không mang đặc trưng giới tính mạnh, thường liên quan đến hành vi trí tuệ hoặc vị trí nghề nghiệp. Nhóm “ma túy” và “khác” lại ghi nhận tỷ lệ giới tính không xác định khá cao (≈ 15–36%), có thể do thiếu dữ liệu hoặc trường hợp đặc thù chưa được phân loại rõ.

Nhìn chung, nam giới chiếm đa số trong các tội phạm mang tính bạo lực, xung động và thể chất, trong khi nữ giới thường là nạn nhân trong các vụ án liên quan đến tình dục. Điều này cho thấy cần tăng cường các biện pháp bảo vệ, giáo dục giới tính và can thiệp sớm, đồng thời chuẩn hóa dữ liệu về giới tính để phục vụ công tác thống kê và hoạch định chính sách. Việc phân tích cơ cấu giới tính giúp nhận diện nhóm có nguy cơ cao và định hướng chiến lược phòng ngừa – hỗ trợ xã hội hiệu quả hơn.

5. Giới tính nạn nhân ở các khu vực phạm tội cao

top_areas <- crime %>%
  group_by(area) %>%
  summarise(total_cases = n()) %>%
  arrange(desc(total_cases)) %>%
  slice_head(n = 5)
crime_top_areas <- crime %>%
  filter(area %in% top_areas$area, !is.na(area), !is.na(sex))
crime_top_areas %>%
  group_by(area, sex) %>%
  summarise(total = n(), .groups = "drop") %>%
  ggplot(aes(x = sex, y = reorder(area, -total), fill = total)) +
  geom_tile(color = "white") +
  geom_text(aes(label = total), color = "red", size = 3) +
  scale_fill_gradient(low = "#d8f3dc", high = "#1b4332") +
  labs(
    title = "Mức độ phạm tội theo giới tính và khu vực",
    x = "Giới tính", y = "Khu vực", fill = "Số vụ"
  ) +
  theme_minimal(base_size = 13) +
  theme(plot.title = element_text(hjust = 0.5, face = "bold"))

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

group_by(area) %>% summarise(total_cases = n()): Gom nhóm theo khu vực, đếm số vụ.

arrange(desc(total_cases)) %>% slice_head(n = 5): Lấy 5 khu vực có nhiều vụ nhất.

filter(area %in% top_areas$area, !is.na(area), !is.na(sex)): Giữ lại dữ liệu hợp lệ của top 5 khu vực.

group_by(area, sex) %>% summarise(total = n()): Đếm số vụ theo giới tính trong từng khu vực.

ggplot(aes(x = sex, y = reorder(area, -total), fill = total)): Thiết lập trục và thang màu.

geom_tile(): Vẽ các ô thể hiện mật độ vụ án.

geom_text(aes(label = total)): Hiển thị số vụ trên từng ô.

scale_fill_gradient(): Tô màu từ nhạt đến đậm theo số vụ.

labs() + theme_minimal(): Đặt tiêu đề, nhãn trục, giao diện đơn giản.

Ý nghĩa thống kê:

Nhìn chung, nạn nhân nam chiếm tỷ lệ cao hơn đáng kể so với nữ ở hầu hết các khu vực, đặc biệt là tại Central – nơi ghi nhận gần 30.000 vụ với nạn nhân là nam, cao nhất trong 5 khu vực.

Các khu vực như 77th Street và Southwest lại có số vụ nạn nhân nữ tương đối cao, cho thấy mức độ tội phạm nhắm vào phụ nữ vẫn đáng lưu ý.

Số vụ có giới tính không xác định tuy chiếm tỷ lệ nhỏ, nhưng vẫn xuất hiện ở tất cả các khu vực.

6. So sánh số lượng giới tính nạn nhân qua các năm

crime %>%
  group_by(year, sex) %>%
  summarise(total_cases = n(), .groups = "drop") %>%
  ggplot(aes(x = factor(year), y = total_cases, fill = sex)) +
  geom_col(position = "dodge") +  
  geom_text(aes(label = total_cases), 
            position = position_dodge(width = 1), vjust = -0.5, size = 3) +
  labs(
    title = "So sánh số lượng nạn nhân qua các năm",
    x = "Năm",
    y = "Số vụ",
    fill = "Giới tính"
  ) +
  theme_minimal()

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

group_by(year, sex): Gom các bản ghi theo hai biến phân loại: year và sex.

summarise(total_cases = n(), .groups = “drop”): Tạo một bảng tóm tắt mới có cột total_cases là số lượng bản ghi (số vụ) trong mỗi nhóm. .groups = “drop” loại bỏ metadata grouping sau khi summarise (tránh cảnh báo và hành vi giữ group không mong muốn).

ggplot(aes(x = factor(year), y = total_cases, fill = sex)): ánh xạ thẩm mỹ (aesthetic mapping) cho biểu đồ: x = factor(year): biến năm được ép về factor (mỗi năm là 1 cột riêng, giữ thứ tự rời rạc). y = total_cases: chiều cao cột là tổng số vụ. fill = sex: màu tô cột theo giá trị sex (phân biệt giới).

geom_col(position = “dodge”): Vẽ cột dựa trên giá trị y đã được tóm tắt. position = “dodge” đặt các cột của các sex khác nhau trong cùng một year nằm cạnh nhau (không chồng lên).

geom_text(aes(label = total_cases), position = position_dodge(width = 1), vjust = -0.5, size = 3): Thêm nhãn số (số vụ) trên/ngoài đầu mỗi cột. position = position_dodge(width = 1) đảm bảo nhãn căn trùng vị trí với cột tương ứng (dùng cùng kiểu dodge). vjust = -0.5 dịch nhãn lên trên đỉnh cột (tránh chồng lên cột). size = 3 kích thước chữ.

labs(…): Đặt tiêu đề và nhãn cho trục, cũng như tên chú giải (fill = “Giới tính”).

theme_minimal(): Chọn theme tối giản cho biểu đồ (gọn, phù hợp báo cáo). Bạn có thể thêm theme(plot.title = element_text(hjust = 0.5)) để căn giữa tiêu đề.

Ý nghĩa thống kê:

Nam giới luôn là nhóm nạn nhân chiếm tỷ lệ cao nhất mỗi năm, dao động từ khoảng 84.000 đến 95.000 vụ.

Nữ giới cũng chiếm tỷ trọng lớn, chỉ thấp hơn nam một chút, đặc biệt trong năm 2022 khi số vụ gần tương đương.

Giới tính không xác định có số vụ thấp hơn nhiều, nhưng lại tăng đều qua các năm, từ khoảng 16.000 vụ (2020) lên gần 26.000 vụ (2023).

Tổng thể, số vụ phạm tội nhắm vào cá nhân đều tăng nhẹ theo thời gian, thể hiện xu hướng cần chú ý về an ninh cộng đồng và bảo vệ cá nhân trong giai đoạn gần đây.

7. Mật độ tội phạm theo nơi xảy ra vụ án (premise_type)

library(dplyr)
library(ggplot2)
top_n <- 5
top_premise <- crime %>%
  group_by(premise_type) %>%
  summarise(total_cases = n(), .groups = "drop") %>%
  arrange(desc(total_cases)) %>%
  slice_head(n = top_n)
ggplot(top_premise, aes(x = reorder(premise_type, -total_cases), y = total_cases/sum(total_cases))) +
  geom_col(fill = "orange") +
  geom_text(aes(label = paste0(round(total_cases/sum(total_cases)*100,1), "%")), vjust = 2, size = 3) +
  labs(title = paste("Mật độ vụ án theo loại địa điểm (Top", top_n, ")"),x = "Loại địa điểm", y = "Tỷ lệ") +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1),
        plot.title = element_text(face = "bold", hjust = 0.5))

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

top_n <- 5: Biến điều khiển số lượng loại hiện trường muốn hiển thị (ở đây là Top 5)

group_by(premise_type): gom theo loại hiện trường.

summarise(total_cases = n(), .groups = “drop”): đếm số vụ cho mỗi loại, .groups=“drop” tránh giữ grouping metadata.

arrange(desc(total_cases)): sắp xếp giảm dần theo số vụ.

slice_head(n = top_n): lấy top_n loại hiện trường có số vụ nhiều nhấtreorder(premise_type, -total_cases): sắp xếp trục x theo total_cases giảm dần.

y = total_cases/sum(total_cases): hiển thị tỷ lệ (phần của tổng Top N).

geom_col(): vẽ cột từ giá trị y sẵn có.

geom_text(…): thêm nhãn phần trăm trên cột, dùng round(…,1) để làm tròn 1 chữ số thập phân; vjust = 2 kéo nhãn vào trong ô cột (nếu muốn ra ngoài dùng vjust = -0.5).

labs(…), theme_minimal() và theme(…): đặt tiêu đề, nhãn và tinh chỉnh giao diện.

Ý nghĩa thống kê:

Kết quả cho thấy sự tập trung đáng kể của các vụ việc tại các khu vực dân cư và công cộng.

SINGLE FAMILY DWELLING (Nhà ở gia đình đơn lẻ) là địa điểm xảy ra vụ án phổ biến nhất, chiếm 31.3% tổng số vụ án trong Top 5. STREET (Đường phố) là loại địa điểm phổ biến thứ hai, chiếm 28% các vụ án. Tỷ lệ cao này nhấn mạnh tầm quan trọng của việc kiểm soát và duy trì an ninh tại các khu vực công cộng mở, nơi các loại tội phạm đường phố hoặc xung đột có xu hướng xảy ra.

MULTI-UNIT DWELLING (Nhà ở đa đơn vị / Chung cư) đứng thứ ba với 22.7%. PARKING LOT (Bãi đậu xe) và OTHER BUSINESS (Doanh nghiệp khác) có tỷ lệ thấp hơn, lần lượt 9.7% và 8.3%. Điều này cho thấy các khu vực thương mại và tiện ích công cộng như bãi đậu xe cũng cần được chú trọng về giám sát và phòng ngừa trộm cắp, phá hoại.

8. Tỷ lệ vụ án đã giải quyết xong

library(dplyr)
library(ggplot2)
crime %>%
  group_by(area) %>%
  summarise(
    total_cases = n(),
    arrested_cases = sum(arrested == "đã bắt giữ"),
    arrest_rate = arrested_cases / total_cases * 100
  ) %>%
  ggplot(aes(x = reorder(area, arrest_rate), y = arrest_rate)) +
  geom_col(fill = "tomato") +
  geom_text(aes(label = paste0(round(arrest_rate,1), "%")),
            position = position_stack(vjust = 0),  
            hjust = -0.1, 
            size = 3) +
  coord_flip() +
  labs(title="Tỷ lệ vụ án đã giải quyết theo khu vực", x="Khu vực", y="Tỷ lệ (%)") +
  theme_minimal()

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

group_by(area): gom nhóm dữ liệu theo khu vực phạm tội.

summarise(…): tạo bảng tóm tắt cho từng khu vực gồm:

total_cases = n(): tổng số vụ án trong khu vực đó.

arrested_cases = sum(arrested == “đã bắt giữ”): đếm số vụ đã bắt giữ (điều kiện logic trả về TRUE/FALSE → TRUE = 1).

arrest_rate = arrested_cases / total_cases * 100: tính tỷ lệ phần trăm vụ án đã được giải quyết.reorder(area, arrest_rate): sắp xếp các khu vực theo tỷ lệ vụ án giải quyết tăng dần, giúp biểu đồ dễ đọc hơn.

geom_text(): thêm nhãn số liệu vào cột.

label = paste0(round(arrest_rate,1), “%”): làm tròn 1 chữ số thập phân, hiển thị kèm dấu %.

position_stack(vjust = 0): canh vị trí nhãn ở đỉnh cột.

hjust = -0.1: đẩy nhãn ra ngoài một chút để dễ đọc.

coord_flip(): Lật biểu đồ nằm ngang, giúp dễ đọc tên khu vực khi danh sách dài.labs(): đặt tiêu đề và nhãn trục.

theme_minimal(): giao diện tối giản, giúp biểu đồ sạch và hiện đại.

Ý nghĩa thống kê:

Tỷ lệ vụ án được giải quyết có sự khác biệt rõ rệt giữa các khu vực.

Harbor (14.7%), Mission (14.4%) và Foothill (13.3%) đạt tỷ lệ cao nhất, cho thấy công tác điều tra và xử lý tội phạm tại đây tương đối hiệu quả.

Ngược lại, Central (7.0%), West LA (7.1%) và Pacific (7.5%) có tỷ lệ thấp hơn, phản ánh những khó khăn do mật độ dân cư cao và tính chất tội phạm phức tạp. Sự chênh lệch gần gấp đôi giữa khu vực cao nhất và thấp nhất cho thấy hiệu quả điều tra chưa đồng đều.

PHẦN 2: BỘ DỮ LIỆU VNM

CHƯƠNG 1: MỞ ĐẦU

1. Lý do chọn đề tài

2. Mục tiêu nghiên cứu

3. Đối tượng và phạm vi nghiên cứu

4. Phương pháp nghiên cứu

5. Kết cấu của đề tài

CHƯƠNG 2: GIỚI THIỆU BỘ DỮ LIỆU VNM

Mục tiêu:

  • Làm quen với bộ dữ liệu bảng cân đối kế toán của công ty Vinamilk (VNM).

  • Kiểm tra kích thước, cấu trúc, kiểu dữ liệu, dữ liệu thiếu và trùng.

1. Nạp thư viện cần thiết

library(readxl)    # đọc dữ liệu Excel
library(dplyr)     # xử lý dữ liệu
library(tidyr)     # làm việc với bảng dữ liệu
library(stringr)   # thao tác chuỗi ký tự
library(lubridate) # xử lý thời gian
## 
## Attaching package: 'lubridate'
## The following objects are masked from 'package:base':
## 
##     date, intersect, setdiff, union

Giải thích: Các thư viện này hỗ trợ đọc file, làm sạch và phân tích dữ liệu.

2. Đọc dữ liệu từ file Excel

cdkt <- read_excel("C:/Users/USER/Downloads/VNMcdkt.xlsx")

Giải thích: Tải dữ liệu bảng cân đối kế toán của Vinamilk và lưu vào biến cdkt.

3. Kiểm tra kích thước dữ liệu

dim(cdkt)
## [1] 85 13

Giải thích:

Kỹ thuật

dim() trả về số hàng và số cột của bảng.

Ý nghĩa

Dữ liệu có 85 dòng (quan sát / chỉ tiêu) và 13 cột (3 cột đầu là mô tả, còn 10 cột sau là các cột giá trị theo thời điểm).

Đây cho thấy dữ liệu không quá lớn — tiện để xử lý thủ công nếu cần.

4. Xem trước 6 dòng đầu tiên

head(cdkt)

Giải thích: Cho phép quan sát nhanh các biến trong bảng và kiểu dữ liệu sơ bộ.

5. Xem 6 dòng cuối cùng

tail(cdkt)

Giải thích: Dùng để kiểm tra xem dữ liệu có bị thiếu hoặc lệch định dạng ở cuối file không.

Kỹ thuật

Hiển thị 6 dòng đầu / 6 dòng cuối.

Ý nghĩa

Dùng để kiểm tra cấu trúc phần đầu-cuối (nhận biết header phụ, dòng tiêu đề lồng, ghi chú ở cuối file…).

6. Liệt kê tên các biến

names(cdkt)
##  [1] "CHỈ TIÊU"    "Mã số"       "Thuyết minh" "45657"       "45291"      
##  [6] "44926"       "44561"       "44196"       "43830"       "43465"      
## [11] "43100"       "42735"       "42369"

Giải thích:

Kỹ thuật

names() liệt kê header cột. Ở đây các tên cột thứ 4–13 là số: “45657”, “45291”…

Ý nghĩa

Những số như 45657, 45291, … rất giống Excel serial date (số ngày kể từ 1899-12-30) — nghĩa là sheet ban đầu có header là ngày (ví dụ 31/12/2024) nhưng khi read_excel() đọc, header đó được chuyển thành giá trị số (serial) vì file gốc có dạng ngày nhưng bị coi là numeric.

Hậu quả: Cần chuyển tên cột này về dạng ngày hoặc năm để hiểu dữ liệu theo thời điểm (ví dụ Y2024).

7. Kiểm tra kiểu dữ liệu

str(cdkt)
## tibble [85 × 13] (S3: tbl_df/tbl/data.frame)
##  $ CHỈ TIÊU   : chr [1:85] "A - TÀI SẢN" "Tài sản ngắn hạn (100 = 110+120+130+140+150)" "Tiền và các khoản tương đương tiền" "Tiền" ...
##  $ Mã số      : chr [1:85] " " "100" "110" "111" ...
##  $ Thuyết minh: chr [1:85] NA NA "V.1" NA ...
##  $ 45657      : num [1:85] NA 3.76e+13 2.23e+12 1.88e+12 3.48e+11 ...
##  $ 45291      : num [1:85] NA 3.59e+13 2.91e+12 1.03e+12 1.89e+12 ...
##  $ 44926      : num [1:85] NA 3.16e+13 2.30e+12 1.33e+12 9.73e+11 ...
##  $ 44561      : chr [1:85] NA "36109910649785" "2348551874348" "1187350251579" ...
##  $ 44196      : chr [1:85] NA "29665725805058" "2111242815581" "863853260384" ...
##  $ 43830      : chr [1:85] NA "24721565376552" "2665194638452" "2378583764655" ...
##  $ 43465      : chr [1:85] NA "20559756794837" "1522610167671" "1072610167671" ...
##  $ 43100      : chr [1:85] NA "20307434789529" "963335914164" "834435914164" ...
##  $ 42735      : num [1:85] NA 1.87e+13 6.55e+11 6.00e+11 5.55e+10 ...
##  $ 42369      : chr [1:85] NA "16731875433624" "1358682600684" "1212517600684" ...

Giải thích: Hiển thị kiểu dữ liệu của từng biến (số, chuỗi, ngày tháng…), giúp xác định bước tiền xử lý sau.

Kỹ thuật

str() cho biết kiểu dữ liệu từng cột: chr (character), num (numeric).

Lưu ý: một số cột tương tự (ví dụ cột với cùng ý nghĩa thời điểm) có kiểu khác nhau: một vài cột là num, một vài cột là chr. Điều này xảy ra khi trong cùng một cột Excel có giá trị dạng văn bản (ví dụ chứa dấu cách, dấu phẩy, hoặc định dạng chuỗi) khiến read_excel() chọn chr thay vì num.

Ý nghĩa

Không đồng nhất kiểu cột: một số cột chứa số lớn (ex: 3.59e+13) — R hiển thị theo ký hiệu khoa học — nhưng các cột khác cùng cột thời điểm lại là chuỗi kiểu “36109910649785” (chuỗi số nguyên lớn). Vì vậy bạn cần chuẩn hóa: chuyển tất cả các cột thời điểm thành numeric.

Các cột CHỈ TIÊU, Mã số, Thuyết minh là chr — hợp lý (đây là nhãn/định danh).

Dòng đầu có nhiều NA trong các cột thời điểm — có thể là dòng tiêu đề phân nhóm (ví dụ “A - TÀI SẢN”) chứ không phải dữ liệu số, nên cần xử lý.

8. Xem kích thước cụ thể từng cột

sapply(cdkt, length)
##    CHỈ TIÊU       Mã số Thuyết minh       45657       45291       44926 
##          85          85          85          85          85          85 
##       44561       44196       43830       43465       43100       42735 
##          85          85          85          85          85          85 
##       42369 
##          85

Giải thích: Kiểm tra số lượng phần tử trong mỗi cột để phát hiện lỗi đọc dữ liệu.

Kết quả

Mỗi cột đều có 85 phần tử.

Kỹ thuật

length() trên cột trả số phần tử (ở đây = số hàng).

Ý nghĩa

Không có cột nào bị thiếu số lượng hàng; file được đọc đầy đủ theo số hàng 85.

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

colSums(is.na(cdkt))
##    CHỈ TIÊU       Mã số Thuyết minh       45657       45291       44926 
##           0           0          53          10          11          12 
##       44561       44196       43830       43465       43100       42735 
##           9           9           6           5           6           7 
##       42369 
##           8

Giải thích: Đếm số lượng giá trị thiếu trong từng cột để chuẩn bị cho bước xử lý dữ liệu thô.

Kỹ thuật

is.na() tạo ma trận TRUE/FALSE; colSums() cộng số TRUE cho mỗi cột.

Ý nghĩa

Cột Thuyết minh có 53 NA trên 85 (~62% missing) — nghĩa là nhiều dòng không có chú thích thuyết minh cho chỉ tiêu. Phần Thuyết minh chỉ được ghi cho 1 số dòng quan trọng.

Các cột thời điểm (45657, 45291, …) có missing từ ~5–12 giá trị — không lớn nhưng cần kiểm tra vì missing là do:

chỉ tiêu đó không áp dụng trong năm (ví dụ nhóm tiêu đề), hoặc

lỗi chuyển kiểu (text -> NA khi chuyển numeric).

10. Tính tỉ lệ thiếu dữ liệu theo phần trăm

missing_rate <- colMeans(is.na(cdkt)) * 100
missing_rate
##    CHỈ TIÊU       Mã số Thuyết minh       45657       45291       44926 
##    0.000000    0.000000   62.352941   11.764706   12.941176   14.117647 
##       44561       44196       43830       43465       43100       42735 
##   10.588235   10.588235    7.058824    5.882353    7.058824    8.235294 
##       42369 
##    9.411765

Giải thích: Giúp ta xem biến nào có dữ liệu thiếu vượt ngưỡng chấp nhận.

Kỹ thuật

colMeans(is.na(…)) trả tỉ lệ trung bình TRUE per column; nhân 100 để ra %.

Ý nghĩa

Thuyết minh ~62% missing — đó là con số lớn; có thể cần:

loại bỏ cột nếu thuyết minh không quan trọng cho phân tích định lượng, hoặc

giữ nhưng ghi chú rằng phần này thiếu nhiều.

Các cột giá trị có missing ~5–15% — chấp nhận được tuỳ phân tích.

11. Đếm số giá trị duy nhất trong từng biến

sapply(cdkt, function(x) length(unique(x)))
##    CHỈ TIÊU       Mã số Thuyết minh       45657       45291       44926 
##          78          84          29          74          72          71 
##       44561       44196       43830       43465       43100       42735 
##          72          74          76          78          78          77 
##       42369 
##          76

Giải thích:

Kỹ thuật

unique() tìm giá trị khác nhau; length() đếm.

Ý nghĩa

CHỈ TIÊU có 78 giá trị khác nhau trên 85 dòng → chủ yếu mỗi dòng là một chỉ tiêu khác nhau (có vài dòng là nhóm/tiêu đề).

Mã số có 84 giá trị khác nhau → hầu như mỗi dòng có mã riêng, có 1 mã trống/không chuẩn.

Các cột năm có ~70–78 giá trị khác nhau → nhiều chỉ tiêu có giá trị khác nhau theo năm; phù hợp.

Thuyết minh chỉ có 29 giá trị khác nhau → nhiều dòng dùng chung thuyết minh/ghi chú.

12. Kiểm tra dữ liệu trùng lặp

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

Giải thích: Phát hiện các dòng dữ liệu bị trùng.

Kỹ thuật

duplicated() kiểm tra toàn hàng; sum() đếm số hàng trùng.

Ý nghĩa

Không có hàng bị trùng hoàn toàn — tốt.

CHƯƠNG 3: LÀM SẠCH VÀ CHUẨN HÓA DỮ LIỆU CDKT

1. Chuẩn hóa tên cột năm

colnames(cdkt) <- gsub("45657", "2024",
                gsub("45291", "2023",
                gsub("44926", "2022",
                gsub("44561", "2021",
                gsub("44196", "2020",
                gsub("43830", "2019",
                gsub("43465", "2018",
                gsub("43100", "2017",
                gsub("42735", "2016",
                gsub("42369", "2015", colnames(cdkt)))))))))))
colnames(cdkt)
##  [1] "CHỈ TIÊU"    "Mã số"       "Thuyết minh" "2024"        "2023"       
##  [6] "2022"        "2021"        "2020"        "2019"        "2018"       
## [11] "2017"        "2016"        "2015"

Giải thích

Kỹ thuật:

Dùng lệnh gsub() lồng nhau để thay thế các mã số cột (serial date) bằng năm thực tế.

Excel lưu ngày tháng dưới dạng “serial number” – ví dụ 45657 tương ứng với năm 2024.

colnames(cdkt) gán lại tên mới cho bảng dữ liệu.

Ý nghĩa:

Sau bước này, các cột dữ liệu tài chính đã mang tên năm rõ ràng (2016, 2017, …, 2024).

Giúp người đọc dễ hiểu, dễ thao tác khi làm phân tích chuỗi thời gian sau này.

2. Chuyển đổi các cột năm sang kiểu số

# Xác định vị trí các cột từ cột thứ 4 trở đi
cols_num <- 4:ncol(cdkt)
 
# Chỉ ép kiểu số cho các cột này
cdkt[cols_num] <- lapply(cdkt[cols_num], function(x) as.numeric(as.character(x)))
## Warning in FUN(X[[i]], ...): NAs introduced by coercion
## Warning in FUN(X[[i]], ...): NAs introduced by coercion
## Warning in FUN(X[[i]], ...): NAs introduced by coercion
## Warning in FUN(X[[i]], ...): NAs introduced by coercion
## Warning in FUN(X[[i]], ...): NAs introduced by coercion
## Warning in FUN(X[[i]], ...): NAs introduced by coercion
# Kiểm tra cấu trúc sau chuyển đổi
str(cdkt)
## tibble [85 × 13] (S3: tbl_df/tbl/data.frame)
##  $ CHỈ TIÊU   : chr [1:85] "A - TÀI SẢN" "Tài sản ngắn hạn (100 = 110+120+130+140+150)" "Tiền và các khoản tương đương tiền" "Tiền" ...
##  $ Mã số      : chr [1:85] " " "100" "110" "111" ...
##  $ Thuyết minh: chr [1:85] NA NA "V.1" NA ...
##  $ 2024       : num [1:85] NA 3.76e+13 2.23e+12 1.88e+12 3.48e+11 ...
##  $ 2023       : num [1:85] NA 3.59e+13 2.91e+12 1.03e+12 1.89e+12 ...
##  $ 2022       : num [1:85] NA 3.16e+13 2.30e+12 1.33e+12 9.73e+11 ...
##  $ 2021       : num [1:85] NA 3.61e+13 2.35e+12 1.19e+12 1.16e+12 ...
##  $ 2020       : num [1:85] NA 2.97e+13 2.11e+12 8.64e+11 1.25e+12 ...
##  $ 2019       : num [1:85] NA 2.47e+13 2.67e+12 2.38e+12 2.87e+11 ...
##  $ 2018       : num [1:85] NA 2.06e+13 1.52e+12 1.07e+12 4.50e+11 ...
##  $ 2017       : num [1:85] NA 2.03e+13 9.63e+11 8.34e+11 1.29e+11 ...
##  $ 2016       : num [1:85] NA 1.87e+13 6.55e+11 6.00e+11 5.55e+10 ...
##  $ 2015       : num [1:85] NA 1.67e+13 1.36e+12 1.21e+12 1.46e+11 ...

Giải thích

Về mặt kỹ thuật R

Kết quả str(cdkt) hiển thị 3 cột đầu: CHỈ TIÊU, Mã số, Thuyết minh → kiểu ký tự (chr)

Từ cột 2024 đến 2015:đều là kiểu số (num)

Về mặt ý nghĩa:

Việc chuẩn hóa này giúp đảm bảo tính toàn vẹn dữ liệu:

ta có thể dễ dàng tính toán các chỉ tiêu tài chính như tổng tài sản, tăng trưởng qua các năm, hay tỷ trọng từng khoản mục.

3. Chuyển đổi đơn vị tiền tệ từ “đồng” sang “triệu đồng” và làm tròn 2 chữ số

# Chuyển đơn vị tiền từ "đồng" sang "triệu đồng" và làm tròn 2 chữ số
cdkt[4:ncol(cdkt)] <- lapply(cdkt[4:ncol(cdkt)], function(x) round(x / 1e6, 2))

Giải thích:

Về mặt kỹ thuật:

Lệnh cdkt[4:ncol(cdkt)] chọn các cột dữ liệu theo năm, lapply áp dụng cùng một phép biến đổi cho từng cột. Trong đó, x / 1e6 đổi đơn vị từ đồng sang triệu đồng và round(…, 2) làm tròn đến hai chữ số thập phân để dữ liệu gọn và dễ đọc hơn.

Sau khi quy đổi:

Các chỉ tiêu như Tổng tài sản, Vốn chủ sở hữu, Tiền mặt, … đều sẽ được thể hiện bằng triệu đồng thay vì đồng.

Giúp người đọc dễ dàng so sánh giữa các năm mà không bị “rối” bởi con số quá dài.

Đây là chuẩn trình bày phổ biến khi làm báo cáo phân tích tài chính doanh nghiệp.

4. Lựa chọn chỉ tiêu phân tích

library(dplyr)
library(stringr)
# Lựa chọn 18 chỉ tiêu tiêu biểu cho cấu trúc tài sản và nguồn vốn
cdkt_chon <- cdkt %>%
  filter(`CHỈ TIÊU` %in% c(
    "Tài sản ngắn hạn (100 = 110+120+130+140+150)",
    "Tiền và các khoản tương đương tiền",
    "Phải thu khách hàng",
    "Phải thu từ cho vay ngắn hạn",
    "Phải thu ngắn hạn khác",
    "Hàng tồn kho",
    "Tài sản ngắn hạn khác",
    "Phải thu từ cho vay dài hạn",
    "Phải thu dài hạn khác",
    "Tài sản cố định hữu hình",
    "Tài sản cố định vô hình",
    "Tài sản dở dang dài hạn",
    "Tài sản dài hạn khác",
    "TỔNG CỘNG TÀI SẢN (270 = 100 + 200)",
    "Nợ ngắn hạn",
    "Nợ dài hạn",
    "Lợi nhuận sau thuế chưa phân phối",
    "Vốn chủ sở hữu",
    "TỔNG NGUỒN VỐN (440 = 300 + 400)"
  ))

Giải thích

Giải thích kỹ thuật

filter() dùng để lọc hàng theo điều kiện.

CHỈ TIÊU %in% c(…) nghĩa là chỉ giữ lại những dòng có tên nằm trong danh sách liệt kê.

Danh sách 18 chỉ tiêu được chọn bao quát cả tài sản ngắn hạn, dài hạn, nợ và vốn chủ sở hữu, giúp phân tích được cấu trúc tài sản – nguồn vốn.

Ý nghĩa:

Bộ dữ liệu sau bước này cô đọng, phản ánh được các nhóm tài sản và nguồn vốn chủ yếu của VNM qua 10 năm.

Điều này tạo nền tảng cho việc phân tích xu hướng thay đổi và đánh giá sự cân đối giữa tài sản – nguồn vốn trong.

5. Kiểm tra dữ liệu sau lọc

# Kiểm tra tổng quan dữ liệu sau khi lọc
glimpse(cdkt_chon)
## Rows: 21
## Columns: 13
## $ `CHỈ TIÊU`    <chr> "Tài sản ngắn hạn (100 = 110+120+130+140+150)", "Tiền và…
## $ `Mã số`       <chr> "100", "110", "131", "135", "136", "140", "141", "150", …
## $ `Thuyết minh` <chr> NA, "V.1", NA, NA, "V.3(a)", "V.5", NA, NA, NA, NA, "V.3…
## $ `2024`        <dbl> 37553650.07, 2225943.73, 4793132.73, NA, 896479.53, 5686…
## $ `2023`        <dbl> 35935879.62, 2912027.36, 4808183.56, NA, 1080803.41, 612…
## $ `2022`        <dbl> 31560382.17, 2299943.53, 4633942.51, NA, 890466.20, 5537…
## $ `2021`        <dbl> 36109910.6, 2348551.9, 4367766.5, NA, 810697.1, 6773071.…
## $ `2020`        <dbl> 29665725.81, 2111242.82, 4173563.21, 150.00, 483737.48, …
## $ `2019`        <dbl> 24721565.38, 2665194.64, 3474498.52, 31170.34, 438267.52…
## $ `2018`        <dbl> 20559756.79, 1522610.17, 3380017.35, NA, 394535.47, 5525…
## $ `2017`        <dbl> 20307434.79, 963335.91, 3613981.84, NA, 367850.64, 40210…
## $ `2016`        <dbl> 18673827.89, 655423.10, 2191348.50, NA, 390619.27, 45217…
## $ `2015`        <dbl> 16731875.43, 1358682.60, 2202396.06, NA, 359995.34, 3810…

Giải thích

Lệnh glimpse() cho biết số dòng, số cột, tên biến và kiểu dữ liệu của từng cột trong bảng cdkt_chon.

Qua đó giúp ta xác nhận rằng dữ liệu đã được lọc đúng (21 dòng, 13 cột) và các cột năm đều có dạng numeric.

Kết quả:

Số dòng: 21

Số cột: 13

3 cột đầu: CHỈ TIÊU, Mã số, Thuyết minh → kiểu chr (chuỗi ký tự)

Từ cột 2024 → 2015: đều là kiểu dbl (numeric)

Một vài ô trong cột Thuyết minh và một số năm có NA — chủ yếu do các chỉ tiêu tổng hợp (không có thuyết minh chi tiết hoặc giá trị chưa cập nhật).

Nhận xét:

Bộ dữ liệu sau khi lọc đã giữ lại 20 chỉ tiêu chính, đủ để phân tích cấu trúc tài sản và nguồn vốn của Vinamilk giai đoạn 2016–2025.

Các cột năm đã ở đúng kiểu số học (numeric) — sẵn sàng cho tính toán tỷ trọng, tăng trưởng,…

6. Kiểm tra sự tồn tại của giá trị thiếu (NA)

colSums(is.na(cdkt_chon))
##    CHỈ TIÊU       Mã số Thuyết minh        2024        2023        2022 
##           0           0          14           2           3           3 
##        2021        2020        2019        2018        2017        2016 
##           3           3           1           1           1           2 
##        2015 
##           2

Giải thích

Hàm is.na() xác định các ô bị thiếu giá trị, sau đó colSums() tính tổng số lượng NA của từng cột.

Một số cột có giá trị NA nhẹ do một năm không dùng chỉ tiêu đó.

7. Kiểm tra trùng lặp trong cột “CHỈ TIÊU”

sum(duplicated(cdkt_chon$`CHỈ TIÊU`))
## [1] 2

Giải thích:

Hàm duplicated() kiểm tra xem trong cột “CHỈ TIÊU” có giá trị nào lặp lại không.

sum() đếm tổng số dòng trùng.

Kết quả:

Có 2 dòng trùng tên “Hàng tồn kho”.

Nguyên nhân & ý nghĩa:

Hai dòng này có mã số khác nhau (140 và 141) → tức là Vinamilk chia nhỏ “Hàng tồn kho” thành các cấu phần chi tiết.

Việc giữ lại cả hai là hợp lý, vì chúng thể hiện các khoản mục khác nhau trong cấu trúc tài sản ngắn hạn.

8. Xem vài dòng đầu để đối chiếu trực quan

head(cdkt_chon)

Giải thích

Hàm head() hiển thị 6 dòng đầu tiên trong bảng dữ liệu.

Mục đích là đối chiếu trực quan: xác nhận tên chỉ tiêu, giá trị các năm, và cách hiển thị số liệu sau khi đổi đơn vị sang triệu đồng.

9. Chuẩn hóa lại tên chỉ tiêu để dễ xử lý

cdkt_chon <- cdkt_chon %>% 
  mutate(`CHỈ TIÊU` = str_trim(str_remove(`CHỈ TIÊU`, "\\(.*\\)")))

Giải thích:

str_remove(“\(.*\)“): xóa phần mô tả trong ngoặc (như “(100 = 110+120+…)”).

str_trim(): loại bỏ khoảng trắng thừa.

Ý nghĩa: Làm cho tên chỉ tiêu ngắn gọn, thống nhất, dễ hiển thị trên biểu đồ và báo cáo.

10. Chuẩn hóa và đặt lại thứ tự các cột

cdkt_chon <- cdkt_chon %>% 
  select(`CHỈ TIÊU`, `Mã số`, `Thuyết minh`, sort(names(.)[4:ncol(.)]))

Giải thích:

select() dùng để sắp xếp lại thứ tự cột.

sort(names(.)[4:ncol(.)]) giúp các năm hiển thị theo thứ tự 2016 → 2025, thay vì ngẫu nhiên.

Ý nghĩa: Giúp dữ liệu nhìn trực quan, thuận lợi cho việc vẽ biểu đồ chuỗi thời gian

CHƯƠNG 4: THỐNG KÊ MÔ TẢ CƠ BẢN

1. Liệt kê các cột năm

years <- names(cdkt_chon)[4:ncol(cdkt_chon)]
years
##  [1] "2015" "2016" "2017" "2018" "2019" "2020" "2021" "2022" "2023" "2024"

Giải thích kỹ thuật (ngắn):

names(cdkt_chon) lấy tên tất cả cột; [4:ncol(…)] chọn từ cột thứ 4 đến cuối (các cột năm).

Gán vào biến years để tái sử dụng trong các thao tác tiếp theo (tránh gõ tay, đảm bảo nhất quán).

years in ra giúp bạn kiểm tra thứ tự và tên (ví dụ “2015” “2017” … “2024”).

Ý nghĩa:

Chuẩn bị biến tiện dụng để lặp qua các năm trong mọi phép tính (mean, sd, pivot, v.v.), đảm bảo không nhầm thứ tự năm khi xử lý.

2. Tính tổng tài sản của VNM qua các năm

library(tidyr)
library(dplyr)
# Lọc đúng dòng chứa chỉ tiêu Tổng cộng tài sản
tong_ts <- cdkt_chon %>%
  filter(str_detect(`CHỈ TIÊU`, regex("tổng cộng tài sản", ignore_case = TRUE)))

Giải thích:

filter() lọc các dòng thỏa điều kiện.

str_detect() tìm chuỗi ký tự trong cột CHỈ TIÊU có chứa cụm “tổng cộng tài sản”.

regex(…, ignore_case = TRUE) cho phép không phân biệt chữ hoa – chữ thường (ví dụ: “TỔNG CỘNG TÀI SẢN” hay “tổng cộng tài sản” đều được nhận).

Kết quả:

Giữ lại duy nhất một dòng có tên chỉ tiêu “Tổng cộng tài sản (270 = 100 + 200)” — tức là tổng giá trị toàn bộ tài sản của Vinamilk từng năm.

3. Chuyển dữ liệu từ dạng ngang sang dọc (mỗi năm 1 dòng)

tong_ts_year <- tong_ts %>%
  select(all_of(years)) %>%
  pivot_longer(cols = everything(),
               names_to = "Năm",
               values_to = "Tổng_tài_sản")

Giải thích:

select(all_of(years)):

Giữ lại các cột năm (2015 → 2024), bỏ các cột mô tả như CHỈ TIÊU, Mã số, Thuyết minh.

Biến years đã được định nghĩa trước đó là danh sách các năm (tên cột).

pivot_longer():

Chuyển dữ liệu từ dạng wide (ngang) → dạng long (dọc).

Trước đó, mỗi cột là một năm, sau lệnh này, mỗi dòng là một cặp (Năm – Giá trị).

names_to = “Năm”: tên cột mới lưu giá trị năm.

values_to = “Tổng_tài_sản”: tên cột mới chứa giá trị tài sản của năm tương ứng.

Kết quả:

Bảng tong_ts_year có 10 dòng (ứng với 10 năm) và 2 cột:

Năm: 2015 → 2024

Tổng_tài_sản: giá trị tổng tài sản (đơn vị: triệu đồng)

4. Xem kết quả

tong_ts_year

Giải thích ý nghĩa kinh tế

Chỉ tiêu “Tổng cộng tài sản” phản ánh quy mô tài sản của doanh nghiệp tại thời điểm cuối năm — nói cách khác, đây là “tổng nguồn lực mà doanh nghiệp đang sở hữu”.

Dữ liệu từ 2015 đến 2024 cho thấy Vinamilk tăng trưởng mạnh về quy mô tài sản, từ khoảng 27.5 nghìn tỷ lên hơn 55 nghìn tỷ đồng, tức là gấp đôi trong vòng 10 năm.

Việc chuyển dữ liệu sang dạng “dọc” (long format) rất quan trọng để:

dễ dàng vẽ biểu đồ xu hướng (dùng ggplot2),

và tính tốc độ tăng trưởng hàng năm trong các bước tiếp theo (ví dụ, tăng trưởng %, CAGR).

5. Tính tốc độ tăng trưởng (%) của Tổng tài sản qua các năm

# Tính tốc độ tăng trưởng tổng tài sản qua các năm
tong_ts_year <- tong_ts_year %>%
arrange(Năm) %>%
mutate(Tang_truong_TS = (Tổng_tài_sản / lag(Tổng_tài_sản) - 1) * 100)
tong_ts_year

Đoạn code này bao gồm 3 thao tác:

Sắp xếp theo năm.

Tạo cột tăng trưởng.

Hiển thị kết quả.

Giải thích kỹ thuật

arrange(Năm): sắp xếp lại theo thứ tự năm tăng dần (đảm bảo tính đúng tỉ lệ tăng).

mutate(): tạo thêm cột mới Tang_truong_TS — tốc độ tăng trưởng theo % giữa năm hiện tại và năm trước.

lag(Tổng_tài_sản): lấy giá trị tổng tài sản của năm liền trước trong phép chia.

(Tổng_tài_sản / lag(Tổng_tài_sản) - 1) * 100: công thức tính % tăng trưởng.

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

Tài sản Vinamilk tăng liên tục hầu hết các năm, chỉ có năm 2022 giảm nhẹ (-9.09%) — khả năng do biến động kinh tế hậu COVID.

Tốc độ tăng cao nhất là ~19.6% (2019) — giai đoạn tăng trưởng mạnh.

Trung bình các năm khoảng 8–10%, cho thấy doanh nghiệp duy trì mức tăng ổn định.

6. Thống kê mô tả cho tốc độ tăng trưởng tài sản

# Thống kê mô tả cho biến Tang_truong_TS
summary(tong_ts_year$Tang_truong_TS)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max.    NA's 
##  -9.093   6.916   8.350   8.318  10.117  19.627       1
# Tính thêm độ lệch chuẩn và hệ số biến thiên (CV)
sd_ts <- sd(tong_ts_year$Tang_truong_TS, na.rm = TRUE)
mean_ts <- mean(tong_ts_year$Tang_truong_TS, na.rm = TRUE)
cv_ts <- (sd_ts / mean_ts) * 100
sd_ts
## [1] 8.253122
mean_ts
## [1] 8.317508
cv_ts
## [1] 99.22589

Giải thích:

summary() cho các giá trị: Min, 1st Qu., Median, Mean, 3rd Qu., Max.

sd() tính độ lệch chuẩn, mean() tính trung bình, và cv_ts = hệ số biến thiên (%).

CV cho biết mức độ biến động của tăng trưởng — CV càng cao, tốc độ tăng càng thất thường.

Diễn giải

Tốc độ tăng trung bình 8.3%/năm ⇒ Vinamilk tăng trưởng tài sản khá ổn.

Độ lệch chuẩn cao (≈8.25) và CV ≈99% ⇒ mức biến động rất lớn — có năm tăng nhanh, có năm giảm mạnh (như 2022).

Giai đoạn 2016–2019 là giai đoạn tăng bền, còn 2022 là năm suy giảm hiếm hoi do Covid.

7. Thống kê mô tả cho quy mô Tổng tài sản

# Thống kê mô tả cho Tổng tài sản
summary(tong_ts_year$Tổng_tài_sản)
##     Min.  1st Qu.   Median     Mean  3rd Qu.     Max. 
## 27478176 35342016 46566177 43156011 51625694 55049062
# Tính độ lệch chuẩn, giá trị trung bình, và hệ số biến thiên (CV)
sd_ts_value <- sd(tong_ts_year$Tổng_tài_sản, na.rm = TRUE)
mean_ts_value <- mean(tong_ts_year$Tổng_tài_sản, na.rm = TRUE)
cv_ts_value <- (sd_ts_value / mean_ts_value) * 100
sd_ts_value
## [1] 10194620
mean_ts_value
## [1] 43156011
cv_ts_value
## [1] 23.62271

Giải thích:

summary() cho biết giá trị nhỏ nhất, lớn nhất, trung bình, và trung vị của Tổng tài sản.

sd() và cv đo mức độ dao động tuyệt đối và tương đối của quy mô tài sản.

CV nhỏ (<20%) → quy mô ổn định; lớn → dao động mạnh.

Diễn giải:

Giá trị trung bình (Mean) khoảng 43,156 tỷ đồng, cho thấy quy mô tài sản trung bình của Vinamilk trong giai đoạn 2015–2024 ở mức rất lớn.

Độ lệch chuẩn ≈ 10,194 tỷ đồng ⇒ mức biến động tuyệt đối vừa phải.

Hệ số biến thiên CV ≈ 23.6% < 30% ⇒ quy mô tài sản ổn định, ít biến động qua các năm.

So sánh với tốc độ tăng trưởng ở Thao tác 6 (CV ≈ 99%) → tốc độ tăng dao động mạnh, nhưng tổng tài sản vẫn tăng đều theo thời gian.

8. Phân tổ dữ liệu theo nhóm chỉ tiêu tài chính

# Thêm cột nhóm lớn cho từng loại chỉ tiêu
cdkt_chon <- cdkt_chon %>%
mutate(Nhom = case_when(
str_detect(`CHỈ TIÊU`, regex("phải thu|hàng tồn kho|tài sản ngắn", ignore_case = TRUE)) ~ "Tài sản ngắn hạn",
str_detect(`CHỈ TIÊU`, regex("dài hạn|cố định", ignore_case = TRUE)) ~ "Tài sản dài hạn",
str_detect(`CHỈ TIÊU`, regex("nợ", ignore_case = TRUE)) ~ "Nợ phải trả",
str_detect(`CHỈ TIÊU`, regex("vốn chủ|lợi nhuận", ignore_case = TRUE)) ~ "Vốn chủ sở hữu",
TRUE ~ "Khác"))

9. Tính trung bình Tổng tài sản của từng nhóm qua các năm

# Tính trung bình Tổng tài sản của từng nhóm qua các năm
ts_nhom <- cdkt_chon %>%
select(Nhom, all_of(years)) %>%
group_by(Nhom) %>%
summarise(across(all_of(years), mean, na.rm = TRUE))
## Warning: There was 1 warning in `summarise()`.
## ℹ In argument: `across(all_of(years), mean, na.rm = TRUE)`.
## ℹ In group 1: `Nhom = "Khác"`.
## Caused by warning:
## ! The `...` argument of `across()` is deprecated as of dplyr 1.1.0.
## Supply arguments directly to `.fns` through an anonymous function instead.
## 
##   # Previously
##   across(a:b, mean, na.rm = TRUE)
## 
##   # Now
##   across(a:b, \(x) mean(x, na.rm = TRUE))
ts_nhom

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

mutate(Nhom = case_when(…)) → tạo biến “Nhom” để gán mỗi chỉ tiêu vào nhóm chính.

group_by(Nhom) và summarise() → tính giá trị trung bình của từng nhóm qua các năm.

Kết quả: bảng mới ts_nhom có 4 nhóm lớn (Tài sản ngắn hạn, Tài sản dài hạn, Nợ phải trả, Vốn chủ sở hữu) và giá trị bình quân của mỗi nhóm theo năm.

Tài sản ngắn hạn và tài sản dài hạn tăng dần đều qua các năm → phản ánh xu hướng mở rộng đầu tư.

Vốn chủ sở hữu cũng tăng, đặc biệt mạnh từ 2021 → 2023.

Nợ phải trả tăng chậm hơn vốn chủ → cơ cấu vốn an toàn, ít phụ thuộc nợ.

Nhóm “Khác” gồm chỉ tiêu tổng hợp, nên giá trị cao nhất — gần bằng tổng tài sản.

10. Tính tỷ trọng (%) từng nhóm trong tổng tài sản

# Lấy tổng tài sản mỗi năm từ nhóm "Khác"
tong_taisan <- ts_nhom %>%
filter(Nhom == "Khác") %>%
select(all_of(years))
# Sao chép bảng, loại nhóm "Khác"
tytrong_nhom <- ts_nhom %>%
filter(Nhom != "Khác")
# Tính tỷ trọng (%) theo từng năm bằng vòng lặp
for (y in years) {
tytrong_nhom[[y]] <- round(tytrong_nhom[[y]] / tong_taisan[[y]] * 100, 2)}
# Xem kết quả
tytrong_nhom
  1. filter(Nhom == “Khác”)

→ Lọc ra dòng chứa nhóm “Khác”, vì trong dữ liệu nhóm này biểu thị Tổng cộng tài sản.

→ Kết quả lưu vào tong_taisan, chỉ gồm các cột năm và giá trị tổng tài sản tương ứng.

  1. select(all_of(years))

→ Giữ lại các cột năm từ 2015 → 2024, bỏ cột “Nhom”.

→ Mục tiêu: chỉ lấy phần dữ liệu số để chia tỷ trọng sau này.

  1. filter(Nhom != “Khác”)

→ Tạo một bản sao tytrong_nhom, loại bỏ dòng “Khác” để chỉ còn 4 nhóm chính:

Tài sản ngắn hạn

Tài sản dài hạn

Nợ phải trả

Vốn chủ sở hữu

  1. for (y in years) vòng lặp

→ Với mỗi năm y, chia giá trị của từng nhóm cho tổng tài sản cùng năm rồi nhân 100, ra tỷ trọng (%).

→ round(…, 2) làm tròn đến 2 chữ số thập phân để hiển thị gọn.

→ Vòng lặp giúp phép chia “cột–cột” chính xác cho từng năm.

  1. tytrong_nhom

→ In ra bảng kết quả đã tính xong tỷ trọng (%) của từng nhóm trong tổng tài sản qua các năm.

Ý nghĩa kinh tế

Nợ phải trả: Tăng từ 31.99% (2015) lên 49.30% (2024) — nghĩa là mức độ vay nợ của Vinamilk tăng mạnh, phản ánh xu hướng sử dụng đòn bẩy tài chính cao hơn trong giai đoạn gần đây.

Vốn chủ sở hữu: Giảm từ 70.09% xuống 52.94% — cho thấy tỷ trọng vốn tự có giảm dần, doanh nghiệp có thể đang chi trả cổ tức lớn hoặc tăng vay nợ để tài trợ hoạt động.

Tài sản ngắn hạn: Gần như ổn định (dao động quanh 18%) — chứng tỏ cấu trúc vốn lưu động duy trì cân bằng, không có biến động mạnh.

Tài sản dài hạn: Giảm nhẹ từ 10.86% xuống 8.80% — phản ánh Vinamilk không mở rộng nhiều tài sản cố định, có thể tập trung vào vốn lưu động hoặc đầu tư ngắn hạn.

11. Tính tỷ lệ Nợ phải trả / Vốn chủ sở hữu (Debt-to-Equity ratio)

# Thao tác 11: Tính tỷ lệ Nợ phải trả / Vốn chủ sở hữu (D/E)
# Tách dữ liệu hai nhóm cần dùng
no_phai_tra <- ts_nhom %>%
filter(Nhom == "Nợ phải trả") %>%
select(all_of(years))
von_chu_so_huu <- ts_nhom %>%
filter(Nhom == "Vốn chủ sở hữu") %>%
select(all_of(years))
# Tính tỷ lệ D/E cho từng năm
tyle_de <- data.frame(
Năm = years,
DE_ratio = round(unlist(no_phai_tra) / unlist(von_chu_so_huu), 3))
# Hiển thị kết quả
tyle_de

Giải thích kỹ thuật

filter(Nhom == “Nợ phải trả”) và filter(Nhom == “Vốn chủ sở hữu”)

→ Lọc riêng hai dòng chứa các nhóm cần tính.

select(all_of(years))

→ Giữ lại các cột năm (2015–2024) để đảm bảo dữ liệu chỉ còn số liệu gốc.

unlist()

→ Chuyển bảng 1 dòng nhiều cột thành vector để dễ chia từng cặp giá trị năm tương ứng.

Phép chia no / vốn

→ Cho biết mỗi 1 đồng vốn chủ sở hữu được tài trợ thêm bao nhiêu đồng nợ.

round(…, 3)

→ Làm tròn 3 chữ số thập phân cho gọn gàng.

Tạo bảng kết quả tyle_de

→ Gồm 2 cột:

Năm (2015 → 2024)

DE_ratio (tỷ lệ Nợ/Vốn chủ tương ứng từng năm).

Phân tích chi tiết theo năm:

2015–2019: D/E tăng từ 0.46 → 0.77, cho thấy Vinamilk tăng vay nợ để tài trợ mở rộng hoạt động.

2020: D/E vọt lên 2.06 – đây là một ngoại lệ, có thể do vốn chủ giảm mạnh (ví dụ chi trả cổ tức, đầu tư mới), làm tỷ lệ tạm thời tăng cao.

2021–2024: D/E giảm dần và ổn định quanh 0.8–0.9, phản ánh Vinamilk quay lại cấu trúc tài chính an toàn hơn, cân bằng giữa nợ và vốn chủ.

12. Tính tốc độ tăng trưởng bình quân (CAGR) giai đoạn 2015–2024

# Thao tác 12: Tính tốc độ tăng trưởng bình quân (CAGR) cho các chỉ tiêu chính
# Lấy dữ liệu cần tính
ts_tong <- ts_nhom %>% filter(Nhom == "Khác") %>% select(all_of(years))
ts_no <- ts_nhom %>% filter(Nhom == "Nợ phải trả") %>% select(all_of(years))
ts_von <- ts_nhom %>% filter(Nhom == "Vốn chủ sở hữu") %>% select(all_of(years))
# Số năm giữa 2015 và 2024
n_years <- length(years) - 1
# Hàm tính CAGR = (Giá trị cuối / Giá trị đầu)^(1/số năm) - 1
CAGR <- function(data){
round(((as.numeric(data[[ncol(data)]]) / as.numeric(data[[1]]))^(1/n_years) - 1) * 100, 2)}
# Tính CAGR cho từng nhóm
cagr_tong_ts <- CAGR(ts_tong)
cagr_no <- CAGR(ts_no)
cagr_von <- CAGR(ts_von)
# Gộp lại thành bảng kết quả
cagr_table <- data.frame(
Chỉ_tiêu = c("Tổng tài sản", "Nợ phải trả", "Vốn chủ sở hữu"),
CAGR_2015_2024 = c(cagr_tong_ts, cagr_no, cagr_von))
cagr_table

Giải thích kỹ thuật

Lọc dữ liệu theo nhóm:

“Khác” → Tổng tài sản.

“Nợ phải trả” → Nợ vay.

“Vốn chủ sở hữu” → Vốn tự có.

Xác định số năm (n_years):

Từ 2015 đến 2024 có 9 giai đoạn tăng trưởng liên tiếp.

Tính toán CAGR (Compound Annual Growth Rate):

CAGR = (Giá trị cuối / Giá trị đầu)^(1/số năm) - 1

Kết quả nhân 100 để chuyển sang đơn vị %.

Làm tròn bằng round(…, 2) để hiển thị gọn gàng.

Kết quả cagr_table:

Gồm hai cột:

Chỉ_tiêu: Tên chỉ tiêu.

CAGR_2015_2024: Tốc độ tăng trưởng bình quân hàng năm (%).

Kết luận:

Tài sản Vinamilk tăng ổn định (~8%/năm), phản ánh doanh nghiệp vẫn đang phát triển bền vững.

Nợ phải trả tăng nhanh nhất (~13%), là nguyên nhân khiến tỷ lệ nợ/vốn (D/E) tăng dần qua thời gian.

Vốn chủ sở hữu tăng chậm (~4.7%), phù hợp với mô hình công ty trưởng thành, ít huy động vốn mới.

13. So sánh và phân loại mức tăng trưởng (cao – trung bình – thấp) dựa trên CAGR

# <5%: Thấp, 5–10%: Trung bình, >10%: Cao
cagr_table <- cagr_table %>%
mutate(
Muc_tang_truong = case_when(
CAGR_2015_2024 < 5 ~ "Thấp",
CAGR_2015_2024 >= 5 & CAGR_2015_2024 <= 10 ~ "Trung bình",
CAGR_2015_2024 > 10 ~ "Cao"))
cagr_table

Giải thích kỹ thuật

mutate():

Thêm cột mới Muc_tang_truong vào bảng hiện tại.

case_when():

Hàm điều kiện của dplyr, cho phép phân loại theo giá trị.

< 5% → “Thấp”

5%–10% → “Trung bình”

10% → “Cao”

Kết quả cagr_table:

Gồm 3 cột:

Chỉ_tiêu

CAGR_2015_2024

Muc_tang_truong

Ý nghĩa:

Nợ phải trả là nhóm tăng trưởng nhanh nhất, thể hiện xu hướng gia tăng đòn bẩy tài chính.

Tổng tài sản tăng trung bình, nghĩa là Vinamilk vẫn đang mở rộng nhưng không ồ ạt.

Vốn chủ sở hữu tăng chậm, phù hợp với doanh nghiệp đã trưởng thành, không cần vốn mới thường xuyên.

CHƯƠNG 5: TRỰC QUAN HÓA DỮ LIỆU

1. Biểu đồ xu hướng Tổng tài sản, Nợ phải trả và Vốn chủ sở hữu (2015–2024)

library(ggplot2)
library(tidyr)
library(dplyr)
# Chuẩn bị dữ liệu dạng long để vẽ
ts_long <- ts_nhom %>%
filter(Nhom %in% c("Khác", "Nợ phải trả", "Vốn chủ sở hữu")) %>%
mutate(Nhom = recode(Nhom, "Khác" = "Tổng tài sản")) %>%
pivot_longer(cols = all_of(years), names_to = "Năm", values_to = "Giá_trị")
# Vẽ biểu đồ đường xu hướng
ggplot(ts_long, aes(x = as.numeric(Năm), y = Giá_trị / 1e3, color = Nhom)) +
geom_line(size = 1.2) +                        # Layer 1: đường xu hướng
geom_point(size = 2) +                         # Layer 2: điểm dữ liệu
geom_text(aes(label = round(Giá_trị / 1e3, 0)),
vjust = -0.5, size = 3, show.legend = FALSE) +  # Layer 3: nhãn giá trị
scale_color_manual(values = c("#0072B2", "#E69F00", "#56B4E9")) + # Layer 4: màu tùy chỉnh
labs(
title = "Xu hướng Tổng tài sản, Nợ phải trả và Vốn chủ sở hữu của Vinamilk (2015–2024)",
x = "Năm",
y = "Giá trị (tỷ đồng)",
color = "Chỉ tiêu") +                                             # Layer 5: tiêu đề + trục
theme_minimal(base_size = 13) +                 # Layer 6: theme hiển thị
theme(
plot.title = element_text(face = "bold", hjust = 0.5, size = 14),
legend.position = "bottom",
panel.grid.minor = element_blank())
## Warning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.
## ℹ Please use `linewidth` instead.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.

Giải thích kỹ thuật

pivot_longer(): chuyển bảng từ dạng ngang → dọc, giúp ggplot đọc dữ liệu dễ hơn.

geom_line(): vẽ đường nối thể hiện xu hướng.

geom_point(): đánh dấu các điểm dữ liệu theo từng năm.

geom_text(): hiển thị giá trị cụ thể trên mỗi điểm.

scale_color_manual(): tùy chỉnh màu sắc riêng cho từng chỉ tiêu.

theme_minimal() + labs(): thêm tiêu đề, nhãn trục, và cải thiện bố cục.

Nhận xét và ý nghĩa kinh tế:

Tổng tài sản (đường vàng)

Tăng đều đặn qua các năm, đặc biệt mạnh giai đoạn 2018–2024.

Cho thấy Vinamilk mở rộng quy mô ổn định, duy trì tốc độ tăng trưởng bền vững.

Nợ phải trả (đường xanh đậm)

Tăng nhanh từ khoảng 6.000 tỷ năm 2015 lên gần 18.000 tỷ năm 2024.

Thể hiện xu hướng gia tăng sử dụng đòn bẩy tài chính.

Vốn chủ sở hữu (đường xanh nhạt)

Tăng chậm hơn, có giai đoạn Năm 2020 có biến động mạnh

Nguyên nhân có thể do chi trả cổ tức cao, ít tăng vốn điều lệ và có thể do dịch Covid.

Dù vậy, xu hướng vẫn tăng dần → nền tài chính ổn định.

2. Biểu đồ tỷ trọng (%) các nhóm trong tổng tài sản (2015–2024)

# Thao tác 2: Biểu đồ tỷ trọng (%) các nhóm trong tổng tài sản
# Chuẩn bị dữ liệu tỷ trọng (đã có ở tytrong_nhom)
# Chuyển dữ liệu từ wide → long để ggplot dễ đọc
tytrong_long <- tytrong_nhom %>%
pivot_longer(cols = all_of(years),
names_to = "Năm",
values_to = "Tỷ_trọng")
# Vẽ biểu đồ stacked area (vùng chồng)
ggplot(tytrong_long, aes(x = as.numeric(Năm), y = Tỷ_trọng, fill = Nhom)) +
geom_area(alpha = 0.8, size = 0.5, color = "white") +   # Layer 1: vùng chồng
geom_line(position = "stack", color = "black", linewidth = 0.3) +  # Layer 2: viền
geom_text(aes(label = ifelse(Tỷ_trọng > 10, paste0(round(Tỷ_trọng,1), "%"), "")),
position = position_stack(vjust = 0.5), size = 3, color = "white") +  # Layer 3: nhãn phần trăm
scale_fill_brewer(palette = "Set2") +                   # Layer 4: màu tự động đẹp
labs(
title = "Biểu đồ tỷ trọng (%) các nhóm trong Tổng tài sản của Vinamilk (2015–2024)",x = "Năm",y = "Tỷ trọng (%)",
fill = "Nhóm chỉ tiêu") +                                                     # Layer 5: tiêu đề + trục
theme_minimal(base_size = 13) +                         # Layer 6: theme trình bày
theme(
plot.title = element_text(face = "bold", hjust = 0.5, size = 14),
legend.position = "bottom",
panel.grid.minor = element_blank())

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

pivot_longer(): chuyển bảng tỷ trọng từ dạng ngang sang dọc để ggplot hiểu đúng cấu trúc.

geom_area(): tạo vùng chồng theo tỷ trọng từng nhóm (mỗi màu đại diện cho một nhóm tài sản hoặc nguồn vốn).

geom_line(): thêm đường viền mảnh để dễ phân biệt các vùng.

geom_text(): hiển thị nhãn phần trăm trên vùng (chỉ hiện nếu >10% để tránh rối).

scale_fill_brewer(palette = “Set2”): sử dụng bảng màu chuyên nghiệp, dễ nhìn.

theme_minimal() + labs(): làm sạch biểu đồ, thêm tiêu đề và trục.

Nhận xét và phân tích tài chính:

Nợ phải trả (màu xanh lá nhạt):

Tỷ trọng tăng rõ rệt từ ~32% năm 2015 lên ~49% năm 2024.

Điều này khẳng định Vinamilk tăng cường sử dụng vốn vay — nhất quán với kết quả ở Thao tác 11 và 12.

Tuy nhiên, vẫn dưới 50%, nên cấu trúc vốn vẫn an toàn.

Vốn chủ sở hữu (màu hồng):

Giảm từ ~70% xuống ~53%, cho thấy tỷ trọng vốn tự có giảm dần.

Nguyên nhân có thể do chi trả cổ tức lớn hàng năm, ít huy động vốn mới.

Tài sản ngắn hạn (màu xanh dương nhạt):

Duy trì ổn định quanh 15–22% → phản ánh mức vốn lưu động hợp lý, ít biến động.

Tài sản dài hạn (màu cam):

Giảm nhẹ từ ~11% xuống ~8–9% → cho thấy Vinamilk không mở rộng mạnh mảng đầu tư dài hạn, tập trung hơn vào vận hành hiện có.

Năm 2020:

Tỷ trọng vốn chủ sở hữu giảm đột ngột → phản ánh tác động COVID-19 hoặc cơ cấu tạm thời thay đổi (chi trả cổ tức, lợi nhuận giảm).

Kết luận rút ra:

Cơ cấu tài chính của Vinamilk đang chuyển dịch dần:

→ Từ “vốn chủ sở hữu là chính” sang kết hợp giữa nợ và vốn chủ.

Tỷ trọng nợ tăng nhưng vẫn an toàn (<50%), phản ánh chiến lược mở rộng có kiểm soát.

Các nhóm tài sản giữ tỷ trọng ổn định, chứng minh hoạt động sản xuất – đầu tư của Vinamilk ổn định, ít biến động lớn.

3. Biểu đồ tỷ lệ Nợ phải trả / Vốn chủ sở hữu (D/E Ratio) qua các năm

# Thao tác 3: Biểu đồ D/E ratio qua các năm
library(ggplot2)
# Dữ liệu đã có trong tyle_de
ggplot(tyle_de, aes(x = as.numeric(Năm), y = DE_ratio)) +
geom_line(color = "#0072B2", size = 1.2) +                # Layer 1: đường xu hướng D/E
geom_point(color = "#0072B2", size = 3) +                 # Layer 2: điểm dữ liệu
geom_text(aes(label = round(DE_ratio, 2)),
vjust = -0.8, size = 3, color = "#0072B2") +    # Layer 3: nhãn giá trị
geom_hline(yintercept = 1, linetype = "dashed",
color = "red", linewidth = 0.8) +              # Layer 4: đường tham chiếu D/E = 1
annotate("text", x = 2016.5, y = 1.05, label = "Ngưỡng D/E = 1 (an toàn)",
color = "red", size = 3.2, hjust = 0) +          # Layer 5: chú thích
labs(
title = "Tỷ lệ Nợ phải trả / Vốn chủ sở hữu (D/E Ratio) của Vinamilk giai đoạn 2015–2024",x = "Năm",y = "Tỷ lệ D/E",
caption = "Nguồn: Báo cáo tài chính Vinamilk 2015–2024") +                                                       # Layer 6: tiêu đề + nhãn
theme_minimal(base_size = 13) +
theme(
plot.title = element_text(face = "bold", hjust = 0.5, size = 14),
panel.grid.minor = element_blank())

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

geom_line(): vẽ đường biểu diễn xu hướng D/E theo thời gian.

geom_point(): đánh dấu giá trị từng năm.

geom_text(): hiển thị nhãn tỷ lệ D/E trên biểu đồ.

geom_hline(): thêm đường tham chiếu D/E = 1, giúp xác định vùng an toàn.

annotate(): ghi chú trực tiếp trên đồ thị để tăng tính giải thích.

labs() + theme_minimal(): thêm tiêu đề, nhãn trục, chú thích và định dạng bố cục.

Phân tích và ý nghĩa tài chính:

2015–2018: Tỷ lệ D/E dao động quanh mức 0.45–0.69, cho thấy Vinamilk có cấu trúc vốn an toàn, phần lớn được tài trợ bằng vốn chủ sở hữu.

→ Doanh nghiệp gần như không phụ thuộc vào nợ vay, khả năng tự chủ tài chính cao.

2019–2020: D/E tăng mạnh, đạt đỉnh 2.06 năm 2020, vượt xa ngưỡng an toàn (D/E = 1).

→ Đây là dấu hiệu Vinamilk gia tăng vay nợ, có thể để mở rộng đầu tư hoặc ứng phó với ảnh hưởng COVID-19.

→ Tuy nhiên, mức nợ này khiến rủi ro tài chính tăng lên tạm thời.

2021–2024: D/E giảm dần về ~0.93, quay lại mức an toàn.

→ Cho thấy doanh nghiệp đã kiểm soát tốt nợ vay, cân bằng lại giữa nợ và vốn chủ sở hữu, đảm bảo sức khỏe tài chính ổn định.

Kết luận rút ra

Tỷ lệ D/E luôn < 1 (ngoại trừ 2020) → Vinamilk vẫn nằm trong ngưỡng an toàn.

Năm 2020 là điểm bất thường, minh chứng tác động từ yếu tố bên ngoài (COVID-19).

Giai đoạn sau 2021, D/E ổn định ~0.9 cho thấy doanh nghiệp tận dụng hợp lý nợ vay để tăng lợi nhuận, mà vẫn duy trì được độ an toàn tài chính.

4. Trực quan hóa tốc độ tăng trưởng bình quân (CAGR)

library(ggplot2)
# Vẽ biểu đồ cột so sánh CAGR
ggplot(cagr_table, aes(x = reorder(Chỉ_tiêu, CAGR_2015_2024), y = CAGR_2015_2024, 
               fill = Chỉ_tiêu)) +
  geom_col(width = 0.6, color = "black") +
  geom_text(aes(label = paste0(round(CAGR_2015_2024, 2), "%")), 
            vjust = -0.5, size = 4, fontface = "bold") +
  scale_fill_manual(values = c("#1f77b4", "#ff7f0e", "#2ca02c")) +
  labs(
    title = "So sánh tốc độ tăng trưởng bình quân (CAGR) giai đoạn 2015–2024",x = "Chỉ tiêu",y = "Tốc độ tăng trưởng bình quân (%)",
    caption = "Nguồn: Báo cáo tài chính Vinamilk 2015–2024") +
  theme_minimal(base_size = 13) +
  theme(
    legend.position = "none",
    plot.title = element_text(face = "bold", size = 15, hjust = 0.5),
    axis.text.x = element_text(angle = 10, hjust = 0.5))

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

geom_col() tạo biểu đồ cột thể hiện giá trị CAGR theo từng chỉ tiêu.

geom_text() hiển thị nhãn phần trăm ngay trên đầu cột để dễ đọc.

reorder() sắp xếp các cột tăng dần theo giá trị CAGR.

scale_fill_manual() chọn màu sắc khác nhau giúp phân biệt chỉ tiêu.

theme_minimal() và theme() điều chỉnh bố cục biểu đồ, loại bỏ chi tiết rườm rà, làm rõ nhãn và tiêu đề.

Diễn giải biểu đồ:

Giai đoạn 2015–2024:

Nợ phải trả (13.3%) tăng mạnh nhất → cho thấy Vinamilk đã sử dụng đòn bẩy tài chính ngày càng cao, tức là vay nợ nhiều hơn để tài trợ cho hoạt động và đầu tư mở rộng.

Tổng tài sản (7.97%) tăng ổn định → chứng tỏ quy mô doanh nghiệp được mở rộng đều đặn qua gần một thập kỷ.

Vốn chủ sở hữu (4.66%) tăng chậm hơn so với tổng tài sản → nghĩa là tăng trưởng phần lớn đến từ nợ vay hơn là vốn góp, điều này cần được kiểm soát để tránh rủi ro tài chính dài hạn.

Nhận xét tổng hợp:

Mặc dù Vinamilk vẫn duy trì được mức tăng trưởng tài sản tích cực, nhưng việc nợ tăng nhanh hơn vốn chủ sở hữu cảnh báo rằng doanh nghiệp đang dần phụ thuộc hơn vào nguồn vốn vay. Tuy nhiên, đây cũng có thể là chiến lược tài chính hợp lý nhằm tận dụng lãi suất thấp và mở rộng đầu tư trong giai đoạn 2018–2020.

5. Biểu đồ kết hợp (Dual Axis) giữa Tổng tài sản và Tỷ lệ Nợ/Vốn (D/E Ratio)

# Gộp hai bảng theo năm (sửa tên biến)
data_ket_hop <- tong_ts_year %>%
  select(Năm, Tổng_tài_sản) %>%
  left_join(tyle_de, by = "Năm")  # <- thay 'de_ratio' bằng 'tyle_de'
# Vẽ biểu đồ kết hợp
ggplot(data_ket_hop, aes(x = as.numeric(Năm))) +
  # Cột: Tổng tài sản
  geom_col(aes(y = Tổng_tài_sản / 1000, fill = "Tổng tài sản"), width = 0.6) +  
  # Đường: Tỷ lệ D/E
  geom_line(aes(y = DE_ratio * 20000, color = "Tỷ lệ D/E"), size = 1.3, group = 1) +
  geom_point(aes(y = DE_ratio * 20000, color = "Tỷ lệ D/E"), size = 3) +
  # Chú thích trục
  scale_y_continuous(
    name = "Tổng tài sản (tỷ đồng)",
    sec.axis = sec_axis(~./20000, name = "Tỷ lệ D/E")) +
  scale_fill_manual(values = "#FDB813") +
  scale_color_manual(values = "#0072B2") +
  labs(
    title = "Quan hệ giữa Quy mô tài sản và Tỷ lệ Nợ/Vốn (D/E) của Vinamilk (2015–2024)",
    subtitle = "Tổng tài sản tăng đều nhưng tỷ lệ nợ/vốn biến động rõ rệt giai đoạn 2020",
    caption = "Nguồn: Báo cáo tài chính Vinamilk 2015–2024",
    x = "Năm") +
  theme_minimal(base_size = 13) +
  theme(
    axis.title.y.right = element_text(color = "#0072B2"),
    axis.title.y.left  = element_text(color = "#FDB813"),
    plot.title = element_text(face = "bold", size = 14, hjust = 0.5),
    legend.position = "bottom")

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

geom_col() → hiển thị cột biểu diễn Tổng tài sản (tỷ đồng).

geom_line() + geom_point() → biểu diễn đường tỷ lệ D/E (nhân hệ số để cùng tỷ lệ trục).

scale_y_continuous(…, sec.axis = sec_axis()) → tạo hai trục tung: bên trái cho tài sản, bên phải cho tỷ lệ D/E.

left_join() → gộp hai bảng dữ liệu theo năm.

scale_color_manual() và scale_fill_manual() → định dạng màu sắc riêng biệt cho từng loại dữ liệu.

Diễn giải:

Giai đoạn 2015–2019:

Tổng tài sản tăng đều, tỷ lệ D/E ổn định quanh mức 0.45–0.75, phản ánh cơ cấu vốn an toàn, doanh nghiệp chủ yếu sử dụng vốn chủ sở hữu.

Năm 2020:

D/E vọt lên trên 2, thể hiện Vinamilk tăng mạnh vay nợ — có thể để duy trì dòng tiền hoạt động và đầu tư trong bối cảnh dịch COVID-19 ảnh hưởng đến doanh thu.

Từ 2021 trở đi:

D/E nhanh chóng giảm xuống dưới 1, cho thấy công ty giảm đòn bẩy tài chính, quay lại trạng thái an toàn, đồng thời duy trì tăng trưởng tổng tài sản ổn định.

6. Biểu đồ Heatmap thể hiện tỷ trọng (%) các nhóm trong cơ cấu tài sản – nguồn vốn của Vinamilk (2015–2024)

library(ggplot2)
library(tidyr)
library(dplyr)
# Giả sử dữ liệu tỷ trọng ở Thao tác 10 được lưu trong 'tytrong_nhom'
# Đưa dữ liệu về dạng "dọc" để ggplot dễ vẽ
tytrong_long <- tytrong_nhom %>%
  pivot_longer(cols = -Nhom,
               names_to = "Năm",
               values_to = "Tỷ_trọng") %>%
  mutate(Năm = as.numeric(Năm))
# Vẽ heatmap
ggplot(tytrong_long, aes(x = Năm, y = Nhom, fill = Tỷ_trọng)) +
  geom_tile(color = "white", size = 0.5) +
  geom_text(aes(label = paste0(round(Tỷ_trọng, 1), "%")), color = "black", size = 3.2) +
  scale_fill_gradient(low = "#E6F5C9", high = "#1A9850") +
  labs(
    title = "Biểu đồ nhiệt thể hiện tỷ trọng (%) các nhóm trong cơ cấu tài sản – nguồn vốn của Vinamilk (2015–2024)",
    subtitle = "Màu càng đậm thể hiện tỷ trọng càng cao",x = "Năm", y = "Nhóm chỉ tiêu",
    fill = "Tỷ trọng (%)",
    caption = "Nguồn: Báo cáo tài chính Vinamilk 2015–2024") +
  theme_minimal(base_size = 13) +
  theme(
    plot.title = element_text(face = "bold", hjust = 0.5),
    plot.subtitle = element_text(hjust = 0.5),
    axis.text.x = element_text(angle = 45, vjust = 0.7, hjust = 1),
    panel.grid = element_blank())

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

pivot_longer() → chuyển dữ liệu từ dạng rộng (wide) sang dọc (long) để mỗi cặp (Năm, Nhóm) có 1 giá trị tỷ trọng.

geom_tile() → tạo ô màu (heatmap) tương ứng với tỷ trọng.

geom_text() → hiển thị giá trị phần trăm ngay trong từng ô.

scale_fill_gradient() → tạo dải màu từ nhạt (tỷ trọng thấp) → đậm (tỷ trọng cao).

theme_minimal() + theme(…) → điều chỉnh font, màu, lưới, hướng chữ… để biểu đồ đẹp, dễ đọc.

Nhận xét biểu đồ:

Biểu đồ heatmap thể hiện sự thay đổi tỷ trọng (%) của 4 nhóm chính trong cơ cấu tài sản – nguồn vốn của Vinamilk giai đoạn 2015–2024, với màu đậm biểu thị tỷ trọng cao hơn.

Vốn chủ sở hữu: Giảm từ ~70% (2015) xuống ~53% (2024) → cho thấy doanh nghiệp ngày càng sử dụng nhiều nợ vay hơn, dù vẫn duy trì cơ cấu vốn an toàn.

Nợ phải trả: Tăng đều từ ~32% lên ~49% → phản ánh xu hướng gia tăng đòn bẩy tài chính, nhất là sau 2020.

Tài sản ngắn hạn: Ổn định quanh 18–21% → cho thấy quản lý vốn lưu động hiệu quả, đảm bảo thanh khoản ổn định.

Tài sản dài hạn: Giảm nhẹ từ ~11% xuống ~9% → chứng tỏ Vinamilk ít đầu tư mở rộng mới, tập trung tối ưu tài sản hiện có.

Tổng kết: Cơ cấu tài chính của Vinamilk chuyển dịch từ “an toàn – vốn chủ chiếm ưu thế” sang “cân bằng – sử dụng nợ hợp lý”, phản ánh chiến lược tăng hiệu quả vốn nhưng vẫn kiểm soát rủi ro tài chính.

7. Biểu đồ tốc độ tăng trưởng Tổng tài sản qua các năm

library(ggplot2)
library(dplyr)
# Chuẩn bị dữ liệu từ bảng tong_ts_year đã có sẵn
# (bao gồm cột Năm, Tổng_tài_sản, và Tang_truong_TS)
# Vẽ biểu đồ cột thể hiện % tăng trưởng từng năm
ggplot(tong_ts_year, aes(x = as.numeric(Năm), y = Tang_truong_TS)) +
  geom_col(fill = ifelse(tong_ts_year$Tang_truong_TS > 0, "#2CA02C", "#D62728"), width = 0.6) + #Xanh nếu tăng, đỏ nếu giảm
  geom_text(aes(label = paste0(round(Tang_truong_TS, 1), "%")),
            vjust = ifelse(tong_ts_year$Tang_truong_TS > 0, -0.5, 1.3),
            size = 3.5, fontface = "bold") +
  geom_hline(yintercept = 0, color = "black", linewidth = 0.8) +
  labs(
    title = "Tốc độ tăng trưởng Tổng tài sản của Vinamilk qua các năm (2015–2024)",x = "Năm", y = "Tăng trưởng (%)",
    caption = "Nguồn: Báo cáo tài chính Vinamilk 2015–2024") +
  theme_minimal(base_size = 13) +
  theme(
    plot.title = element_text(face = "bold", hjust = 0.5, size = 14),
    panel.grid.minor = element_blank())
## Warning: Removed 1 row containing missing values or values outside the scale range
## (`geom_col()`).
## Warning: Removed 1 row containing missing values or values outside the scale range
## (`geom_text()`).

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

geom_col() vẽ biểu đồ cột biểu diễn % tăng trưởng từng năm.

ifelse() đổi màu: xanh (#2CA02C) khi tăng, đỏ (#D62728) khi giảm.

geom_text() hiển thị nhãn % ngay trên đầu cột.

geom_hline(yintercept = 0) kẻ đường mốc 0% giúp dễ nhận biết năm tăng hay giảm.

theme_minimal() làm biểu đồ gọn, dễ đọc.

Nhận xét & ý nghĩa kinh tế:

Tốc độ tăng trưởng trung bình (CAGR) giai đoạn 2015–2024 ≈ 8%/năm → mức tăng trưởng tốt và bền vững.

Mặc dù có biến động tạm thời (2022), xu hướng dài hạn vẫn là tăng đều và ổn định.

Điều này khẳng định Vinamilk duy trì sức mạnh tài chính vững chắc, kiểm soát tốt quy mô tài sản và khả năng mở rộng trong dài hạn.

8. Biểu đồ tương quan giữa Tốc độ tăng trưởng tài sản và Tỷ lệ Nợ/Vốn (D/E Ratio)

library(ggplot2)
library(dplyr)
# Gộp dữ liệu tăng trưởng tài sản và D/E ratio theo năm
data_tuongquan <- tong_ts_year %>%
left_join(tyle_de, by = "Năm") %>%
filter(!is.na(Tang_truong_TS))
# Vẽ biểu đồ scatter plot (tương quan)
ggplot(data_tuongquan, aes(x = DE_ratio, y = Tang_truong_TS)) +
geom_point(color = "#0072B2", size = 3) +                       # Layer 1: các điểm dữ liệu
geom_smooth(method = "lm", se = FALSE, color = "red", linewidth = 1) +  # Layer 2: đường hồi quy tuyến tính
geom_text(aes(label = Năm), vjust = -1, size = 3, color = "black") +    # Layer 3: nhãn năm
labs(
title = "Tương quan giữa Tốc độ tăng trưởng tài sản và Tỷ lệ Nợ/Vốn (D/E) của Vinamilk",
subtitle = "Mức độ sử dụng nợ vay có liên hệ với tốc độ tăng trưởng tài sản qua các năm",x = "Tỷ lệ Nợ/Vốn (D/E)",y = "Tốc độ tăng trưởng tài sản (%)",
caption = "Nguồn: Báo cáo tài chính Vinamilk 2015–2024") +
theme_minimal(base_size = 13) +                                  # Layer 4: theme hiển thị
theme(
plot.title = element_text(face = "bold", size = 14, hjust = 0.5),
plot.subtitle = element_text(size = 11, hjust = 0.5))
## `geom_smooth()` using formula = 'y ~ x'

Giải thích kỹ thuật (5 Layer)

geom_point() → biểu diễn mỗi năm bằng một điểm trên mặt phẳng (D/E ratio – tốc độ tăng trưởng).

geom_smooth(method = “lm”) → thêm đường hồi quy tuyến tính, giúp xác định xu hướng tương quan.

geom_text() → gắn nhãn năm lên mỗi điểm để dễ nhận biết giai đoạn nào có thay đổi bất thường.

labs() → thêm tiêu đề, chú thích, nhãn trục, và nguồn dữ liệu.

theme_minimal() → làm gọn bố cục, tăng tính chuyên nghiệp khi trình bày báo cáo.

Nhận xét biểu đồ: Tương quan giữa tốc độ tăng trưởng tài sản và tỷ lệ Nợ/Vốn (D/E)

Biểu đồ scatter plot thể hiện mối quan hệ giữa tốc độ tăng trưởng tài sản (%) và tỷ lệ Nợ/Vốn (D/E) của Vinamilk trong giai đoạn 2015–2024.

Đường hồi quy có độ dốc âm nhẹ, cho thấy không tồn tại mối tương quan rõ ràng giữa việc tăng tỷ lệ nợ vay và tăng trưởng tài sản.

Các năm 2017–2019 đạt tốc độ tăng cao dù D/E ở mức thấp → tăng trưởng đến chủ yếu từ vốn tự có và hiệu quả hoạt động.

Năm 2020, dù D/E tăng mạnh lên hơn 2 lần, tốc độ tăng tài sản lại không vượt trội → dấu hiệu hiệu quả sử dụng vốn vay chưa cao.

Năm 2022 có D/E tương đối cao nhưng tăng trưởng âm → minh chứng cho rủi ro đòn bẩy tài chính nếu doanh nghiệp vay nợ trong giai đoạn suy giảm.

Kết luận: Việc sử dụng nợ vay chỉ hỗ trợ tăng trưởng tài sản ở mức hạn chế. Hiệu quả tăng trưởng của Vinamilk phụ thuộc nhiều hơn vào quản trị nội bộ và hiệu quả hoạt động, thay vì chỉ mở rộng đòn bẩy tài chính.

9. Biểu đồ cột nhóm so sánh cơ cấu tài sản – nguồn vốn của Vinamilk (2015–2024)

library(ggplot2)
library(tidyr)
library(dplyr)
# Chuẩn bị dữ liệu dạng long
tytrong_long_bar <- tytrong_nhom %>%
pivot_longer(cols = all_of(years),
names_to = "Năm",
values_to = "Tỷ_trọng")
# Vẽ biểu đồ cột nhóm
ggplot(tytrong_long_bar, aes(x = Năm, y = Tỷ_trọng, fill = Nhom)) +
geom_col(position = position_dodge(width = 0.8), width = 0.7) +      # Layer 1: cột nhóm
geom_text(aes(label = paste0(round(Tỷ_trọng, 1), "%")),
position = position_dodge(width = 0.8),
vjust = -0.5, size = 3) +                                 # Layer 2: nhãn phần trăm
scale_fill_brewer(palette = "Set2") +                               # Layer 3: bảng màu chuyên nghiệp
labs(
title = "So sánh tỷ trọng (%) các nhóm trong cơ cấu tài sản – nguồn vốn của Vinamilk (2015–2024)",x = "Năm",y = "Tỷ trọng (%)",
fill = "Nhóm chỉ tiêu",
caption = "Nguồn: Báo cáo tài chính Vinamilk 2015–2024") +
theme_minimal(base_size = 13) +                                     # Layer 4: theme trình bày
theme(
plot.title = element_text(face = "bold", hjust = 0.5, size = 14),
axis.text.x = element_text(angle = 10, hjust = 1),
legend.position = "bottom")

Giải thích kỹ thuật (5 Layer):

geom_col(position_dodge) → tạo các cột nhóm đại diện cho từng nhóm tài sản hoặc nguồn vốn theo năm.

geom_text() → thêm nhãn phần trăm ngay trên cột, giúp đọc nhanh tỷ trọng từng nhóm.

scale_fill_brewer() → áp dụng bảng màu “Set2” của R, giúp biểu đồ hài hòa và chuyên nghiệp.

labs() → thêm tiêu đề, nhãn trục, chú thích, và nguồn dữ liệu.

theme_minimal() → làm sạch bố cục, hiển thị rõ ràng và hiện đại.

Nhận xét & Ý nghĩa kinh tế:

Vốn chủ sở hữu chiếm tỷ trọng cao nhất trong toàn giai đoạn nhưng giảm dần từ 70% → 53%, phản ánh sự chuyển dịch dần sang sử dụng vốn vay.

Nợ phải trả tăng từ 32% → 49%, thể hiện xu hướng gia tăng đòn bẩy tài chính để mở rộng quy mô.

Tài sản ngắn hạn duy trì ổn định quanh 18–21%, cho thấy quản lý vốn lưu động hiệu quả.

Tài sản dài hạn giảm nhẹ (~11% → 9%), chứng tỏ Vinamilk tập trung tối ưu tài sản hiện có thay vì đầu tư mới ồ ạt.

👉 Tổng kết: Biểu đồ này cho thấy Vinamilk đang dịch chuyển từ cấu trúc “vốn chủ chiếm ưu thế” sang “vốn cân bằng giữa chủ sở hữu và nợ vay”, giúp tăng hiệu quả sử dụng vốn nhưng vẫn giữ an toàn tài chính.