CHƯƠNG 1: PHÂN TÍCH DỮ LIỆU LỰC LƯỢNG LAO ĐỘNG HOA KỲ (2023)

1. Thông tin tổng quan về bộ dữ liệu

1.1. Giới thiệu tổng quan

Chương 1 của tiểu luận này tập trung vào việc thực hiện phân tích dữ liệu chuyên sâu (Exploratory Data Analysis - EDA) trên bộ dữ liệu lực lượng lao động Hoa Kỳ (2023). Mục tiêu chính của chương là xây dựng một nền tảng hiểu biết vững chắc về các đặc điểm của lực lượng lao động trong mẫu khảo sát. Các phân tích được thực hiện nhằm trả lời ba câu hỏi cốt lõi:

- Họ là ai? Phân tích các đặc điểm nhân khẩu học cơ bản (tuổi, giới tính, học vấn, hôn nhân).

- Họ làm gì? Đánh giá tình trạng việc làm (Employed, Unemployed, Not in labor force) và phân bố ngành nghề.

- Họ kiếm được bao nhiêu? Khám phá phân phối thu nhập và xác định các yếu tố nhân khẩu học, xã hội có mối tương quan mạnh mẽ nhất với mức lương.

Quá trình phân tích được thực hiện một cách tuần tự, bao gồm các bước từ nạp dữ liệu thô, làm sạch, tạo biến phân tích đến việc áp dụng các kỹ thuật thống kê mô tả và trực quan hóa để rút ra các số liệu có ý nghĩa, làm cơ sở cho mọi đánh giá và kết luận.

1.2. Nạp và khám phá dữ liệu ban đầu

1.2.1. Đọc dữ liệu

df_raw <- read_excel("data_2023_hoaKy.xlsx")

1.2.2. Chuẩn hóa tên cột

df <- df_raw %>% clean_names()

Quá trình phân tích được khởi đầu bằng việc nạp và khám phá sơ bộ bộ dữ liệu, giai đoạn nền tảng nhằm định hình sự hiểu biết ban đầu về cấu trúc và đặc điểm của dữ liệu. Về mặt kỹ thuật, hàm read_excel() được sử dụng để nhập dữ liệu. Ngay sau đó, hàm clean_names() từ gói janitor được áp dụng. Đây là một thao tác quan trọng giúp chuẩn hóa tên các cột về định dạng chữ thường và sử dụng dấu gạch dưới qua đó đảm bảo tính nhất quán và giảm thiểu lỗi cú pháp trong các câu lệnh về sau.

1.2.3. Xem một mẫu dữ liệu ngẫu nhiên

df %>% sample_n(6) %>% flextable() %>% set_caption("6 dòng dữ liệu ngẫu nhiên từ bộ dữ liệu") %>% autofit()
6 dòng dữ liệu ngẫu nhiên từ bộ dữ liệu

age

sex

race

marst

empstat

occ

ind

educ

fullpart

incwage

80

Female

White

Separated

Not in labor force

Not in universe

N/A (not applicable)

Some college

Not working

0

33

Female

White

Married, spouse present

Employed

Managers, all other

Machinery manufacturing, n.e.c. or not specified

Graduate degree

Full-time

130,000

66

Male

American Indian

Single

Not in labor force

Not in universe

N/A (not applicable)

Some college

Not working

0

8

Male

White

Single

NIU

Not in universe

N/A (not applicable)

Elementary school

Not working

0

85

Male

White

Separated

Not in labor force

Not in universe

N/A (not applicable)

Some high school

Not working

0

20

Male

Asian

Widowed

Not in labor force

Not in universe

N/A (not applicable)

Some college

Not working

0

Việc xem xét trực tiếp một vài dòng dữ liệu ngẫu nhiên là một thao tác trực quan cần thiết để hình dung rõ hơn về nội dung và định dạng thực tế của các biến. Về mặt kỹ thuật, một chuỗi lệnh sử dụng toán tử pipe (%>%) đã được thực thi: một mẫu gồm 6 quan sát đã được trích xuất ngẫu nhiên bằng hàm sample_n(6), phương pháp lấy mẫu ngẫu nhiên này được ưu tiên hơn so với việc chỉ kiểm tra các quan sát đầu tiên, nhằm giảm thiểu rủi ro sai lệch chọn mẫu có thể phát sinh nếu dữ liệu đã được sắp xếp trước theo một biến cụ thể nào đó (ví dụ: sắp xếp theo tuổi hoặc theo khu vực địa lý). Kết quả sau đó được chuyển qua hàm flextable() để tạo ra một bảng biểu có định dạng chuyên nghiệp. Từ bảng kết quả, ta có thể rút ra những nhận định ban đầu giá trị. Trước hết, mẫu dữ liệu cho thấy sự đa dạng về các đặc điểm nhân khẩu học với sự hiện diện của cả nam và nữ, độ tuổi trải dài đa dạng và nhiều chủng tộc, tình trạng hôn nhân khác nhau. Quan trọng hơn, thao tác này giúp phát hiện trực quan các giá trị khuyết được mã hóa dưới dạng chuỗi ký tự như “Not in universe”, “N/A (not applicable)” và “NIU”. Việc nhìn thấy trực tiếp các giá trị này đã củng cố tầm quan trọng của việc phải xử lý chúng một cách cẩn thận ở bước làm sạch dữ liệu. Cuối cùng, mẫu ngẫu nhiên cũng gợi ý về các mối liên hệ tiềm năng cần được khám phá sâu hơn, ví dụ như mối quan hệ giữa độ tuổi, nghề nghiệp và mức thu nhập. Thao tác này không chỉ đơn thuần hiển thị dữ liệu mà còn là một bước khám phá quan trọng, giúp xác nhận các đặc điểm cấu trúc và phát hiện sớm các vấn đề tiềm ẩn trong dữ liệu thô.

1.2.4. Xem kích thước bộ dữ liệu

dim(df)
[1] 146133     10

Kết quả từ hàm dim(df) cho thấy bộ dữ liệu bao gồm 146133 quan sát và 10 biến, thể hiện một quy mô mẫu lớn, đủ để các suy luận thống kê có được độ tin cậy cần thiết.

1.2.5. Xem cấu trúc dữ liệu

str(df)
tibble [146,133 × 10] (S3: tbl_df/tbl/data.frame)
 $ age     : num [1:146133] 66 68 52 51 78 65 68 74 74 76 ...
 $ sex     : chr [1:146133] "Female" "Female" "Female" "Male" ...
 $ race    : chr [1:146133] "White" "White" "White" "White" ...
 $ marst   : chr [1:146133] "Separated" "Separated" "Married, spouse present" "Married, spouse present" ...
 $ empstat : chr [1:146133] "Not in labor force" "Not in labor force" "Not in labor force" "Employed" ...
 $ occ     : chr [1:146133] "Not in universe" "Not in universe" "Not in universe" "Retail salespersons" ...
 $ ind     : chr [1:146133] "N/A (not applicable)" "N/A (not applicable)" "N/A (not applicable)" "Auto parts, accessories, and tire stores" ...
 $ educ    : chr [1:146133] "Some college" "Some college" "Some college" "Some college" ...
 $ fullpart: chr [1:146133] "Not working" "Not working" "Not working" "Full-time" ...
 $ incwage : num [1:146133] 0 0 0 42000 0 55000 52000 0 0 0 ...

Để có một cái nhìn tổng quan về cấu trúc bên trong và các kiểu dữ liệu mà R đã tự động gán, hàm str() đã được sử dụng. Kết quả trả về 1 lần nữa xác nhận kích thước của bộ dữ liệu gồm 146133 quan sát và 10 biến, cung cấp thêm hai nhận định quan trọng. Thứ nhất, các biến định lượng như age và incwage đã được nhận diện chính xác dưới dạng số (num), sẵn sàng cho các phép tính toán học. Thứ hai, hầu hết các biến định tính dùng để phân loại như sex, race và marst lại đang được lưu trữ dưới dạng ký tự (chr). Việc nhận diện kiểu dữ liệu chr này là rất quan trọng vì nó chỉ ra sự cần thiết phải chuyển đổi chúng sang định dạng factor ở bước tiếp theo để tối ưu hóa cho các phân tích thống kê và trực quan hóa. Đồng thời, việc xem xét các giá trị mẫu trong output cũng củng cố phát hiện về sự tồn tại của các giá trị khuyết được mã hóa dưới dạng chuỗi ký tự như “not in universe” hay “n/a (not applicable)”. Từ đó định hướng cho các thao tác làm sạch dữ liệu cần thiết.

1.2.6. Tính các chỉ số thống kê mô tả chính cho biến định lượng

df %>% select(where(is.numeric)) %>% summarise(mean = mean(age), median = median(age),  std_dev = sd(age), min = min(age), max = max(age), mean_income = mean(incwage), median_income = median(incwage), stv_dev_income = sd(incwage), min = min(incwage), max = max(incwage)) %>% pivot_longer(everything(), names_to = "Chi_so_thong_ke", values_to = "Gia_tri") %>% kable(caption = "Các chỉ số thống kê mô tả chính", digits = 2) %>% kable_styling(full_width = F)
Các chỉ số thống kê mô tả chính
Chi_so_thong_ke Gia_tri
mean 38.73
median 38.00
std_dev 23.32
min 0.00
max 1549999.00
mean_income 31456.44
median_income 0.00
stv_dev_income 67360.93

Để có cái nhìn định lượng ban đầu về các biến số, một bảng thống kê mô tả đã được tạo ra. Về mặt kỹ thuật, hàm select(where(is.numeric)) được áp dụng để chỉ chọn các cột có kiểu dữ liệu là số, sau đó hàm summarise() tính toán các chỉ số thống kê trung tâm và phân tán như: giá trị trung bình (mean), trung vị (median) và độ lệch chuẩn (standard deviation). Cuối cùng, pivot_longer() được dùng để tái cấu trúc bảng kết quả về định dạng dài, giúp việc trình bày trở nên gọn gàng và dễ đọc hơn. Từ bảng kết quả, ta có thể rút ra một số nhận định sơ bộ quan trọng. Độ tuổi trung bình (mean) của mẫu là khoảng 38.7 tuổi và tuổi trung vị (median) là 38.0 tuổi. Hai giá trị này rất gần nhau, gợi ý rằng phân phối tuổi trong mẫu là tương đối đối xứng. Ngược lại, có một sự khác biệt rất lớn giữa thu nhập trung bình (mean_income = 31456.44 usd) và thu nhập trung vị (median_income = 0.00 usd). Giá trị trung vị bằng 0 cho thấy có ít nhất 50% số người trong bộ dữ liệu không có thu nhập từ lương (incwage). Sự chênh lệch lớn này cũng cho thấy phân phối thu nhập bị lệch phải rất mạnh với một số ít người có thu nhập rất cao đã kéo giá trị trung bình lên đáng kể.

1.2.7. Kiểm tra tổng số giá trị bị thiếu (NA)

sum(is.na(df))
[1] 0

Một bước kiểm tra chất lượng dữ liệu cơ bản là xác định sự tồn tại của các giá trị bị thiếu. Hàm sum(is.na(df)) được sử dụng để đếm tổng số giá trị NA chuẩn trong toàn bộ bộ dữ liệu. Kết quả trả về là 0, điều này có thể tạo ra một ấn tượng ban đầu rằng dữ liệu hoàn toàn đầy đủ. Tuy nhiên, như đã được phát hiện ở các bước khám phá trước, bộ dữ liệu này chứa các giá trị khuyết được mã hóa dưới dạng chuỗi ký tự. Do đó, kết quả bằng 0 này chỉ xác nhận rằng không có giá trị NA ở định dạng chuẩn của R và củng cố thêm sự cần thiết của việc phải xử lý các chuỗi ký tự đặc biệt này ở giai đoạn làm sạch dữ liệu.

1.2.8. Đếm số dòng bị trùng lặp

num_duplicated_rows <- sum(duplicated(df)) 
cat(paste("Tổng số dòng bị trùng lặp hoàn toàn là:", num_duplicated_rows, "\n"))
Tổng số dòng bị trùng lặp hoàn toàn là: 65820 
duplicated_summary <- df %>% group_by(across(everything())) %>% summarise(Frequency = n(), .groups = "drop") %>% arrange(desc(Frequency))

sampled_data <- duplicated_summary %>% sample_n(10) 

sampled_data %>% select(age, sex, race, marst, empstat, occ) %>% flextable() %>% set_caption("10 dòng ngẫu nhiên trong bảng tần suất (Phần 1: Nhân khẩu học và nghề nghiệp)") %>% autofit()
10 dòng ngẫu nhiên trong bảng tần suất (Phần 1: Nhân khẩu học và nghề nghiệp)

age

sex

race

marst

empstat

occ

39

Female

White

Single

Employed

Web developers

62

Female

White

Married, spouse present

Not in labor force

Not in universe

29

Male

Black

Single

Employed

Software developers

62

Male

White

Married, spouse present

Employed

First-line supervisors of construction trades and extraction workers

24

Female

Black

Married, spouse present

Employed

Secretaries and administrative assistants, except legal, medical, and executive

48

Male

Black

Single

Employed

Firefighters

43

Male

White

Married, spouse present

Employed

Carpenters

51

Male

White

Married, spouse present

Employed

Driver/sales workers and truck drivers

44

Male

Black

Single

Employed

Janitors and building cleaners

44

Female

White

Married, spouse present

Employed

Advertising and promotions managers

sampled_data %>% select(ind, educ, fullpart, incwage, Frequency) %>% flextable() %>% set_caption("10 dòng ngẫu nhiên trong bảng tần suất (Phần 2: Ngành, học vấn và thu nhập)") %>% autofit()
10 dòng ngẫu nhiên trong bảng tần suất (Phần 2: Ngành, học vấn và thu nhập)

ind

educ

fullpart

incwage

Frequency

Computer systems design and related services

Graduate degree

Full-time

0

1

N/A (not applicable)

Some college

Part-time

30,000

1

Computer systems design and related services

Graduate degree

Full-time

40,000

1

Construction

Some college

Full-time

0

1

Colleges and universities, including junior colleges

Some college

Not working

0

1

Justice, public order, and safety activities

Some college

Full-time

60,000

1

Construction

Graduate degree

Full-time

48,000

1

Restaurants and other food services

Associate degree

Full-time

46,000

1

Services to buildings and dwellings (except cleaning during construction and immediately after construction)

Some college

Full-time

45,000

1

Advertising and related services

Graduate degree

Full-time

50,000

1

#Tần suất trùng lặp cao nhất và thấp nhất
max_freq <- max(duplicated_summary$Frequency)
min_freq <- min(duplicated_summary$Frequency)

cat(paste("Tần suất trùng lặp cao nhất là:", max_freq, "\n"))
Tần suất trùng lặp cao nhất là: 906 
cat(paste("Tần suất trùng lặp thấp nhất là:", min_freq, "\n"))
Tần suất trùng lặp thấp nhất là: 1 

Một khía cạnh khác của việc kiểm tra chất lượng dữ liệu là xác định sự tồn tại của các bản ghi trùng lặp. Hàm sum(duplicated(df)) được sử dụng để đếm số dòng có nội dung giống hệt nhau. Kết quả cho thấy một con số đáng kể có đến 65820 dòng bị trùng lặp. Để hiểu rõ hơn bản chất của sự trùng lặp này, một bảng tần suất (duplicated_summary) đã được tạo ra bằng cách nhóm tất cả các biến và đếm số lần xuất hiện của mỗi tổ hợp giá trị duy nhất. Kết quả cho thấy tần suất trùng lặp cao nhất chỉ là 906 lần và tần suất thấp nhất là 1, nghĩa là có rất nhiều dòng dữ liệu là duy nhất. Trong bối cảnh dữ liệu khảo sát xã hội với các biến định tính có số lượng giá trị giới hạn và nhiều quan sát có cùng đặc điểm (ví dụ: không có thu nhập), sự trùng lặp này được đánh giá là sự trùng hợp tự nhiên của các đặc điểm chứ không phải lỗi nhập liệu. Do đó, quyết định giữ lại toàn bộ dữ liệu được đưa ra để bảo toàn tính toàn vẹn và cấu trúc của mẫu khảo sát gốc.

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

colnames(df)
 [1] "age"      "sex"      "race"     "marst"    "empstat"  "occ"     
 [7] "ind"      "educ"     "fullpart" "incwage" 

Hàm colnames(df) được sử dụng để trích xuất và hiển thị một vector chứa tên của tất cả các biến trong bộ dữ liệu. Thao tác này cung cấp một danh sách tham khảo nhanh chóng, giúp xác nhận rằng quá trình chuẩn hóa tên biến ở các bước trước đã thành công và cung cấp tên gọi chính xác để sử dụng trong các câu lệnh phân tích sau này.

1.2.10. Đếm số lượng giá trị duy nhất mỗi cột

sapply(df, function(x) length(unique(x)))
     age      sex     race    marst  empstat      occ      ind     educ 
      82        2        6        6        4      527      264        5 
fullpart  incwage 
       3     3537 

Để hiểu rõ hơn về đặc tính của từng biến, hàm sapply() đã được áp dụng để đếm số lượng giá trị duy nhất trong mỗi cột. Về mặt kỹ thuật, sapply() lặp qua từng cột của dataframe df và áp dụng hàm length(unique(x)) lên nó. Kết quả cho thấy các biến như sex (2 giá trị), race (6 giá trị), empstat (4 giá trị) có số lượng giá trị duy nhất thấp, khẳng định chúng là các biến định tính (phân loại). Ngược lại, các biến như occ (nghề nghiệp, 527 giá trị) và incwage (thu nhập, 3537 giá trị) có độ đa dạng rất cao. Con số này giúp định hướng cho các bước xử lý tiếp theo.

1.3. Làm sạch dữ liệu và tạo biến phân tích

Trong phần này, các phép biến đổi được thực hiện và tạo ra các biến mới từ dữ liệu gốc để phục vụ cho các câu hỏi phân tích sâu hơn. Sau mỗi thao tác, một phần kết quả sẽ được hiển thị để minh họa cho sự thay đổi.

1.3.1. Chuyển đổi kiểu dữ liệu character sang factor

df <- df %>% mutate(across(where(is.character), as.factor))
# Hiển thị kiểu dữ liệu của một vài cột để kiểm tra
glimpse(df %>% select(sex, race, marst))
Rows: 146,133
Columns: 3
$ sex   <fct> Female, Female, Female, Male, Female, Male, Female, Female, Male…
$ race  <fct> White, White, White, White, White, White, White, White, White, W…
$ marst <fct> "Separated", "Separated", "Married, spouse present", "Married, s…

Dựa trên phát hiện từ bước khám phá dữ liệu, chuyển đổi các biến định tính từ kiểu ký tự (chr) sang kiểu factor là một thao tác tiền xử lý quan trọng đã được thực hiện. Câu lệnh mutate(across(where(is.character), as.factor)) được áp dụng để tự động tìm tất cả các cột có kiểu dữ liệu là ký tự và ép kiểu chúng thành factor. Kết quả từ hàm glimpse() đã xác nhận sự thay đổi này, khi các biến sex, race, marst giờ đây được hiển thị với kiểu . Việc chuyển đổi này là cần thiết vì factor là kiểu dữ liệu được R tối ưu hóa cho các biến phân loại, giúp các hàm thống kê và trực quan hóa sau này có thể nhận diện và xử lý các nhóm một cách chính xác, đồng thời cải thiện hiệu suất tính toán.

1.3.2. Mã hóa biến học vấn

df <- df %>% mutate(educ_group = case_when(educ %in% c("Less than high school", "Elementary school") ~ "Thấp", educ %in% c("High school graduate", "Some college") ~ "Trung bình", educ %in% c("Associate degree", "Bachelor's degree", "Graduate degree") ~ "Cao", TRUE ~ "Không xác định"))
# Hiển thị cột mới và cột gốc để so sánh
df %>% select(educ, educ_group) %>% head() %>% flextable() %>% set_caption("So sánh biến 'educ' gốc và 'educ_group' mới") %>% autofit()
So sánh biến 'educ' gốc và 'educ_group' mới

educ

educ_group

Some college

Trung bình

Some college

Trung bình

Some college

Trung bình

Some college

Trung bình

Some college

Trung bình

Some college

Trung bình

Để phục vụ cho các phân tích so sánh một cách hiệu quả, biến educ với nhiều cấp độ chi tiết đã được mã hóa thành một biến nhóm mới là educ_group. Hàm case_when() được sử dụng để tạo ra các điều kiện logic, gom các giá trị học vấn gốc thành ba nhóm chính có ý nghĩa kinh tế - xã hội rõ ràng hơn: “thấp”, “trung bình” và “cao”. Việc tạo ra một biến nhóm mới thay vì ghi đè lên biến cũ giúp bảo toàn dữ liệu gốc. Bảng so sánh kết quả cho thấymột vài giá trị “some college” từ biến educ đã được ánh xạ chính xác thành giá trị “trung bình” trong biến educ_group, xác nhận rằng quá trình mã hóa đã thành công. Thao tác này giúp đơn giản hóa việc phân tích và làm cho các xu hướng liên quan đến học vấn trở nên rõ ràng và dễ diễn giải hơn.

1.3.3. Sắp xếp thứ tự biến học vấn

df$educ_group <- factor(df$educ_group, levels = c("Thấp", "Trung bình", "Cao"))
# Hiển thị tần số của các nhóm tuổi mới tạo
levels(df$educ_group)
[1] "Thấp"       "Trung bình" "Cao"       

Mặc định, R sẽ sắp xếp các cấp độ của một biến factor theo thứ tự alphabet. Để thay đổi điều này, hàm factor() được tái sử dụng với tham số levels được chỉ định rõ ràng là c(“thấp”, “trung bình”, “cao”). Thao tác này ghi đè lên thứ tự mặc định và thiết lập một trật tự logic, có thứ bậc cho biến học vấn. Kết quả từ hàm levels() sau đó đã xác nhận rằng trật tự mới đã thành công. Điều này đảm bảo rằng tất cả các bảng biểu và biểu đồ được tạo ra ở các phần sau sẽ hiển thị các nhóm học vấn theo đúng trình tự tăng dần, giúp việc so sánh và diễn giải kết quả trở nên trực quan và chính xác hơn.

1.3.4 Mã hóa biến tuổi

df <- df %>% mutate(age_group = cut(age, breaks = c(0, 18, 30, 45, 60, Inf), labels = c("Dưới 18", "18-30", "31-45", "46-60", "Trên 60"), right = FALSE))
# Hiển thị tần số của các nhóm tuổi mới tạo
table(df$age_group) %>% as.data.frame() %>% rename("Nhóm Tuổi" = Var1, "Số lượng" = Freq) %>% flextable() %>% set_caption("Tần số của các nhóm tuổi") %>% autofit()
Tần số của các nhóm tuổi

Nhóm Tuổi

Số lượng

Dưới 18

36,125

18-30

19,768

31-45

30,005

46-60

26,390

Trên 60

33,845

Tương tự như biến học vấn, biến tuổi (age) cũng được mã hóa thành biến nhóm age_group.Hàm cut() được sử dụng để phân chia biến tuổi liên tục thành các khoảng rời rạc, tương ứng với các giai đoạn khác nhau trong cuộc đời và sự nghiệp. Tham số breaks định nghĩa các điểm cắt và labels gán nhãn cho từng khoảng. Bảng tần số kết quả cho thấy sự phân bổ của các quan sát vào từng nhóm tuổi. Đáng chú ý, nhóm “dưới 18” chiếm số lượng lớn nhất (36,125), phản ánh một phần đáng kể của mẫu khảo sát là những người chưa đến tuổi lao động. Các nhóm tuổi lao động chính như “31-45” và “trên 60” cũng có số lượng quan sát cao. Việc tạo ra biến nhóm cho phép thực hiện các phân tích so sánh và tổng hợp dữ liệu giúp làm nổi bật các xu hướng nhân khẩu học một cách rõ ràng hơn.

1.3.5. Tạo tập dữ liệu con (có thu nhập)

df_with_income <- df %>% filter(incwage > 0)
# Hiển thị kích thước của dataframe mới
cat(paste("Số người có thu nhập > 0 là:", nrow(df_with_income)))
Số người có thu nhập > 0 là: 69148

Tập dữ liệu con tên là df_with_income đã được tạo ra phục vụ cho các phân tích chuyên sâu về thu nhập. Hàm filter(incwage > 0) được sử dụng để lọc và chỉ giữ lại những quan sát có giá trị thu nhập từ lương lớn hơn 0. Kết quả từ nrow() cho thấy có 69,148 cá nhân trong tổng số 146,133 quan sát là có thu nhập. Việc tạo ra tập dữ liệu con này giúp đảm bảo các phân tích về sau chỉ tập trung vào đúng đối tượng những người đang có thu nhập, từ đó tránh được sự sai lệch do việc bao gồm những người có thu nhập bằng 0.

1.3.6. Tạo tập dữ liệu con (có việc làm)

df_employed <- df %>% filter(empstat == "Employed")
# Hiển thị kích thước của dataframe mới
cat(paste("Số người đang có việc làm là:", nrow(df_employed)))
Số người đang có việc làm là: 69160

Tương tự việc tạo tập dữ liệu cho người có thu nhập, một tập dữ liệu con khác là df_employed đã được tạo ra để phục vụ các phân tích liên quan đến nhóm dân số đang có việc làm. Về mặt kỹ thuật, hàm filter(empstat == “employed”) được sử dụng để trích xuất tất cả các quan sát có tình trạng việc làm là “employed”. Kết quả cho thấy có 69,160 người trong mẫu đang có việc làm.

1.3.7. Tạo biến nhị phân có thu nhập

df <- df %>% mutate(has_income = ifelse(incwage > 0, "Có thu nhập", "Không có thu nhập"))
# Hiển thị tần số của biến mới
table(df$has_income) %>% as.data.frame() %>% rename("Trạng thái" = Var1, "Số lượng" = Freq) %>% flextable() %>% set_caption("Tần số biến có thu nhập") %>% autofit()
Tần số biến có thu nhập

Trạng thái

Số lượng

Có thu nhập

69,148

Không có thu nhập

76,985

Để đơn giản hóa việc phân loại các quan sát dựa trên tình trạng thu nhập, biến nhị phân mới tên là has_income đã được tạo ra. Hàm ifelse() được sử dụng để kiểm tra một điều kiện logic: nếu giá trị incwage lớn hơn 0, biến mới sẽ nhận giá trị “có thu nhập”; ngược lại, nó sẽ nhận giá trị “không có thu nhập”. Bảng tần số cho thấy sự phân bổ của mẫu khảo sát thành hai nhóm với 69,148 người có thu nhập và 76,985 người không có thu nhập.

1.3.8. Lấy danh sách Top 5 nghề nghiệp phổ biến

top_5_occ <- df %>% count(occ, sort = TRUE) %>% head(5) %>% pull(occ)
# In ra danh sách top 5 nghề nghiệp
top_5_occ
[1] Not in universe                       
[2] Managers, all other                   
[3] Elementary and middle school teachers 
[4] Driver/sales workers and truck drivers
[5] Registered nurses                     
527 Levels: Accountants and auditors Actors Actuaries ... Writers and authors

Vector chứa 5 nghề phổ biến nhất đã được tạo ra để xử lý biến nghề nghiệp (occ) vốn có độ đa dạng cao. Kỹ thuật này sử dụng chuỗi lệnh count(), head() và pull() để đếm tần suất, chọn ra 5 nghề đứng đầu và lưu tên của chúng vào biến top_5_occ. Danh sách này, bao gồm các nghề như quản lý, giáo viên và y tá, sẽ được sử dụng như một bộ lọc hiệu quả trong các phân tích và trực quan hóa tập trung ở các phần sau.

1.3.9. Chọn lọc các biến quan trọng

df_subset <- df %>% select(age, sex, educ_group, empstat, incwage)
# Hiển thị 6 dòng đầu của dataframe con mới
head(df_subset) %>% flextable() %>% set_caption("6 dòng đầu của dataframe con 'df_subset'") %>% autofit()
6 dòng đầu của dataframe con 'df_subset'

age

sex

educ_group

empstat

incwage

66

Female

Trung bình

Not in labor force

0

68

Female

Trung bình

Not in labor force

0

52

Female

Trung bình

Not in labor force

0

51

Male

Trung bình

Employed

42,000

78

Female

Trung bình

Not in labor force

0

65

Male

Trung bình

Not in labor force

55,000

Hàm select() được sử dụng để tạo ra một dataframe con (df_subset) chỉ chứa các biến được xác định là trọng tâm cho các câu hỏi phân tích chính của tiểu luận. Bảng kết quả hiển thị 6 dòng đầu của dataframe mới này, xác nhận rằng chỉ có các biến age, sex, educ_group, empstat và incwage được giữ lại. Việc tạo ra tập dữ liệu con chuyên biệt này không chỉ là một thao tác kỹ thuật để có một phiên bản dữ liệu gọn nhẹ hơn, mà còn là một bước thể hiện định hướng phân tích, tập trung vào việc khám phá mối quan hệ giữa các đặc điểm nhân khẩu học cốt lõi với tình trạng việc làm và thu nhập.

1.3.10. Biến đổi Logarit cho thu nhập

df_with_income <- df_with_income %>% mutate(log_income = log1p(incwage))
# Hiển thị cột thu nhập gốc và cột thu nhập logarit hóa để so sánh
df_with_income %>% select(incwage, log_income) %>% head() %>%  flextable() %>% colformat_double(digits = 2) %>% set_caption("So sánh 'incwage' và 'log_income'") %>% autofit()
So sánh 'incwage' và 'log_income'

incwage

log_income

42,000.00

10.65

55,000.00

10.92

52,000.00

10.86

22,000.00

10.00

45,000.00

10.71

70,000.00

11.16

Để xử lý tính chất phân phối lệch phải của dữ liệu thu nhập, phép biến đổi logarit được thực hiện bằng hàm log1p() để tạo ra biến log_income. Bảng kết quả so sánh đã minh họa rõ sự thay đổi này: một giá trị thu nhập lớn như 42,000 được chuyển đổi thành 10.65, trong khi 70,000 trở thành 11.16. Kết quả này cho thấy phép biến đổi đã thành công trong việc nén khoảng giá trị của biến thu nhập, đưa các giá trị về một thang đo nhỏ và ít chênh lệch hơn. Ý nghĩa của kỹ thuật này là để giảm thiểu tác động của các giá trị ngoại lai (những người có thu nhập rất cao), giúp phân phối của biến trở nên đối xứng hơn, một điều kiện thuận lợi và đôi khi là bắt buộc cho nhiều mô hình phân tích thống kê.

2. Phân tích đặc điểm nhân khẩu học

Để hiểu về lực lượng lao động, trước tiên chúng ta cần trả lời câu hỏi “Họ là ai?”. Mục này sẽ vẽ nên chân dung nhân khẩu học của mẫu dữ liệu, tập trung vào các đặc điểm cơ bản nhất: độ tuổi, cấu trúc thế hệ, giới tính, và các đặc điểm xã hội khác. Việc hiểu rõ những đặc điểm này là nền tảng để phân tích sâu hơn về việc làm và thu nhập ở các mục sau.

2.1. Phân tích về tuổi và cấu trúc thế hệ

Tuổi là một trong những yếu tố nhân khẩu học quan trọng nhất, phản ánh kinh nghiệm, giai đoạn sự nghiệp và thế hệ của người lao động. Phần này sẽ khám phá phân bố độ tuổi tổng thể và xem xét mối liên hệ của nó với trình độ học vấn và giới tính.

# Thống kê mô tả cho tuổi
df %>% summarise("Tuổi trung bình" = mean(age), "Tuổi trung vị" = median(age), "Độ lệch chuẩn" = sd(age), "Tuổi nhỏ nhất" = min(age), "Tuổi lớn nhất" = max(age)) %>% pivot_longer(everything(), names_to = "Chỉ số", values_to = "Giá trị") %>% flextable() %>% colformat_double(digits = 1) %>% set_caption("Bảng 1: Thống kê mô tả về tuổi") %>% autofit()
Bảng 1: Thống kê mô tả về tuổi

Chỉ số

Giá trị

Tuổi trung bình

38.7

Tuổi trung vị

38.0

Độ lệch chuẩn

23.3

Tuổi nhỏ nhất

0.0

Tuổi lớn nhất

85.0

Bảng 1 tóm tắt các chỉ số thống kê mô tả chính của biến tuổi được tính toán thông qua hàm summarise() và trình bày một cách rõ ràng bằng flextable(). Kết quả cho thấy một điểm đáng chú ý: tuổi trung bình của mẫu là 38.7 tuổi, gần như tương đồng với tuổi trung vị là 38.0 tuổi. Khoảng cách gần nhau giữa hai chỉ số đo lường trung tâm này hàm ý rằng phân phối tuổi trong mẫu khảo sát là tương đối đối xứng, không bị lệch nhiều về phía các nhóm tuổi trẻ hay già. Khoảng tuổi rộng từ 0 đến 85 cũng cho thấy tính bao quát của bộ dữ liệu, bao gồm cả những người chưa đến và đã qua tuổi lao động, cung cấp một bối cảnh toàn diện cho các phân tích về sau.

# Biểu đồ 1: Phân phối tuổi của lực lượng lao động
ggplot(df, aes(x = age)) + geom_histogram(binwidth = 5, fill = "skyblue", color = "white") + geom_vline(aes(xintercept = mean(age)), color = "red", linetype = "dashed", size = 1) + labs(title = "Biểu đồ 1: Phân phối tuổi của lực lượng lao động", subtitle = "Đường nét đứt màu đỏ thể hiện tuổi trung bình", x = "Tuổi", y = "Số lượng") + theme_minimal()

Để trực quan hóa sự phân bố của biến tuổi, một biểu đồ tần suất (histogram) đã được xây dựng bằng hàm geom_histogram() từ gói ggplot2. Biểu đồ này củng cố cho nhận định từ bảng thống kê mô tả trước đó, cho thấy một phân phối tương đối đối xứng với hai đỉnh nhỏ: một ở nhóm tuổi rất trẻ (dưới 20) và một ở giai đoạn lao động chính (khoảng 30-55 tuổi). Đường nét đứt màu đỏ, được thêm vào bằng geom_vline(), biểu diễn giá trị tuổi trung bình (38.7 tuổi), nằm gần trung tâm của phân phối. Việc biểu đồ có số lượng quan sát đáng kể ở cả hai phía của độ tuổi lao động cho thấy sự đa dạng của mẫu, bao gồm cả những người đang đi học, đang làm việc và đã nghỉ hưu, cung cấp một bối cảnh rộng cho các phân tích sâu hơn về lực lượng lao động.

# Bảng tần số các nhóm tuổi
table(df$age_group) %>% as.data.frame() %>% rename("Nhóm Tuổi" = Var1, "Số lượng" = Freq) %>% flextable() %>% set_caption("Bảng 2: Tần suất các nhóm tuổi") %>% autofit()
Bảng 2: Tần suất các nhóm tuổi

Nhóm Tuổi

Số lượng

Dưới 18

36,125

18-30

19,768

31-45

30,005

46-60

26,390

Trên 60

33,845

Câu hỏi đặt ra là các cá nhân trong mẫu khảo sát phân bổ như thế nào theo các giai đoạn cuộc đời khác nhau. Để trả lời câu hỏi này, một bảng tần suất cho biến age_group đã được tạo ra bằng hàm table(). Kết quả trong bảng 2 cho thấy một sự phân bổ không đồng đều. Nhóm “dưới 18” có số lượng đông nhất với 36,125 quan sát, điều này có thể lý giải vì nhóm này bao gồm cả những người đang đi học và chưa tham gia lực lượng lao động. Nhóm tuổi lao động nòng cốt “31-45” và nhóm “trên 60” cũng chiếm tỷ trọng đáng kể.

# Biểu đồ 2: Số lượng người theo từng nhóm tuổi
ggplot(df, aes(x = age_group)) + geom_bar(fill = "steelblue") + geom_text(stat='count', aes(label=..count..), vjust=-0.5) + labs(title = "Biểu đồ 2: Số lượng người theo từng nhóm tuổi", x = "Nhóm tuổi", y = "Số lượng") + theme_minimal()

Biểu đồ 2 cung cấp một cái nhìn trực quan về sự phân bổ số lượng cá nhân theo từng nhóm tuổi đã được mã hóa. Bằng việc sử dụng hình học cột (geom_bar), chiều cao của mỗi cột đại diện cho tổng số người trong nhóm tương ứng với con số cụ thể được ghi rõ trên đỉnh cột nhờ lớp geom_text(). Biểu đồ cho thấy một sự phân bổ không đồng đều, với nhóm “dưới 18” chiếm số lượng cao nhất (36,125 người). Đáng chú ý, nhóm tuổi trẻ nhất trong lực lượng lao động (“18-30”) lại có số lượng thấp nhất, trong khi các nhóm tuổi trung niên và lớn tuổi hơn lại có quy mô lớn hơn. Sự trực quan hóa này giúp nhanh chóng nắm bắt được cấu trúc nhân khẩu học theo thế hệ của mẫu khảo sát.

# Biểu đồ 3: Phân phối tuổi theo từng nhóm học vấn
ggplot(df, aes(x = educ_group, y = age, fill = educ_group)) + geom_violin(trim = FALSE) + stat_summary(fun = "median", geom = "point", size = 3, color = "white") + theme(legend.position = "none") + labs(title = "Biểu đồ 3: Phân phối tuổi theo từng nhóm học vấn", subtitle = "Chấm trắng thể hiện độ tuổi trung vị của mỗi nhóm", x = "Nhóm học vấn", y = "Tuổi")

Biểu đồ 3 khám phá sâu hơn về mối liên hệ giữa tuổi và trình độ học vấn bằng cách sử dụng biểu đồ violin. Về mặt kỹ thuật, geom_violin() được dùng để vẽ hình dạng phân phối mật độ của tuổi cho mỗi nhóm học vấn, với phần thân violin rộng hơn ở những độ tuổi có nhiều người hơn. Lớp stat_summary() được thêm vào để đánh dấu tuổi trung vị của mỗi nhóm bằng một chấm trắng. Kết quả trực quan cho thấy một xu hướng rõ ràng: tuổi trung vị tăng dần theo trình độ học vấn, từ nhóm “thấp” đến nhóm “cao”. Hình dạng của violin cũng cho thấy phân phối tuổi của nhóm “thấp” tập trung ở độ tuổi trẻ, trong khi nhóm “cao” có phân phối rộng hơn và nghiêng về phía tuổi lớn hơn. Điều này là một kết quả hợp lý, phản ánh thực tế rằng những người có trình độ học vấn cao hơn cần nhiều thời gian hơn để hoàn thành quá trình học tập của mình.

# Biểu đồ 4: Số lượng người theo nhóm tuổi và giới tính
ggplot(df, aes(x = age_group, fill = sex)) + geom_bar(position = "dodge") + labs(title = "Biểu đồ 4: Phân bổ nhân khẩu học theo tuổi và giới tính", x = "Nhóm tuổi", y = "Số lượng", fill = "Giới tính") + theme_minimal() + scale_fill_brewer(palette = "Pastel1")

Để khám phá sự tương tác giữa hai biến nhân khẩu học quan trọng là tuổi và giới tính, một biểu đồ cột nhóm đã được xây dựng bằng geom_bar(). Bằng cách đặt các cột đại diện cho nam và nữ cạnh nhau (position = “dodge”) trong mỗi nhóm tuổi, biểu đồ 4 cho phép so sánh trực quan số lượng của hai giới. Kết quả cho thấy một sự cân bằng tương đối về giới tính trong hầu hết các giai đoạn cuộc đời được khảo sát. Điều này củng cố thêm độ tin cậy của mẫu dữ liệu, cho thấy nó không bị thiên lệch quá nhiều về một giới tính cụ thể nào. Một quan sát thú vị là ở nhóm tuổi cao nhất (“trên 60”), số lượng nữ giới có phần nhỉnh hơn, phù hợp với xu hướng nhân khẩu học chung về tuổi thọ cao hơn ở nữ giới.

# Tuổi trung bình theo tình trạng việc làm
df %>% group_by(empstat) %>% summarise("Tuổi trung bình" = mean(age)) %>% flextable() %>% colformat_double(digits = 1) %>% set_caption("Bảng 3: Tuổi trung bình theo tình trạng việc làm") %>% autofit()
Bảng 3: Tuổi trung bình theo tình trạng việc làm

empstat

Tuổi trung bình

Employed

42.5

NIU

7.5

Not in labor force

53.5

Unemployed

37.6

Một kết quả đáng chú ý từ bảng 3 là nhóm “unemployed” (thất nghiệp) có độ tuổi trung bình (37.6 tuổi) thấp hơn đáng kể so với nhóm “employed” (có việc làm, 42.5 tuổi). Sự chênh lệch này gợi ý rằng rủi ro thất nghiệp có thể tập trung nhiều hơn ở những người lao động trẻ tuổi. Bảng thống kê này được tạo ra bằng cách nhóm dữ liệu theo biến empstat và sau đó tính tuổi trung bình cho mỗi nhóm bằng hàm summarise(). Ngoài ra, kết quả cũng cho thấy nhóm “not in labor force” có tuổi trung bình cao nhất (53.5), phù hợp với dự đoán rằng nhóm này bao gồm một phần lớn những người đã đến tuổi nghỉ hưu.

# Tuổi trung vị theo tình trạng việc làm
df %>% group_by(empstat) %>% summarise("Tuổi trung vị" = median(age)) %>% flextable() %>% colformat_double(digits = 1) %>% set_caption("Bảng 4: Tuổi trung vị theo tình trạng việc làm") %>% autofit()
Bảng 4: Tuổi trung vị theo tình trạng việc làm

empstat

Tuổi trung vị

Employed

42.0

NIU

8.0

Not in labor force

62.0

Unemployed

35.0

Bảng 4 cung cấp thêm một góc nhìn về mối liên hệ giữa tuổi và tình trạng việc làm thông qua chỉ số trung vị, được tính bằng hàm median(). Tuổi trung vị cho biết mức tuổi mà ở đó một nửa số người trong nhóm trẻ hơn và một nửa già hơn. Kết quả cho thấy một nửa số người thất nghiệp (unemployed) có độ tuổi từ 35 trở xuống, trong khi một nửa số người có việc làm (employed) có độ tuổi từ 42 trở lên. Sự chênh lệch này một lần nữa nhấn mạnh rằng nhóm thất nghiệp có xu hướng trẻ hơn. Việc sử dụng trung vị thay vì trung bình giúp khẳng định rằng kết luận này không bị ảnh hưởng bởi một vài cá nhân rất già hoặc rất trẻ trong từng nhóm.

2.2. Phân tích về các đặc điểm xã hội khác

Bên cạnh tuổi, các yếu tố như giới tính, tình trạng hôn nhân cũng đóng vai trò quan trọng trong việc định hình chân dung lực lượng lao động. Mục này sẽ đi vào chi tiết các yếu tố đó.

# Bảng tỷ lệ Nam/Nữ
prop.table(table(df$sex)) %>% as.data.frame() %>% rename("Giới tính" = Var1, "Tỷ lệ" = Freq) %>% flextable() %>% colformat_double(digits = 3) %>% set_caption("Bảng 5: Tỷ lệ nam/nữ trong mẫu") %>% autofit()
Bảng 5: Tỷ lệ nam/nữ trong mẫu

Giới tính

Tỷ lệ

Female

0.512

Male

0.488

Để phác họa chân dung lực lượng lao động, việc xem xét cấu trúc giới tính là một bước cơ bản. Bảng 5 trình bày tỷ lệ nam và nữ trong mẫu khảo sát, được tính toán bằng cách áp dụng hàm prop.table() lên kết quả tần suất của table(df$sex). Kết quả cho thấy một sự cân bằng đáng kể về giới, với nữ giới chiếm khoảng 51.2% và nam giới chiếm 48.8%. Tỷ lệ gần như 50-50 này là một chỉ báo tốt về tính đại diện của bộ dữ liệu, cho thấy mẫu khảo sát không bị thiên lệch nghiêm trọng về một giới tính cụ thể nào, từ đó làm tăng độ tin cậy cho các phân tích so sánh theo giới ở các phần sau.

# Biểu đồ 5: Tỷ lệ phân bổ theo giới tính
df %>% count(sex) %>% mutate(pct = n / sum(n)) %>% ggplot(aes(x = "", y = pct, fill = sex)) + geom_col(width = 1) + coord_polar(theta = "y") + theme_void() + geom_text(aes(label = percent(pct, accuracy = 0.1)), position = position_stack(vjust = 0.5)) + labs(title = "Biểu đồ 5: Tỷ lệ phân bổ theo giới tính", fill = "Giới tính")

Để trực quan hóa tỷ lệ phân bổ giới tính đã tính ở bảng trước, một biểu đồ tròn đã được tạo ra bằng ggplot2. Dữ liệu đầu tiên được tóm tắt bằng count() và mutate() để tính toán tỷ lệ phần trăm (pct) cho mỗi giới. Sau đó, geom_col() và coord_polar() được kết hợp để vẽ biểu đồ tròn, một kỹ thuật phổ biến để thể hiện cơ cấu. Lớp geom_text() được thêm vào để hiển thị trực tiếp tỷ lệ phần trăm trên mỗi phần của biểu đồ. Biểu đồ 5 xác nhận lại một cách trực quan sự cân bằng về giới tính trong mẫu, với nữ giới chiếm 51.2% và nam giới chiếm 48.8%, không có sự chênh lệch quá lớn giữa hai nhóm.

# Biểu đồ 6: Heatmap giữa tình trạng hôn nhân và nhóm tuổi
df %>% count(marst, age_group) %>% ggplot(aes(x = age_group, y = marst, fill = n)) + geom_tile(color = "white") + scale_fill_viridis_c(labels = comma) + labs(title = "Biểu đồ 6: Mối quan hệ giữa tuổi và tình trạng hôn nhân", x = "Nhóm tuổi", y = "Tình trạng hôn nhân", fill = "Số lượng") + theme_minimal()

Heatmap (Biểu đồ 6) đã được sử dụng để trực quan hóa mối quan hệ giữa tuổi tác và tình trạng hôn nhân. Màu sắc của các ô thể hiện số lượng người trong từng sự kết hợp giữa nhóm tuổi và tình trạng hôn nhân. Quan sát cho thấy sự chuyển biến rõ rệt theo độ tuổi: những người trẻ tuổi thường độc thân, trong khi tỷ lệ người đã kết hôn có xu hướng tăng dần theo thời gian. Ngoài ra, cũng có thể nhận thấy sự hiện diện của các nhóm khác (ví dụ: ly hôn, góa bụa) ở các nhóm tuổi khác nhau, cho thấy sự đa dạng của các giai đoạn trong cuộc sống.

# Bảng chéo tình trạng hôn nhân và giới tính
table(df$sex, df$marst) %>% as.data.frame() %>% rename("Giới tính" = Var1, "Tình trạng Hôn nhân" = Var2, "Số lượng" = Freq) %>% flextable() %>% set_caption("Bảng 6: Bảng chéo giới tính và tình trạng hôn nhân") %>% autofit()
Bảng 6: Bảng chéo giới tính và tình trạng hôn nhân

Giới tính

Tình trạng Hôn nhân

Số lượng

Female

Divorced

6,606

Male

Divorced

4,479

Female

Married, spouse absent

835

Male

Married, spouse absent

807

Female

Married, spouse present

29,338

Male

Married, spouse present

29,298

Female

Separated

4,946

Male

Separated

1,618

Female

Single

31,921

Male

Single

34,302

Female

Widowed

1,188

Male

Widowed

795

Để lượng hóa mối quan hệ giữa giới tính và tình trạng hôn nhân, một bảng chéo (cross-tabulation) đã được tạo ra bằng hàm table(). Bảng 6 trình bày số lượng cụ thể của nam và nữ trong từng nhóm tình trạng hôn nhân. Kết quả cho thấy số lượng nam và nữ ở trạng thái “married, spouse present” (đã kết hôn, có vợ/chồng) là gần như tương đương nhau. Tuy nhiên, có sự khác biệt đáng kể ở các nhóm khác: số lượng nữ giới trong các nhóm “divorced” (ly hôn), “separated” (ly thân) và “widowed” (góa bụa) đều cao hơn so với nam giới. Ngược lại, số lượng nam giới ở trạng thái “single” (độc thân) lại cao hơn một chút so với nữ giới. Những con số này cung cấp bằng chứng định lượng về sự khác biệt trong mô hình hôn nhân và gia đình giữa hai giới trong mẫu khảo sát.

3. Phân tích tình trạng việc làm và nghề nghiệp

Sau khi đã hiểu “họ là ai” qua các đặc điểm nhân khẩu học, mục này sẽ tập trung vào câu hỏi “họ làm gì?”. Chúng ta sẽ phân tích bức tranh tổng thể về tình trạng việc làm (có việc, thất nghiệp, hay không tham gia lao động), khám phá các yếu tố có thể ảnh hưởng đến cơ hội việc làm, và cuối cùng là xác định những ngành nghề phổ biến nhất trong mẫu dữ liệu.

3.1. Tổng quan về tình trạng việc làm

Đầu tiên, chúng ta cần có một cái nhìn tổng quan về sự phân bổ của lực lượng lao động theo các trạng thái việc làm khác nhau.

# Bảng tần suất tình trạng việc làm
table(df$empstat) %>% as.data.frame() %>% rename("Tình trạng việc làm" = Var1, "Số lượng" = Freq) %>% flextable() %>% set_caption("Bảng 7: Tần suất các tình trạng việc làm") %>% autofit()
Bảng 7: Tần suất các tình trạng việc làm

Tình trạng việc làm

Số lượng

Employed

69,160

NIU

29,483

Not in labor force

45,018

Unemployed

2,472

Một câu hỏi cơ bản khi phân tích lực lượng lao động là tỷ lệ có việc làm, thất nghiệp và không tham gia lao động là bao nhiêu. Bảng 7 được tạo ra bằng cách tính tần suất của biến empstat, đã lượng hóa sự phân bổ này. Kết quả cho thấy trong tổng số các quan sát, nhóm “employed” (có việc làm) chiếm số lượng lớn nhất (69,160 người). Đáng chú ý, số người “not in labor force” (không tham gia lực lượng lao động) cũng rất cao (45,018 người), cao hơn nhiều so với nhóm “unemployed” (thất nghiệp). Điều này cho thấy một phần đáng kể dân số trong mẫu khảo sát không có việc làm không phải vì họ đang tìm việc mà không được mà vì họ ở ngoài lực lượng lao động (ví dụ: sinh viên, nội trợ, người về hưu).

# Biểu đồ 7: Tần suất tình trạng việc làm
# Dùng fct_infreq() để tự động sắp xếp các cột theo thứ tự giảm dần
ggplot(df, aes(x = fct_infreq(empstat))) + geom_bar(fill = "coral") + geom_text(stat='count', aes(label=..count..), vjust=-0.5) + labs(title = "Biểu đồ 7: Phân bố theo tình trạng việc làm", x = "Tình trạng việc làm", y = "Số lượng") + theme_minimal()

Biểu đồ 7 cung cấp một cái nhìn trực quan về sự phân bổ của lực lượng lao động theo các trạng thái việc làm khác nhau. Bằng cách sử dụng geom_bar để vẽ các cột có chiều cao tương ứng với số lượng người và hàm fct_infreq() để sắp xếp các cột theo thứ tự giảm dần, biểu đồ đã làm nổi bật quy mô của từng nhóm. Có thể thấy rõ, cột “employed” (có việc làm) cao vượt trội, cho thấy đây là nhóm chiếm đa số. Cột “not in labor force” (không tham gia lực lượng lao động) cũng chiếm một phần đáng kể, trong khi cột “unemployed” (thất nghiệp) lại rất thấp, cho thấy tỷ lệ thất nghiệp trong mẫu này là không cao. Việc trực quan hóa này giúp nhanh chóng nắm bắt được cấu trúc tổng thể của thị trường lao động trong mẫu khảo sát.

3.2. Mối liên hệ giữa việc làm và các yếu tố nhân khẩu học

Một người có việc làm hay không có thể phụ thuộc vào nhiều yếu tố. Trong phần này,mục đích là điều tra mối quan hệ giữa tình trạng việc làm với trình độ học vấn và giới tính - hai yếu tố xã hội có ảnh hưởng lớn.

# Bảng chéo giữa Nhóm học vấn và Tình trạng việc làm
table(df$educ_group, df$empstat) %>% as.data.frame() %>% rename("Nhóm học vấn" = Var1, "Tình trạng việc làm" = Var2, "Số lượng" = Freq) %>% flextable() %>% set_caption("Bảng 8: Bảng chéo giữa học vấn và tình trạng việc làm") %>% autofit()
Bảng 8: Bảng chéo giữa học vấn và tình trạng việc làm

Nhóm học vấn

Tình trạng việc làm

Số lượng

Thấp

Employed

91

Trung bình

Employed

29,064

Cao

Employed

35,102

Thấp

NIU

29,483

Trung bình

NIU

0

Cao

NIU

0

Thấp

Not in labor force

232

Trung bình

Not in labor force

21,488

Cao

Not in labor force

13,346

Thấp

Unemployed

7

Trung bình

Unemployed

1,351

Cao

Unemployed

735

Câu hỏi đặt ra là liệu trình độ học vấn có ảnh hưởng đến tình trạng việc làm hay không. Để trả lời, một bảng chéo đã được lập để thống kê số lượng người theo cả hai tiêu chí này. Các con số trong bảng 8 đã cho thấy một mối liên hệ rõ rệt. Đáng chú ý nhất là ở nhóm học vấn “cao”, số người có việc làm (35,102) cao hơn rất nhiều so với số người thất nghiệp (735). Trong khi đó, ở nhóm “trung bình”, mặc dù số người có việc làm vẫn chiếm đa số, nhưng số người thất nghiệp (1,351) lại cao gần gấp đôi so với nhóm “cao”. Sự khác biệt này cho thấy trình độ học vấn cao dường như là một lợi thế lớn trên thị trường lao động.

# Biểu đồ 8: Tỷ lệ tình trạng việc làm theo nhóm học vấn
ggplot(df, aes(y = educ_group, fill = empstat)) + geom_bar(position = "fill") + scale_x_continuous(labels = percent) + labs(title = "Biểu đồ 8: Tỷ lệ tình trạng việc làm theo nhóm học vấn", y = "Nhóm học vấn", x = "Tỷ lệ", fill = "Tình trạng việc làm") + theme_minimal()

Biểu đồ 8 đã làm sáng tỏ vai trò quan trọng của trình độ học vấn đối với cơ hội việc làm. Bằng cách sử dụng biểu đồ cột chồng tỷ lệ 100% (position = “fill”), chúng ta có thể so sánh một cách công bằng cơ cấu tình trạng việc làm giữa các nhóm học vấn. Điểm nổi bật nhất là sự khác biệt về tỷ lệ có việc làm (màu hồng) giữa các nhóm: tỷ lệ này tăng lên rõ rệt từ nhóm “trung bình” đến nhóm “cao”. Ở nhóm “cao”, phần màu hồng chiếm ưu thế áp đảo, cho thấy phần lớn những người có trình độ học vấn cao đều có việc làm. Ngược lại, tỷ lệ thất nghiệp (màu tím) ở nhóm “cao” là thấp nhất. Những kết quả trực quan này cung cấp một luận điểm vững chắc rằng việc nâng cao trình độ học vấn có mối tương quan thuận mạnh mẽ với khả năng có việc làm và giảm thiểu rủi ro thất nghiệp.

# Tỷ lệ thất nghiệp theo nhóm học vấn
df %>% group_by(educ_group) %>% summarise("Tỷ lệ thất nghiệp" = mean(empstat == "Unemployed")) %>% mutate("Tỷ lệ thất nghiệp (%)" = scales::percent(`Tỷ lệ thất nghiệp`, accuracy=0.1)) %>% flextable() %>% set_caption("Bảng 9: Tỷ lệ thất nghiệp theo nhóm học vấn") %>% autofit()
Bảng 9: Tỷ lệ thất nghiệp theo nhóm học vấn

educ_group

Tỷ lệ thất nghiệp

Tỷ lệ thất nghiệp (%)

Thấp

0.0002347969

0.0%

Trung bình

0.0260293239

2.6%

Cao

0.0149441880

1.5%

0.0248785611

2.5%

Bảng 9 đóng vai trò bổ sung và làm rõ hơn cho biểu đồ 8 bằng cách tập trung vào một khía cạnh cụ thể là tỷ lệ thất nghiệp. Bằng cách áp dụng hàm summarise() để tính tỷ lệ thất nghiệp cho mỗi nhóm học vấn, bảng này cung cấp những con số chính xác đằng sau các thanh màu tím trong biểu đồ. Kết quả cho thấy tỷ lệ thất nghiệp ở nhóm học vấn “cao” chỉ là 1.5%, trong khi ở nhóm “trung bình” là 2.6%. Thao tác thống kê này giúp lượng hóa một cách rõ ràng mức độ rủi ro thất nghiệp mà mỗi nhóm phải đối mặt, khẳng định rằng trình độ học vấn cao hơn đi liền với sự an toàn hơn trên thị trường lao động.

# Tính và hiển thị bảng tỷ lệ có việc làm theo giới tính
df %>% group_by(sex) %>% summarise(ty_le_co_viec_lam = mean(empstat == "Employed")) %>% mutate(ty_le_co_viec_lam_pct = scales::percent(ty_le_co_viec_lam, accuracy = 0.1)) %>% flextable() %>% set_caption("Bảng 10: Tỷ lệ có việc làm theo giới tính") %>% autofit()
Bảng 10: Tỷ lệ có việc làm theo giới tính

sex

ty_le_co_viec_lam

ty_le_co_viec_lam_pct

Female

0.4416709

44.2%

Male

0.5064307

50.6%

Để lượng hóa cơ hội việc làm theo giới tính, bảng 10 đã tính toán tỷ lệ người có việc làm trong nội bộ từng nhóm. Bằng cách nhóm dữ liệu theo sex và áp dụng hàm summarise(), kết quả cho thấy 50.6% tổng số nam giới trong mẫu khảo sát đang có việc làm. Trong khi đó, con số này ở nữ giới là 44.2%. Sự chênh lệch hơn 6 điểm phần trăm này cho thấy nam giới trong mẫu có tỷ lệ tham gia vào thị trường lao động với tư cách là người có việc làm cao hơn so với nữ giới. Phép tính này không so sánh số lượng tuyệt đối mà tập trung vào tỷ lệ, cung cấp một góc nhìn sâu sắc hơn về sự khác biệt trong đặc điểm lao động giữa hai giới.

# Vẽ biểu đồ so sánh tỷ lệ
df %>% group_by(sex) %>% summarise(ty_le_co_viec_lam = mean(empstat == "Employed")) %>% ggplot(aes(x = sex, y = ty_le_co_viec_lam, fill = sex)) + geom_col() + scale_y_continuous(labels = percent) + geom_text(aes(label = percent(ty_le_co_viec_lam, accuracy = 0.1)), vjust = -0.5) + theme_minimal() + theme(legend.position = "none") + labs(title = "Biểu đồ 9: So sánh tỷ lệ có việc làm giữa hai giới tính", x = "Giới tính", y = "Tỷ lệ có việc làm")

Biểu đồ 9 cung cấp một sự so sánh trực diện về cơ hội việc làm giữa nam và nữ. Dữ liệu từ bảng 10 đã được trực quan hóa thành hai cột đơn giản, nơi chiều cao mỗi cột đại diện cho tỷ lệ người có việc làm trong nhóm giới tính tương ứng. Sự chênh lệch về chiều cao giữa hai cột là không thể nhầm lẫn: cột của nam giới ở mức 50.6% cao hơn rõ rệt so với cột của nữ giới ở mức 44.2%. Hình ảnh trực quan này không chỉ đơn thuần là sự biểu diễn lại con số, mà nó nhấn mạnh một cách mạnh mẽ về khoảng cách và sự khác biệt trong đặc điểm lao động giữa hai giới, là một tiền đề quan trọng cho các phân tích sâu hơn về bất bình đẳng giới.

3.3. Phân tích nghề nghiệp phổ biến

Cuối cùng, chúng ta sẽ xem xét cụ thể hơn về các công việc mà lực lượng lao động đang làm.

# Tạo bảng 10 nghề nghiệp phổ biến nhất
top_10_occ_df <- df %>% filter(!occ %in% c("N/A", "Not in universe")) %>% count(occ, sort = TRUE) %>%  head(10) 
top_10_occ_df %>% rename("Nghề nghiệp" = occ, "Số lượng" = n) %>% flextable() %>% set_caption("Bảng 11: Top 10 nghề nghiệp phổ biến nhất") %>% autofit()
Bảng 11: Top 10 nghề nghiệp phổ biến nhất

Nghề nghiệp

Số lượng

Managers, all other

2,510

Elementary and middle school teachers

1,638

Driver/sales workers and truck drivers

1,619

Registered nurses

1,498

First-Line supervisors of retail sales workers

1,324

Cashiers

1,295

Customer service representatives

1,273

Retail salespersons

1,213

Janitors and building cleaners

1,075

Construction laborers

989

Bảng 11 phác họa một bức tranh về thị trường lao động trong mẫu khảo sát bằng cách liệt kê 10 ngành nghề có số lượng lao động đông đảo nhất. Sau khi loại bỏ các quan sát không có thông tin nghề nghiệp hợp lệ, hàm count() đã được sử dụng để thống kê và xếp hạng. Kết quả cho thấy sự đa dạng của các ngành nghề phổ biến, từ các vị trí quản lý (“managers, all other”) và chuyên môn cao (giáo viên, y tá) cho đến các công việc trong lĩnh vực dịch vụ bán lẻ và lao động phổ thông (tài xế, nhân viên vệ sinh). Sự hiện diện của nhiều loại hình công việc khác nhau trong top 10 cho thấy một cơ cấu lao động đa dạng, không tập trung quá mức vào một lĩnh vực cụ thể nào.

# Vẽ biểu đồ Top 10 nghề nghiệp
ggplot(top_10_occ_df, aes(x = reorder(occ, n), y = n)) + geom_col(fill = "darkcyan") + coord_flip() +  geom_text(aes(label = n), hjust = -0.2, size = 3.5) + scale_y_continuous(expand = expansion(mult = c(0, 0.1))) + labs(title = "Biểu đồ 10: Top 10 nghề nghiệp phổ biến nhất", x = "Nghề nghiệp", y = "Số lượng") + theme_light()

Biểu đồ 10 cung cấp một sự so sánh trực quan về quy mô của 10 ngành nghề phổ biến nhất trong mẫu. Bằng cách sử dụng biểu đồ cột ngang (coord_flip()) và reorder(occ, n) để sắp xếp các cột theo thứ tự tăng dần về số lượng, tên của từng nghề nghiệp được hiển thị đầy đủ và độ dài của các thanh cho phép so sánh nhanh chóng số lượng lao động. Có thể thấy rõ, nhóm “managers, all other” có quy mô lớn nhất, gần gấp rưỡi so với nhóm đứng thứ hai là “elementary and middle school teachers”. Các nghề nghiệp còn lại trong top 10 có số lượng khá tương đồng với nhau. Hình ảnh trực quan này không chỉ minh họa cho bảng thống kê trước đó mà còn làm nổi bật sự chênh lệch về quy mô giữa nhóm nghề quản lý và các nhóm nghề nghiệp chuyên môn, dịch vụ khác.

4. Phân tích thu nhập và các yếu tố ảnh hưởng

Sau khi đã xác định “họ là ai” và “họ làm gì”, câu hỏi cuối cùng và quan trọng nhất là “họ kiếm được bao nhiêu?”. Mục này sẽ đi sâu phân tích về thu nhập, từ việc xem xét bức tranh phân phối tổng thể cho đến việc điều tra các yếu tố chính có khả năng ảnh hưởng đến mức lương của một người lao động, như giới tính, trình độ học vấn, tuổi tác và nghề nghiệp.

4.1. Phân phối thu nhập tổng thể

Trước khi so sánh thu nhập giữa các nhóm, chúng ta cần hiểu rõ đặc điểm phân phối thu nhập của toàn bộ những người có lương trong mẫu.

# Thống kê mô tả thu nhập cho những người có lương > 0
df_with_income <- df %>% filter(incwage > 0)
df_with_income %>% summarise("Thu nhập Trung bình" = mean(incwage), "Thu nhập Trung vị" = median(incwage), "Độ lệch chuẩn" = sd(incwage), "Thu nhập nhỏ nhất" = min(incwage), "Thu nhập lớn nhất" = max(incwage)) %>% pivot_longer(everything(), names_to = "Chỉ số", values_to = "Giá trị") %>% flextable() %>% colformat_double(digits = 0, big.mark = ",") %>% set_caption("Bảng 12: Thống kê mô tả về thu nhập (USD/năm)") %>% autofit()
Bảng 12: Thống kê mô tả về thu nhập (USD/năm)

Chỉ số

Giá trị

Thu nhập Trung bình

66,478

Thu nhập Trung vị

49,000

Độ lệch chuẩn

85,212

Thu nhập nhỏ nhất

1

Thu nhập lớn nhất

1,549,999

Một cái nhìn sơ bộ về thu nhập của những người lao động có lương đã hé lộ một bức tranh về sự bất bình đẳng. Bảng 12, kết quả của việc tính toán các chỉ số thống kê mô tả cho thấy thu nhập trung bình (66,478 USD) cao hơn rất nhiều so với thu nhập trung vị (49,000 USD). Sự chênh lệch lớn này có ý nghĩa rằng hơn một nửa số người lao động có mức thu nhập thấp hơn mức trung bình chung và giá trị trung bình này đang bị kéo lên bởi một số ít cá nhân có thu nhập ngoại lệ với mức cao nhất được ghi nhận lên tới gần 1.55 triệu USD. Độ lệch chuẩn cực kỳ cao (85,212 USD) càng củng cố thêm cho nhận định về sự phân tán và chênh lệch lớn trong thu nhập.

# Biểu đồ 11: Phân phối mật độ của thu nhập (thang đo Log)
ggplot(df_with_income, aes(x = incwage)) + geom_density(fill = "lightgreen", alpha = 0.7) + scale_x_log10(labels = dollar_format()) + labs(title = "Biểu đồ 11: Phân phối mật độ của thu nhập", subtitle = "Trục hoành được biểu diễn theo thang đo Logarit", x = "Thu nhập (USD/năm)", y = "Mật độ") + theme_classic()

Việc trực quan hóa một biến có độ lệch cao như thu nhập thường gặp khó khăn. Một biểu đồ tần suất thông thường sẽ bị co cụm ở một đầu và rất khó đọc. Để giải quyết vấn đề này, biểu đồ 11 đã sử dụng hai kỹ thuật kết hợp: geom_density() để vẽ một đường cong phân phối mượt mà thay vì các cột rời rạc, và quan trọng hơn là scale_x_log10() để áp dụng thang đo logarit cho trục hoành. Kết quả là một biểu đồ rõ ràng hơn rất nhiều, cho thấy đỉnh của phân phối thu nhập nằm ở đâu. Có thể thấy, phần lớn người lao động có thu nhập tập trung trong khoảng từ 30,000 đến 80,000 USD/năm và mật độ giảm dần ở cả hai phía thu nhập thấp hơn và cao hơn.

# Phân vị thu nhập (25%, 50% và 75%)
quantile(df_with_income$incwage, probs = c(0.25, 0.5, 0.75)) %>% as.list() %>% as.data.frame() %>% rename("25%" = X25., "50% (Trung vị)" = X50., "75%" = X75.) %>% flextable() %>% colformat_double(digits = 0, big.mark = ",") %>% set_caption("Bảng 13: Các mức phân vị của thu nhập") %>% autofit()
Bảng 13: Các mức phân vị của thu nhập

25%

50% (Trung vị)

75%

26,000

49,000

80,000

Bảng 13 chia những người lao động có thu nhập thành bốn nhóm bằng nhau thông qua các mức phân vị được tính bằng hàm quantile(). Các con số này phác họa nên một bức tranh chi tiết hơn về cơ cấu thu nhập. Cụ thể, một phần tư lực lượng lao động có thu nhập thấp nhất kiếm được dưới 26,000 USD mỗi năm. Mức thu nhập trung vị là 49,000 USD là ranh giới chia đôi lực lượng lao động. Đáng chú ý, để lọt vào top 25% những người có thu nhập cao nhất, một cá nhân cần kiếm được ít nhất 80,000 USD mỗi năm. Việc lượng hóa các ngưỡng thu nhập này giúp hiểu rõ hơn về sự phân tầng kinh tế trong mẫu khảo sát.

# Top 5 người có thu nhập cao nhất
df %>% select(age, sex, educ, occ, incwage) %>% arrange(desc(incwage)) %>% head(5) %>% flextable() %>% colformat_double(j = "incwage", big.mark = ",") %>% set_caption("Bảng 14: Top 5 cá nhân có thu nhập cao nhất") %>% autofit()
Bảng 14: Top 5 cá nhân có thu nhập cao nhất

age

sex

educ

occ

incwage

32

Male

Some high school

First-line supervisors of construction trades and extraction workers

1,549,999.0

66

Male

Some college

First-line supervisors of construction trades and extraction workers

1,549,999.0

40

Male

Graduate degree

Managers, all other

1,154,999.0

48

Female

Graduate degree

Accountants and auditors

1,099,999.0

45

Female

Graduate degree

Accountants and auditors

1,099,999.0

Sự chênh lệch lớn giữa thu nhập trung bình và trung vị đã được lý giải một cách cụ thể thông qua bảng 14, nơi liệt kê 5 cá nhân có thu nhập cao nhất. Việc sử dụng hàm arrange(desc(incwage)) để xếp hạng thu nhập cho thấy sự tồn tại của các mức lương vượt trội, lên đến hơn 1.5 triệu USD. Những giá trị ngoại lai này, dù chỉ chiếm một tỷ lệ rất nhỏ trong tổng số mẫu, lại có tác động rất lớn lên giá trị trung bình, kéo nó lệch xa khỏi mức thu nhập điển hình của đa số dân chúng (được thể hiện qua trung vị). Bảng kết quả này không chỉ là một danh sách, mà còn là một bằng chứng thực tế minh họa cho tính chất phân phối lệch của dữ liệu thu nhập

# Số lượng người không có thu nhập theo nhóm học vấn
df %>% filter(incwage == 0) %>% count(educ_group, name = "Số lượng người không có thu nhập") %>% arrange(desc(`Số lượng người không có thu nhập`)) %>% flextable() %>% set_caption("Bảng 15: Phân bố người không có thu nhập theo học vấn") %>% autofit()
Bảng 15: Phân bố người không có thu nhập theo học vấn

educ_group

Số lượng người không có thu nhập

Thấp

29,713

Trung bình

22,562

Cao

14,617

10,093

Bảng 15 khám phá mối quan hệ giữa trình độ học vấn và tình trạng không có thu nhập. Bằng cách lọc ra các cá nhân có incwage == 0 và đếm số lượng theo educ_group, kết quả cho thấy một mối tương quan nghịch rõ ràng. Cụ thể, số lượng người không có thu nhập cao nhất ở nhóm học vấn “thấp” và giảm dần ở các nhóm “trung bình” và “cao”. Điều này cho thấy trình độ học vấn không chỉ là yếu tố thúc đẩy thu nhập cao hơn mà còn đóng vai trò như một tấm đệm bảo vệ, giúp giảm khả năng một cá nhân rơi vào tình trạng không có thu nhập từ lương.

4.2. Tác động của yếu tố nhân khẩu học đến thu nhập

Đây là phần cốt lõi, nơi chúng ta kiểm tra xem các đặc điểm cá nhân như giới tính, học vấn, chủng tộc có tạo ra sự khác biệt về thu nhập hay không.

# Biểu đồ 12: Phân phối thu nhập theo giới tính
ggplot(df_with_income, aes(x = sex, y = incwage, fill = sex)) + geom_violin(trim = FALSE, alpha = 0.8) + geom_boxplot(width = 0.1, fill = "white", alpha = 0.5) + scale_y_continuous(labels = dollar_format(), limits = c(0, 200000)) + labs(title = "Biểu đồ 12: Phân phối thu nhập theo giới tính", subtitle = "(Giới hạn hiển thị ở mức 200,000 USD/năm để dễ quan sát)", x = "Giới tính", y = "Thu nhập (USD/năm)") + theme_light() + theme(legend.position = "none")

Biểu đồ 12 cung cấp một bằng chứng trực quan thuyết phục về sự tồn tại của khoảng cách thu nhập theo giới. Bằng cách kết hợp biểu đồ violin (geom_violin) và biểu đồ hộp (geom_boxplot), chúng ta có thể so sánh toàn diện phân phối thu nhập giữa nam và nữ. Toàn bộ thân của violin và hộp phân vị của nam giới đều dịch chuyển lên phía trên so với nữ giới, cho thấy ở mọi phân khúc, thu nhập của nam giới đều có xu hướng cao hơn. Đặc biệt, đường kẻ ngang biểu thị mức thu nhập trung vị của nam giới nằm ở vị trí cao hơn rõ rệt. Điều này có nghĩa là một người đàn ông điển hình trong mẫu này có thu nhập cao hơn một người phụ nữ. Kết quả này không chỉ cho thấy sự khác biệt về con số trung bình mà còn là sự khác biệt mang tính hệ thống trong toàn bộ cơ cấu thu nhập giữa hai giới.

# Biểu đồ 13: Thu nhập trung bình theo trình độ học vấn
df_with_income %>% filter(educ_group %in% c("Thấp", "Trung bình", "Cao")) %>% group_by(educ_group) %>% summarise(mean_income = mean(incwage)) %>% ggplot(aes(x = educ_group, y = mean_income, fill = educ_group)) + geom_col() + geom_text(aes(label = dollar(round(mean_income))), vjust = -0.5, size = 3.5) + scale_y_continuous(labels = dollar) + labs(title = "Biểu đồ 13: Thu nhập trung bình theo nhóm học vấn", x = "Nhóm học vấn", y = "Thu nhập trung bình") + theme_classic() + theme(legend.position = "none")

Biểu đồ 13 đã làm nổi bật tác động mạnh mẽ của trình độ học vấn đến thu nhập. Bằng cách tính toán thu nhập trung bình cho từng nhóm học vấn và trực quan hóa bằng biểu đồ cột (geom_col), kết quả cho thấy một xu hướng tăng tiến rõ rệt. Đáng chú ý nhất là sự chênh lệch lớn của nhóm “cao”, với mức thu nhập trung bình là 88,930 USD, cao gần gấp đôi so với nhóm “trung bình” (46,755 USD) và nhóm “thấp” (43,144 USD). Sự khác biệt rõ ràng về chiều cao giữa các cột cung cấp một bằng chứng trực quan thuyết phục về phần thưởng kinh tế (economic premium) của việc đầu tư vào giáo dục đại học và sau đại học.

# Biểu đồ 14: So sánh thu nhập theo học vấn, phân tách theo giới tính
ggplot(df_with_income, aes(x = educ_group, y = incwage, fill = sex)) + geom_boxplot() + facet_wrap(~sex) + scale_y_continuous(labels = dollar, limits = c(0, 250000)) + labs(title = "Biểu đồ 14: Phân phối thu nhập theo học vấn và giới tính", subtitle = "(Giới hạn hiển thị 250,000 USD)", x = "Nhóm học vấn", y = "Thu nhập") + theme_bw()

Biểu đồ 14 làm sáng tỏ một câu chuyện phức tạp hơn về thu nhập bằng cách đồng thời xem xét cả hai yếu tố học vấn và giới tính. Bằng cách sử dụng facet_wrap(~sex) để tạo ra hai biểu đồ con riêng biệt, chúng ta có thể quan sát hai xu hướng song song. Xu hướng thứ nhất, theo chiều ngang trong mỗi biểu đồ, là thu nhập trung vị (đường kẻ đậm trong hộp) tăng lên rõ rệt khi trình độ học vấn tăng từ “thấp” lên “cao”, điều này đúng cho cả nam và nữ. Xu hướng thứ hai, khi so sánh theo chiều dọc giữa hai biểu đồ là ở bất kỳ nhóm học vấn nào hộp phân vị của nam giới cũng đều cao hơn so với nữ giới. Sự kết hợp của hai xu hướng này cho thấy rằng, mặc dù giáo dục giúp cải thiện thu nhập cho tất cả mọi người nhưng dường như nó vẫn chưa đủ để xóa bỏ hoàn toàn khoảng cách thu nhập giữa hai giới.

# Biểu đồ 15: Phân phối thu nhập theo chủng tộc
ggplot(df_with_income, aes(x = reorder(race, incwage, FUN = median), y = incwage, fill = race)) + geom_boxplot() + scale_y_log10(labels = dollar) + coord_flip() + theme(legend.position = "none") + labs(title = "Biểu đồ 15: Phân phối thu nhập theo chủng tộc (thang đo Log)", y = "Thu nhập (USD/năm)", x = "Chủng tộc")

Kết quả trực quan hóa cho thấy một sự chênh lệch đáng kể về thu nhập trung vị giữa các nhóm chủng tộc. Cụ thể, nhóm “asian” và “white” có mức thu nhập trung vị (đường kẻ đậm giữa hộp) cao hơn rõ rệt so với các nhóm còn lại. Chiều cao của các hộp và độ dài của các “râu” cũng thể hiện sự khác biệt về độ phân tán thu nhập trong từng nhóm. Sự hiện diện của nhiều giá trị ngoại lai (outliers) ở tất cả các nhóm cho thấy tình trạng bất bình đẳng thu nhập không chỉ tồn tại giữa các chủng tộc mà còn diễn ra mạnh mẽ ngay trong nội bộ của từng nhóm. Những phát hiện này cung cấp bằng chứng về sự bất bình đẳng kinh tế theo chủng tộc trong mẫu khảo sát, một vấn đề phù hợp với nhiều nghiên cứu xã hội học tại Hoa Kỳ.

# Biểu đồ 16: Thu nhập TB theo tình trạng hôn nhân và giới tính
ggplot(df_with_income, aes(x = marst, y = incwage, fill = sex)) + stat_summary(fun = "mean", geom = "bar", position = "dodge") + scale_y_continuous(labels = dollar) + theme(axis.text.x = element_text(angle = 45, hjust = 1)) + labs(title = "Biểu đồ 16: Thu nhập TB theo tình trạng hôn nhân và giới tính", x = "Tình trạng hôn nhân", y = "Thu nhập trung bình", fill = "Giới tính")

Trực quan hóa thu nhập trung bình (incwage) theo cả tình trạng hôn nhân (marst) và giới tính (sex) để điều tra sâu hơn về sự tương tác giữa các yếu tố xã hội. Biểu đồ cột nhóm này được tạo ra bằng cách sử dụng stat_summary() với fun = “mean” và geom = “bar”, kết hợp position = “dodge” để đặt các cột của nam và nữ cạnh nhau, giúp việc so sánh trở nên trực quan. Kết quả cho thấy một mô hình nhất quán: trong mọi nhóm tình trạng hôn nhân, nam giới đều có thu nhập trung bình cao hơn nữ giới. Khoảng cách thu nhập này đặc biệt rõ rệt ở nhóm “married, spouse present”, nơi nam giới đạt mức thu nhập trung bình cao nhất trong tất cả các nhóm được khảo sát. Ngược lại, nhóm “married, spouse absent” có mức thu nhập trung bình thấp hơn đáng kể ở cả hai giới, gợi ý rằng không chỉ giới tính mà cả hoàn cảnh sống cũng có mối liên hệ phức tạp đến khả năng tạo ra thu nhập.

# Thu nhập trung vị theo tình trạng hôn nhân
df_with_income %>% group_by(marst) %>% summarise("Thu nhập trung vị" = median(incwage)) %>% arrange(desc(`Thu nhập trung vị`)) %>% flextable() %>% colformat_double(digits = 0, big.mark = ",") %>% set_caption("Bảng 16: Thu nhập trung vị theo tình trạng hôn nhân") %>% autofit()
Bảng 16: Thu nhập trung vị theo tình trạng hôn nhân

marst

Thu nhập trung vị

Married, spouse present

60,000

Divorced

49,000

Married, spouse absent

40,002

Widowed

38,000

Separated

37,050

Single

33,000

Nhằm cung cấp một thước đo vững chắc hơn so với giá trị trung bình, thu nhập trung vị (median) được tính toán theo từng nhóm tình trạng hôn nhân bằng cách sử dụng group_by(marst) và summarise(). Kỹ thuật này giúp xác định mức thu nhập điển hình, loại bỏ ảnh hưởng của các giá trị ngoại lai và kết quả được trình bày rõ ràng bằng flextable. Phân tích cho thấy một sự phân tầng thu nhập đáng kể, với nhóm “married, spouse present” có mức thu nhập trung vị cao nhất (60,000 USD), trong khi nhóm “single” có mức thu nhập trung vị thấp nhất (33,000 USD). Sự khác biệt rõ rệt này củng cố thêm bằng chứng về mối liên hệ phức tạp giữa các yếu tố xã hội như tình trạng hôn nhân và kết quả kinh tế của cá nhân.

# Độ lệch chuẩn của thu nhập theo nhóm học vấn
df_with_income %>% group_by(educ_group) %>% summarise("Độ lệch chuẩn thu nhập" = sd(incwage)) %>% flextable() %>% colformat_double(digits = 0, big.mark = ",") %>% set_caption("Bảng 17: Độ phân tán thu nhập theo nhóm học vấn") %>% autofit()
Bảng 17: Độ phân tán thu nhập theo nhóm học vấn

educ_group

Độ lệch chuẩn thu nhập

Thấp

122,273

Trung bình

58,397

Cao

99,531

65,184

Bên cạnh việc xác định mức thu nhập trung bình, việc phân tích độ biến động thu nhập trong mỗi nhóm học vấn cũng mang lại nhiều giá trị. Độ lệch chuẩn (sd) của thu nhập (incwage) đã được tính toán cho từng nhóm educ_group để lượng hóa sự chênh lệch. Kết quả cho thấy một bức tranh phức tạp: mặc dù nhóm “Thấp” có thu nhập trung bình thấp nhất, nhưng lại là nhóm có độ bất bình đẳng thu nhập cao nhất (độ lệch chuẩn 122,273). Điều này có thể cho thấy một sự phân cực lớn trong nhóm này, giữa những người lao động phổ thông và những người làm các công việc tay nghề không yêu cầu bằng cấp nhưng có thu nhập khá. Ngược lại, nhóm “trung bình” thể hiện sự ổn định nhất với độ lệch chuẩn thấp nhất (58,397). Sự gia tăng trở lại của độ lệch chuẩn ở nhóm “cao” (99,531) cho thấy rằng ngay cả khi có trình độ học vấn cao, con đường sự nghiệp và mức thu nhập vẫn rất đa dạng, từ các chuyên viên mới ra trường đến các nhà điều hành cấp cao.

4.3. Tác động của tuổi và nghề nghiệp đến thu nhập

Cuối cùng, chúng ta xem xét thu nhập thay đổi như thế nào theo vòng đời sự nghiệp (thể hiện qua tuổi) và theo ngành nghề cụ thể.

# Biểu đồ 17: Mối quan hệ giữa tuổi và thu nhập
ggplot(df_with_income %>% sample_n(5000), aes(x = age, y = incwage)) + geom_point(alpha = 0.1, color = "gray50", shape = 16) + geom_smooth(method = "loess", color = "firebrick", se = FALSE) + scale_y_continuous(labels = dollar, limits = c(0, 250000)) + labs(title = "Biểu đồ 17: Mối quan hệ giữa tuổi và thu nhập", subtitle = "Dựa trên mẫu ngẫu nhiên 5,000 người", x = "Tuổi", y = "Thu nhập") + theme_bw()

Biểu đồ 17 sử dụng biểu đồ phân tán (geom_point) để trực quan hóa mối quan hệ giữa tuổi và thu nhập. Do số lượng quan sát lớn, một mẫu ngẫu nhiên 5,000 điểm đã được sử dụng để biểu đồ không bị quá tải. Mỗi điểm đại diện cho một cá nhân. Để làm nổi bật xu hướng chung, một đường cong hồi quy cục bộ (geom_smooth(method = “loess”)) đã được thêm vào. Kết quả cho thấy một mối quan hệ phi tuyến tính rõ ràng: thu nhập có xu hướng tăng theo tuổi ở giai đoạn đầu, đạt đỉnh ở độ tuổi trung niên (khoảng 45-55 tuổi) và sau đó giảm dần. Hình dạng parabol này được gọi là “vòng cung sự nghiệp” - một quy luật kinh tế học cơ bản, phản ánh sự tích lũy kinh nghiệm và năng suất lao động trong suốt cuộc đời của một người.

# Hệ số tương quan tuổi-thu nhập
correlation <- cor(df_with_income$age, df_with_income$incwage)
cat(paste("Hệ số tương quan Pearson giữa tuổi và thu nhập là:", round(correlation, 3)))
Hệ số tương quan Pearson giữa tuổi và thu nhập là: 0.137

Bên cạnh việc trực quan hóa, việc tính toán hệ số tương quan Pearson bằng hàm cor() cho phép đưa ra một con số cụ thể để đo lường cường độ và chiều hướng của mối quan hệ tuyến tính giữa tuổi và thu nhập. Kết quả thu được là 0.137, một giá trị dương cho thấy xu hướng chung là thu nhập tăng khi tuổi tăng. Tuy nhiên, độ lớn của hệ số này (gần 0) chỉ ra rằng mối quan hệ tuyến tính này rất yếu. Điều này không có nghĩa là tuổi không ảnh hưởng đến thu nhập, mà nó cho thấy mối quan hệ thực tế có thể phức tạp hơn và không phải là một đường thẳng đơn giản, điều đã được thể hiện qua đường cong trong biểu đồ phân tán trước đó.

# Biểu đồ 18: Xu hướng Thu nhập trung bình theo Tuổi và Học vấn
df_with_income %>% group_by(age_group, educ_group) %>% summarise(mean_income = mean(incwage), .groups = 'drop') %>% ggplot(aes(x = age_group, y = mean_income, color = educ_group, group = educ_group)) + geom_line(size = 1.5) + geom_point(size = 3) + scale_y_continuous(labels = dollar) + labs(title = "Biểu đồ 18: Thu nhập trung bình thay đổi theo tuổi và học vấn", x = "Nhóm tuổi", y = "Thu nhập trung bình", color = "Nhóm học vấn") + theme_light()

Biểu đồ này minh họa một cách trực quan tác động cộng hưởng của tuổi tác và trình độ học vấn lên thu nhập trung bình. Bằng cách nhóm dữ liệu theo age_group và educ_group rồi tính toán giá trị trung bình tạo ra một biểu đồ đường (geom_line) để theo dõi quỹ đạo thu nhập của từng nhóm học vấn qua các giai đoạn sự nghiệp. Kết quả cho thấy học vấn không chỉ thiết lập một mức thu nhập khởi điểm khác nhau mà còn định hình cả con đường phát triển thu nhập trong tương lai. Nhóm có trình độ học vấn “cao” (màu xanh dương) không chỉ bắt đầu với mức lương cao hơn mà còn trải qua giai đoạn tăng trưởng thu nhập mạnh mẽ nhất, đạt đỉnh ở độ tuổi 46-60. Ngược lại, các nhóm có trình độ học vấn thấp hơn có quỹ đạo thu nhập phẳng hơn, với mức tăng trưởng khiêm tốn hơn theo tuổi tác. Sự nới rộng khoảng cách giữa các đường cong theo thời gian là một minh chứng rõ ràng cho thấy lợi tức của việc đầu tư vào giáo dục được tích lũy và nhân lên cùng với kinh nghiệm làm việc.

# Biểu đồ 19: Top 10 nghề nghiệp có thu nhập trung bình cao nhất
top_10_occ_df <- df_with_income %>% filter(occ != "N/A", !is.na(occ)) %>% group_by(occ) %>% summarise(mean_income = mean(incwage), count = n(), .groups = 'drop') %>% filter(count > 30) %>% arrange(desc(mean_income)) %>% head(10)
ggplot(top_10_occ_df, aes(x = reorder(occ, mean_income), y = mean_income)) + geom_col(fill = "darkgoldenrod1") + geom_text(aes(label = dollar(round(mean_income))), hjust = -0.1, color = "black", size=3) + coord_flip() + scale_y_continuous(labels = dollar, expand = expansion(mult = c(0, 0.1))) + labs(title = "Top 10 nghề nghiệp có thu nhập trung bình cao nhất", subtitle = "(Chỉ xét các nghề có trên 30 quan sát)", x = "Nghề nghiệp", y = "Thu nhập trung bình")
Biểu đồ cột ngang liệt kê Top 10 nghề có thu nhập trung bình cao nhất.

Biểu đồ cột ngang liệt kê Top 10 nghề có thu nhập trung bình cao nhất.

Để xác định các ngành nghề có tiềm năng thu nhập cao nhất trong mẫu, tiến hành lọc và xếp hạng các nghề nghiệp (occ) dựa trên mức thu nhập trung bình. Quy trình phân tích bao gồm việc nhóm dữ liệu theo nghề, tính toán thu nhập trung bình và số lượng quan sát, sau đó filter(count > 30) để chỉ giữ lại những nghề có đủ dữ liệu nhằm đảm bảo tính ổn định thống kê. Top 10 nghề nghiệp có thu nhập cao nhất được trực quan hóa bằng biểu đồ cột ngang (geom_col kết hợp coord_flip()), một lựa chọn phù hợp để hiển thị các nhãn nghề nghiệp dài. Kết quả cho thấy các ngành nghề đòi hỏi trình độ chuyên môn hóa cao như “other physicians” (bác sĩ) và “lawyers” (luật sư), cùng với các vị trí quản lý cấp cao như “chief executives” (giám đốc điều hành) đều chiếm lĩnh các vị trí dẫn đầu với mức thu nhập trung bình vượt trội, trên 180,000 USD/năm. Phát hiện này củng cố mạnh mẽ luận điểm rằng việc đầu tư vào giáo dục chuyên sâu và thăng tiến lên các vai trò quản lý là con đường hiệu quả nhất để đạt được mức thu nhập cao.

# Biểu đồ 20: Phân phối thu nhập của 5 nghề nghiệp phổ biến nhất
top_5_occ <- df %>% filter(occ != "N/A") %>% count(occ, sort = TRUE) %>% head(5) %>% pull(occ)
df_with_income %>% filter(occ %in% top_5_occ) %>% ggplot(aes(x = reorder(occ, incwage, FUN=median), y = incwage, fill = occ)) + geom_boxplot() + scale_y_log10(labels = dollar) + theme(legend.position = "none", axis.text.x = element_text(angle = 30, hjust = 1)) + labs(title = "Biểu đồ 20: Phân phối thu nhập của 5 nghề nghiệp phổ biến nhất", x = "Nghề nghiệp", y = "Thu nhập (thang đo Log)")

Nhằm trả lời câu hỏi “nghề phổ biến có phải là nghề lương cao không?”, biểu đồ hộp (geom_boxplot) so sánh phân phối thu nhập giữa 5 ngành nghề có số lượng lao động đông đảo nhất. Dữ liệu đầu vào được chuẩn bị bằng cách xác định top 5 nghề qua hàm count() và head(), sau đó lọc (filter) tập dữ liệu df_with_income theo danh sách này. Biểu đồ cho thấy mặc dù các nghề như “managers, all other” (quản lý) và “registered nurses” (y tá) đều là những nghề phổ biến, chúng cũng có mức thu nhập trung vị và khoảng phân phối cao hơn hẳn so với các nghề phổ thông khác. Điều này cho thấy rằng ngay cả trong nhóm các nghề nghiệp có số lượng lớn, vẫn tồn tại một sự phân tầng thu nhập rõ rệt, trong đó các vị trí đòi hỏi kỹ năng quản lý và chuyên môn y tế vẫn mang lại lợi ích kinh tế vượt trội.

# Thu nhập trung bình của 5 nghề nghiệp phổ biến nhất
df_with_income %>% filter(occ %in% top_5_occ) %>% group_by(occ) %>% summarise("Thu nhập trung bình" = mean(incwage)) %>% arrange(desc(`Thu nhập trung bình`)) %>% flextable() %>% colformat_double(digits = 0, big.mark = ",") %>% set_caption("Bảng 18: Thu nhập trung bình của 5 nghề phổ biến nhất") %>% autofit()
Bảng 18: Thu nhập trung bình của 5 nghề phổ biến nhất

occ

Thu nhập trung bình

Managers, all other

105,603

Registered nurses

81,147

Elementary and middle school teachers

59,508

Driver/sales workers and truck drivers

56,723

Not in universe

30,813

Bảng 18 trình bày kết quả tính toán thu nhập trung bình (mean) cho 5 nghề nghiệp có tần suất xuất hiện cao nhất trong mẫu dữ liệu. Quá trình tính toán được thực hiện bằng cách lọc (filter) dữ liệu theo danh sách 5 nghề phổ biến, sau đó nhóm (group_by) và tổng hợp (summarise) giá trị thu nhập. Kết quả cho thấy một sự phân hóa thu nhập rõ rệt: vị trí “managers, all other” (quản lý) có mức thu nhập trung bình cao nhất (105,603 USD), vượt xa các nhóm còn lại. Điều này cho thấy vai trò quản lý và các ngành nghề đòi hỏi chuyên môn cao như “registered nurses” (y tá) vẫn có tiềm năng thu nhập tốt hơn so với các công việc phổ thông khác, ngay cả khi tất cả đều là những ngành nghề có số lượng lao động lớn.

# Thu nhập trung vị của 5 nghề nghiệp phổ biến nhất
df_with_income %>% filter(occ %in% top_5_occ) %>% group_by(occ) %>% summarise("Thu nhập trung vị" = median(incwage)) %>% arrange(desc(`Thu nhập trung vị`)) %>% flextable() %>% colformat_double(digits = 0, big.mark = ",") %>% set_caption("Bảng 19: Thu nhập trung vị của 5 nghề phổ biến nhất") %>% autofit()
Bảng 19: Thu nhập trung vị của 5 nghề phổ biến nhất

occ

Thu nhập trung vị

Managers, all other

85,000

Registered nurses

73,000

Elementary and middle school teachers

55,000

Driver/sales workers and truck drivers

50,000

Not in universe

15,000

Nhằm cung cấp một thước đo vững chắc hơn so với thu nhập trung bình, vốn dễ bị ảnh hưởng bởi các giá trị ngoại lai. Bảng 19 trình bày kết quả tính toán thu nhập trung vị (median) cho 5 nghề nghiệp phổ biến nhất. Kỹ thuật được sử dụng bao gồm việc lọc (filter) dữ liệu theo danh sách 5 nghề này, sau đó nhóm (group_by) và tổng hợp (summarise) để tìm ra mức thu nhập điển hình của mỗi nghề. Kết quả cho thấy một thứ bậc thu nhập tương tự như phân tích thu nhập trung bình: vị trí “managers, all other” (quản lý) tiếp tục dẫn đầu với thu nhập trung vị là 85,000 USD, theo sau là “registered nurses” (y tá) với 73,000 USD. Việc sử dụng trung vị khẳng định lại sự phân tầng thu nhập giữa các ngành nghề, cho thấy rằng ngay cả khi loại bỏ ảnh hưởng của những cá nhân có thu nhập đặc biệt cao, các vị trí quản lý và chuyên môn vẫn có mức thu nhập cao hơn một cách rõ rệt.

5. Kết luận

Qua quá trình khám phá, xử lý và phân tích sâu bộ dữ liệu lực lượng lao động Hoa Kỳ năm 2023, chương này đã phác họa nên một bức tranh đa chiều về đặc điểm nhân khẩu học, tình trạng việc làm và các yếu tố ảnh hưởng đến thu nhập. Các phát hiện chính đã trả lời cho các câu hỏi nghiên cứu cốt lõi được đặt ra ban đầu.

- Về đặc điểm nhân khẩu học (“Họ là ai?”): phân tích cho thấy một lực lượng lao động trưởng thành, với độ tuổi trung bình và trung vị đều xoay quanh mốc 38 tuổi. Sự phân bổ giới tính tương đối cân bằng, cung cấp một nền tảng vững chắc cho các phân tích so sánh. Tuy nhiên, các phân tích sâu hơn đã chỉ ra các mô hình xã hội phức tạp, chẳng hạn như sự khác biệt về độ tuổi trung vị giữa các nhóm học vấn và sự phân bổ không đồng đều của các tình trạng hôn nhân theo giới tính và độ tuổi.

- Về tình trạng việc làm (“Họ làm gì?”): kết quả khẳng định một cách mạnh mẽ vai trò của giáo dục như một yếu tố quyết định đến cơ hội việc làm. Dữ liệu cho thấy tỷ lệ có việc làm tăng lên và tỷ lệ thất nghiệp giảm xuống rõ rệt ở những người có trình độ học vấn cao. Bên cạnh đó, cũng có sự khác biệt về tỷ lệ tham gia lao động giữa nam và nữ. Phân tích về các nghề nghiệp phổ biến cho thấy một cơ cấu lao động đa dạng, bao gồm cả các vị trí quản lý, chuyên môn và lao động phổ thông.

- Về thu nhập (“Họ kiếm được bao nhiêu?”): phân tích là nơi bộc lộ rõ nhất sự phân tầng kinh tế. Dữ liệu thu nhập thể hiện một sự phân phối lệch phải mạnh, với khoảng cách đáng kể giữa thu nhập trung bình và trung vị, cho thấy sự tồn tại của bất bình đẳng thu nhập. Các yếu tố như giới tính, trình độ học vấn, tuổi tác và chủng tộc đều được chứng minh là có mối tương quan đáng kể đến mức thu nhập. Cụ thể, nam giới, người có học vấn cao, người ở độ tuổi trung niên, và người thuộc nhóm chủng tộc “Asian” hoặc “White” có xu hướng đạt được mức thu nhập cao hơn. Đặc biệt, lợi ích kinh tế của việc đầu tư vào giáo dục được thể hiện rõ nét qua quỹ đạo thu nhập dốc hơn và đạt đỉnh cao hơn ở nhóm có trình độ “Cao”.

Tóm lại, chương 1 đã xây dựng một nền tảng dữ liệu vững chắc và rút ra được những cốt lõi quan trọng. Bức tranh toàn cảnh phác họa nên chân dung một người lao động điển hình, nơi con đường sự nghiệp và mức thu nhập chịu ảnh hưởng mạnh mẽ bởi nền tảng giáo dục và ngành nghề họ theo đuổi. Tuy nhiên, con đường đó không bằng phẳng cho tất cả mọi người; các yếu tố nhân khẩu học và xã hội vẫn tạo ra những rào cản và sự khác biệt nhất định trong việc tiếp cận cơ hội và phúc lợi kinh tế.

CHƯƠNG 2: PHÂN TÍCH BÁO CÁO TÀI CHÍNH MÃ CHỨNG KHOÁN VIC (VINGROUP)

Chương 2 chuyển hướng phân tích từ dữ liệu vi mô xã hội sang dữ liệu tài chính doanh nghiệp, cụ thể là “Báo cáo kết quả hoạt động kinh doanh của Tập đoàn Vingroup (VIC) trong giai đoạn 2015-2024”. Mục tiêu của chương là đánh giá hiệu quả hoạt động của Vingroup qua các năm thông qua việc phân tích các chỉ số tài chính quan trọng, từ đó rút ra các nhận định về xu hướng tăng trưởng, khả năng sinh lời và hiệu quả quản lý chi phí của tập đoàn.

1. Giới thiệu và chuẩn bị bộ dữ liệu

Bước đầu tiên và quan trọng nhất trong phân tích dữ liệu tài chính là đảm bảo dữ liệu được thu thập và tái cấu trúc về một định dạng chuẩn. Phần này sẽ trình bày quy trình nạp dữ liệu từ file Excel, xử lý và chuyển đổi dữ liệu. Cuối cùng là tính toán các chỉ số tài chính phái sinh để chuẩn bị cho các phân tích sâu hơn.

1.1. Đọc kết quả hoạt động kinh doanh

df_kqkd_raw <- read_excel("VIC_BCTC.xlsx", sheet = "Kết quả hoạt động kinh doanh")

Quá trình phân tích được khởi đầu bằng việc nhập dữ liệu từ file VIC_BCTC.xlsx. Cụ thể, hàm read_excel() từ gói readxl được sử dụng với tham số sheet được chỉ định là “Kết quả hoạt động kinh doanh” để đọc duy nhất dữ liệu liên quan vào dataframe thô df_kqkd_raw. Thao tác này là bước đầu tiên để tải dữ liệu vào môi trường R để chuẩn bị cho các bước tái cấu trúc và phân tích tiếp theo.

1.2. Xem vài dòng đầu và cấu trúc

head(df_kqkd_raw, 5) %>% kable(caption = "5 chỉ tiêu đầu tiên trong báo cáo KQKD") %>% kable_styling(full_width = F)
5 chỉ tiêu đầu tiên trong báo cáo KQKD
…1 2015 2016 2017 2018 2019 2020 2021 2022 2023 2024
Doanh thu bán hàng và cung cấp dịch vụ 34054968.885 57670387.20 89392047.93 121971750.63 130161398 110755497 125780761 101809529 161452751 189090599
Các khoản giảm trừ doanh thu -7002.572 -56043.54 -41999.34 -77350.21 -125384 -265464 -92891 -15947 -25183 -22559
Doanh thu thuần về bán hàng và cung cấp dịch vụ 34047966.313 57614343.67 89350048.60 121894400.42 130036014 110490033 125687870 101793582 161427568 189068040
Giá vốn hàng bán -22338933.561 -40184632.61 -62796326.96 -92971050.56 -92484797 -93177227 -91623165 -87099750 -137919092 -161767222
Lợi nhuận gộp về bán hàng và cung cấp dịch vụ 11709032.752 17429711.06 26553721.64 28923349.86 37551217 17312806 34064705 14693832 23508476 27300818

Việc hiển thị năm chỉ tiêu đầu tiên bằng hàm head() xác nhận dữ liệu đã được đọc vào đúng cấu trúc dạng rộng ban đầu. Quan sát các giá trị mẫu, có thể nhận thấy sự khác biệt đáng kể về quy mô con số giữa các năm trước và sau 2018, gợi ý về sự không nhất quán trong đơn vị tính. Vấn đề này sẽ cần được xử lý ở các bước tái cấu trúc dữ liệu tiếp theo để đảm bảo tính chính xác của phân tích.

1.3. Tái cấu trúc dữ liệu

kqkd_long <- df_kqkd_raw %>% rename(chi_tieu = 1) %>% pivot_longer(cols = -chi_tieu, names_to = "nam", values_to = "gia_tri", names_transform = as.numeric, values_transform = as.numeric) %>% filter(!is.na(nam) & !is.na(gia_tri) & !is.na(chi_tieu))

Để chuẩn bị cho việc phân tích, dữ liệu thô cần được chuyển đổi từ định dạng rộng (wide) sang định dạng dài (long). Thao tác này được thực hiện thông qua một chuỗi lệnh dplyr: đầu tiên, rename() được dùng để đặt tên cho cột chỉ tiêu; sau đó, hàm pivot_longer() thực hiện nhiệm vụ cốt lõi là “xoay” các cột năm thành các hàng. Các tham số names_transform và values_transform được sử dụng để bắt buộc chuyển đổi kiểu dữ liệu của cột năm và cột giá trị sang dạng số, nhằm đảm bảo tính nhất quán. Cuối cùng, filter() loại bỏ các hàng không hợp lệ (hàng trống hoặc hàng tổng kết không chứa dữ liệu). Kết quả của quá trình này là một dataframe kqkd_long có cấu trúc “tidy”, nơi mỗi hàng là một quan sát duy nhất (một chỉ tiêu trong một năm), một định dạng linh hoạt cho các bước xử lý và hợp nhất dữ liệu tiếp theo.

1.4. Xem cấu trúc dữ liệu sau khi tái cấu trúc

glimpse(kqkd_long)
Rows: 229
Columns: 3
$ chi_tieu <chr> "Doanh thu bán hàng và cung cấp dịch vụ", "Doanh thu bán hàng…
$ nam      <dbl> 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023, 2024, 2…
$ gia_tri  <dbl> 34054968.885, 57670387.202, 89392047.933, 121971750.626, 1301…

Sau bước tái cấu trúc, hàm glimpse() được sử dụng để xác thực lại cấu trúc của dataframe kqkd_long. Kết quả cho thấy dữ liệu đã được chuyển đổi thành công sang định dạng dài với 229 hàng và 3 cột chính. Quan trọng hơn, kết quả xác nhận rằng cả cột năm và cột giá trị đều có kiểu dữ liệu là số (dbl), chứng tỏ quá trình ép kiểu dữ liệu bên trong pivot_longer đã thành công. Việc đảm bảo kiểu dữ liệu nhất quán này là một bước kiểm tra chất lượng quan trọng, tạo tiền đề vững chắc cho các thao tác tính toán và hợp nhất dữ liệu ở các bước tiếp theo.

1.5. Tái cấu trúc về định dạng phân tích cuối cùng

vic_kqkd <- kqkd_long %>% pivot_wider(names_from = chi_tieu, values_from = gia_tri) %>% janitor::clean_names() %>% arrange(nam)

Để có được một dataframe tối ưu cho việc phân tích và trực quan hóa theo chuỗi thời gian, thao tác cuối cùng là chuyển đổi dữ liệu từ định dạng dài trở lại định dạng rộng. Kỹ thuật này được thực hiện bằng hàm pivot_wider(), trong đó các giá trị duy nhất từ cột chi_tieu được biến thành các cột mới và giá trị tương ứng được lấy từ cột gia_tri. Sau đó, hàm clean_names() được áp dụng để chuẩn hóa tên các cột mới này. Kết quả là dataframe vic_kqkd có cấu trúc lý tưởng với mỗi hàng đại diện cho một năm và mỗi cột đại diện cho một chỉ tiêu tài chính cụ thể, sẵn sàng cho các bước tính toán chỉ số phái sinh và phân tích xu hướng.

1.6. Kiểm tra số lượng giá trị duy nhất

# Đếm số lượng giá trị duy nhất trong mỗi cột chỉ tiêu
sapply(vic_kqkd, function(x) length(unique(x)))
                                            nam 
                                             10 
         doanh_thu_ban_hang_va_cung_cap_dich_vu 
                                             10 
                   cac_khoan_giam_tru_doanh_thu 
                                             10 
doanh_thu_thuan_ve_ban_hang_va_cung_cap_dich_vu 
                                             10 
                               gia_von_hang_ban 
                                             10 
  loi_nhuan_gop_ve_ban_hang_va_cung_cap_dich_vu 
                                             10 
                  doanh_thu_hoat_dong_tai_chinh 
                                             10 
                              chi_phi_tai_chinh 
                                             10 
        chi_phi_lai_vay_va_phat_hanh_trai_phieu 
                                             10 
  phan_lai_lo_trong_cong_ty_lien_doanh_lien_ket 
                                             10 
                               chi_phi_ban_hang 
                                             10 
                   chi_phi_quan_ly_doanh_nghiep 
                                             10 
        loi_nhuan_thuan_tu_hoat_dong_kinh_doanh 
                                             10 
                                  thu_nhap_khac 
                                             10 
                                   chi_phi_khac 
                                             10 
                                    lai_lo_khac 
                                             10 
              tong_loi_nhuan_ke_toan_truoc_thue 
                                             10 
                    chi_phi_thue_tndn_hien_hanh 
                                             10 
                chi_phi_thu_nhap_thue_tndn_hoan 
                                             10 
       loi_nhuan_sau_thue_thu_nhap_doanh_nghiep 
                                             10 
                  loi_nhuan_sau_thue_cong_ty_me 
                                             10 
  loi_nhuan_sau_thue_cong_ty_me_khong_kiem_soat 
                                             10 
                       lai_co_ban_tren_co_phieu 
                                             10 
                     lai_suy_giam_tren_co_phieu 
                                             10 

Một bước kiểm tra quan trọng trong quá trình khám phá dữ liệu là đánh giá độ đa dạng của từng biến. Thao tác này được thực hiện bằng cách áp dụng hàm sapply() để lặp qua mỗi cột trong dataframe vic_kqkd và đếm số lượng giá trị duy nhất bằng length(unique(x)). Kết quả cho thấy tất cả các biến tài chính đều có 10 giá trị duy nhất. Con số này khớp với số lượng quan sát (số năm) trong bộ dữ liệu, cho thấy rằng trong suốt giai đoạn được phân tích, không có chỉ tiêu nào có giá trị không đổi qua các năm.

1.7. Xem kích thước dữ liệu cuối cùng

dim(vic_kqkd)
[1] 10 24

Sau khi hoàn tất quá trình tái cấu trúc, hàm dim() được sử dụng để xác nhận lại kích thước của dataframe vic_kqkd cuối cùng. Kết quả 10 24 cho thấy bộ dữ liệu phân tích bao gồm 10 quan sát, tương ứng với chuỗi thời gian 10 năm và 24 biến, là các chỉ tiêu tài chính từ báo cáo kết quả kinh doanh. Việc xác nhận số lượng dòng và cột này đảm bảo rằng quá trình xử lý dữ liệu đã bảo toàn được đầy đủ các năm và các chỉ tiêu cần thiết cho việc phân tích xu hướng.

1.8. Liệt kê tên các biến đã được chuẩn hóa

colnames(vic_kqkd)
 [1] "nam"                                            
 [2] "doanh_thu_ban_hang_va_cung_cap_dich_vu"         
 [3] "cac_khoan_giam_tru_doanh_thu"                   
 [4] "doanh_thu_thuan_ve_ban_hang_va_cung_cap_dich_vu"
 [5] "gia_von_hang_ban"                               
 [6] "loi_nhuan_gop_ve_ban_hang_va_cung_cap_dich_vu"  
 [7] "doanh_thu_hoat_dong_tai_chinh"                  
 [8] "chi_phi_tai_chinh"                              
 [9] "chi_phi_lai_vay_va_phat_hanh_trai_phieu"        
[10] "phan_lai_lo_trong_cong_ty_lien_doanh_lien_ket"  
[11] "chi_phi_ban_hang"                               
[12] "chi_phi_quan_ly_doanh_nghiep"                   
[13] "loi_nhuan_thuan_tu_hoat_dong_kinh_doanh"        
[14] "thu_nhap_khac"                                  
[15] "chi_phi_khac"                                   
[16] "lai_lo_khac"                                    
[17] "tong_loi_nhuan_ke_toan_truoc_thue"              
[18] "chi_phi_thue_tndn_hien_hanh"                    
[19] "chi_phi_thu_nhap_thue_tndn_hoan"                
[20] "loi_nhuan_sau_thue_thu_nhap_doanh_nghiep"       
[21] "loi_nhuan_sau_thue_cong_ty_me"                  
[22] "loi_nhuan_sau_thue_cong_ty_me_khong_kiem_soat"  
[23] "lai_co_ban_tren_co_phieu"                       
[24] "lai_suy_giam_tren_co_phieu"                     

Để đảm bảo tính chính xác và tránh lỗi trong các bước tính toán sau này, hàm colnames() được sử dụng để trích xuất và hiển thị danh sách đầy đủ tên của 24 biến trong dataframe vic_kqkd. Kết quả này đóng vai trò như một từ điển dữ liệu, cung cấp các tên biến chính xác đã được hàm clean_names() tự động tạo ra. Việc có được danh sách tham chiếu này là rất quan trọng, cho phép sao chép và dán trực tiếp tên biến vào các công thức, qua đó loại bỏ các lỗi chính tả tiềm ẩn và đảm bảo tính nhất quán trong toàn bộ quy trình phân tích.

1.9. Thống kê mô tả nhanh

summary(vic_kqkd)
      nam       doanh_thu_ban_hang_va_cung_cap_dich_vu
 Min.   :2015   Min.   : 34054969                     
 1st Qu.:2017   1st Qu.: 92496418                     
 Median :2020   Median :116363624                     
 Mean   :2020   Mean   :112213969                     
 3rd Qu.:2022   3rd Qu.:129066239                     
 Max.   :2024   Max.   :189090599                     
                                                      
 cac_khoan_giam_tru_doanh_thu doanh_thu_thuan_ve_ban_hang_va_cung_cap_dich_vu
 Min.   :-265464              Min.   : 34047966                              
 1st Qu.: -89006              1st Qu.: 92460932                              
 Median : -49021              Median :116192217                              
 Mean   : -72982              Mean   :112140987                              
 3rd Qu.: -23215              3rd Qu.:128948978                              
 Max.   :  -7003              Max.   :189068040                              
                                                                             
 gia_von_hang_ban     loi_nhuan_gop_ve_ban_hang_va_cung_cap_dich_vu
 Min.   :-161767222   Min.   :11709033                             
 1st Qu.: -93125683   1st Qu.:17342032                             
 Median : -92053981   Median :25031099                             
 Mean   : -88236220   Mean   :23904767                             
 3rd Qu.: -68872183   3rd Qu.:28517717                             
 Max.   : -22338934   Max.   :37551217                             
                                                                   
 doanh_thu_hoat_dong_tai_chinh chi_phi_tai_chinh  
 Min.   : 1636951              Min.   :-31208095  
 1st Qu.: 6265805              1st Qu.:-13945865  
 Median :15023396              Median : -9772519  
 Mean   :17949986              Mean   :-11809239  
 3rd Qu.:28426930              3rd Qu.: -5028971  
 Max.   :47925492              Max.   : -3282075  
                                                  
 chi_phi_lai_vay_va_phat_hanh_trai_phieu
 Min.   :-22980044                      
 1st Qu.:-11287844                      
 Median : -8718125                      
 Mean   : -9406061                      
 3rd Qu.: -4031479                      
 Max.   : -2402861                      
                                        
 phan_lai_lo_trong_cong_ty_lien_doanh_lien_ket chi_phi_ban_hang   
 Min.   :-688443                               Min.   :-18053919  
 1st Qu.:-223415                               1st Qu.:-11913502  
 Median : -64096                               Median : -8760777  
 Mean   : -65243                               Mean   : -9624425  
 3rd Qu.:  34377                               3rd Qu.: -6995827  
 Max.   : 848773                               Max.   : -2957826  
                                                                  
 chi_phi_quan_ly_doanh_nghiep loi_nhuan_thuan_tu_hoat_dong_kinh_doanh
 Min.   :-24034459            Min.   :-4905383                       
 1st Qu.:-14727044            1st Qu.: 6232590                       
 Median :-13040264            Median : 8724775                       
 Mean   :-11834935            Mean   : 8520911                       
 3rd Qu.: -6981296            3rd Qu.:13107118                       
 Max.   : -3922773            Max.   :15756406                       
                                                                     
 thu_nhap_khac       chi_phi_khac       lai_lo_khac      
 Min.   :  283039   Min.   :-5778675   Min.   :-4614455  
 1st Qu.:  706181   1st Qu.:-3017040   1st Qu.: -581481  
 Median :  907443   Median :-1004932   Median :  -24687  
 Mean   : 4315587   Mean   :-2076220   Mean   : 2239368  
 3rd Qu.: 4648442   3rd Qu.: -811413   3rd Qu.: 3629800  
 Max.   :22132506   Max.   : -493550   Max.   :18674735  
                                                         
 tong_loi_nhuan_ke_toan_truoc_thue chi_phi_thue_tndn_hien_hanh
 Min.   : 2852101                  Min.   :-12913575          
 1st Qu.: 6623140                  1st Qu.:-10993880          
 Median :13262435                  Median : -9096090          
 Mean   :10760279                  Mean   : -7886998          
 3rd Qu.:13920326                  3rd Qu.: -4616819          
 Max.   :16738706                  Max.   : -1424643          
                                                              
 chi_phi_thu_nhap_thue_tndn_hoan loi_nhuan_sau_thue_thu_nhap_doanh_nghiep
 Min.   :-799260                 Min.   :-7558164                        
 1st Qu.:-389626                 1st Qu.: 2047273                        
 Median : 272042                 Median : 4029320                        
 Mean   : 217063                 Mean   : 3094085                        
 3rd Qu.: 519751                 3rd Qu.: 5560221                        
 Max.   :1450927                 Max.   : 7716613                        
 NA's   :1                                                               
 loi_nhuan_sau_thue_cong_ty_me loi_nhuan_sau_thue_cong_ty_me_khong_kiem_soat
 Min.   :-2513883              Min.   :-6737517                             
 1st Qu.: 2227572              1st Qu.:-4012974                             
 Median : 4119570              Median :   34917                             
 Mean   : 4523290              Mean   :-1429205                             
 3rd Qu.: 7025593              3rd Qu.:  876593                             
 Max.   :11903028              Max.   : 2414154                             
                                                                            
 lai_co_ban_tren_co_phieu lai_suy_giam_tren_co_phieu
 Min.   :-685.00000       Min.   :-685.00000        
 1st Qu.:   0.00079       1st Qu.:   0.00079        
 Median : 282.50091       Median : 262.50091        
 Mean   : 941.60037       Mean   : 907.10037        
 3rd Qu.:2198.00000       3rd Qu.:2075.00000        
 Max.   :3045.00000       Max.   :2976.00000        
                                                    

Một bản tóm tắt thống kê nhanh được tạo ra bằng hàm summary() để lượng hóa các đặc điểm phân phối chính của từng chỉ tiêu tài chính trong bộ dữ liệu vic_kqkd. Kết quả này không chỉ cung cấp các giá trị cực trị (min, max) mà còn cả các thước đo xu hướng trung tâm (mean, median) và độ phân tán (thông qua các tứ phân vị). Qua đó, có thể thấy các chỉ tiêu chính như doanh_thu_… và loi_nhuan_… có khoảng giá trị rất rộng và giá trị trung bình thường lớn hơn giá trị trung vị. Điều này cho thấy sự tăng trưởng không đồng đều qua các năm và sự hiện diện của những năm có kết quả kinh doanh đặc biệt cao, làm lệch phân phối chung.

1.10: Kiểm tra dữ liệu bị thiếu

colSums(is.na(vic_kqkd))
                                            nam 
                                              0 
         doanh_thu_ban_hang_va_cung_cap_dich_vu 
                                              0 
                   cac_khoan_giam_tru_doanh_thu 
                                              0 
doanh_thu_thuan_ve_ban_hang_va_cung_cap_dich_vu 
                                              0 
                               gia_von_hang_ban 
                                              0 
  loi_nhuan_gop_ve_ban_hang_va_cung_cap_dich_vu 
                                              0 
                  doanh_thu_hoat_dong_tai_chinh 
                                              0 
                              chi_phi_tai_chinh 
                                              0 
        chi_phi_lai_vay_va_phat_hanh_trai_phieu 
                                              0 
  phan_lai_lo_trong_cong_ty_lien_doanh_lien_ket 
                                              0 
                               chi_phi_ban_hang 
                                              0 
                   chi_phi_quan_ly_doanh_nghiep 
                                              0 
        loi_nhuan_thuan_tu_hoat_dong_kinh_doanh 
                                              0 
                                  thu_nhap_khac 
                                              0 
                                   chi_phi_khac 
                                              0 
                                    lai_lo_khac 
                                              0 
              tong_loi_nhuan_ke_toan_truoc_thue 
                                              0 
                    chi_phi_thue_tndn_hien_hanh 
                                              0 
                chi_phi_thu_nhap_thue_tndn_hoan 
                                              1 
       loi_nhuan_sau_thue_thu_nhap_doanh_nghiep 
                                              0 
                  loi_nhuan_sau_thue_cong_ty_me 
                                              0 
  loi_nhuan_sau_thue_cong_ty_me_khong_kiem_soat 
                                              0 
                       lai_co_ban_tren_co_phieu 
                                              0 
                     lai_suy_giam_tren_co_phieu 
                                              0 

Bước cuối cùng trong quá trình khám phá dữ liệu là kiểm tra lại chất lượng và tính toàn vẹn của bộ dữ liệu sau khi đã xử lý. Hàm colSums(is.na(vic_kqkd)) được sử dụng để đếm số lượng giá trị bị thiếu (NA) trong mỗi cột. Kết quả cho thấy hầu hết các biến đều hoàn chỉnh với 0 giá trị thiếu. Tuy nhiên, một điểm đáng chú ý là sự xuất hiện của một giá trị NA duy nhất trong cột chi_phi_thu_nhap_thue_tndn_hoan. Giá trị thiếu này có thể xuất hiện ở năm đầu tiên của các chỉ số có tính toán liên năm. Sự hiện diện của NA này củng cố tầm quan trọng của việc sử dụng tham số na.rm = TRUE trong tất cả các phép tính thống kê tổng hợp ở các phần sau để đảm bảo R bỏ qua các giá trị thiếu và không trả về kết quả lỗi.

2. Xử lý dữ liệu và tạo biến mới

Từ bộ dữ liệu vic_kqkd đã được chuẩn bị, phần này sẽ thực hiện việc tính toán các chỉ số tài chính quan trọng liên quan đến hiệu quả hoạt động và khả năng sinh lời. Mỗi chỉ số được tạo ra dưới dạng một biến mới, là một thao tác xử lý dữ liệu.

2.1. Tính lợi nhuận gộp

vic_kqkd_processed <- vic_kqkd %>% mutate(loi_nhuan_gop = doanh_thu_thuan_ve_ban_hang_va_cung_cap_dich_vu - gia_von_hang_ban)

Thao tác đầu tiên trong việc tạo biến mới là tính lợi nhuận gộp bằng cách sử dụng hàm mutate(), một biến mới loi_nhuan_gop đã được tạo ra thông qua phép trừ giữa doanh_thu_thuan_ve_ban_hang_va_cung_cap_dich_vu và gia_von_hang_ban. Lợi nhuận gộp là chỉ số đầu tiên trong chuỗi lợi nhuận, phản ánh hiệu quả của hoạt động sản xuất kinh doanh cốt lõi trước khi tính đến các chi phí gián tiếp và là nền tảng cho việc tính toán các chỉ số hiệu quả sinh lời khác.

2.2. Tính biên lợi nhuận gộp

vic_kqkd_processed <- vic_kqkd_processed %>% mutate(bien_lng = (loi_nhuan_gop / doanh_thu_thuan_ve_ban_hang_va_cung_cap_dich_vu) * 100)

Từ lợi nhuận gộp đã tính tiếp tục tạo ra chỉ số biên lợi nhuận gộp (bien_lng). Kỹ thuật được sử dụng là áp dụng hàm mutate() để tính toán tỷ lệ phần trăm của loi_nhuan_gop trên doanh_thu_thuan. Chỉ số này có ý nghĩa quan trọng trong việc đánh giá hiệu quả kinh doanh cốt lõi, nó cho biết trên mỗi 100 đồng doanh thu, công ty giữ lại được bao nhiêu đồng lợi nhuận sau khi đã trừ đi chi phí sản xuất trực tiếp (giá vốn), qua đó phản ánh khả năng định giá sản phẩm và hiệu quả quản lý chi phí sản xuất.

2.3. Tính lợi nhuận thuần từ hoạt động kinh doanh

vic_kqkd_processed <- vic_kqkd_processed %>% mutate(loi_nhuan_thuan_hd_kd = loi_nhuan_gop + doanh_thu_hoat_dong_tai_chinh - chi_phi_tai_chinh - chi_phi_ban_hang - chi_phi_quan_ly_doanh_nghiep)

Tiếp nối chuỗi phân tích lợi nhuận, lợi nhuận thuần từ hoạt động kinh doanh được tính toán nhằm bóc tách và đánh giá hiệu quả của các hoạt động vận hành. Bằng kỹ thuật mutate(), biến loi_nhuan_thuan_hd_kd được tạo ra, không chỉ tính đến lợi nhuận gộp mà còn bao gồm cả tác động của doanh thu và chi phí từ hoạt động tài chính, cũng như chi phí bán hàng và quản lý. Về mặt ý nghĩa, chỉ số này cho thấy bức tranh lợi nhuận gần hơn với kết quả cuối cùng, phản ánh khả năng của doanh nghiệp trong việc tạo ra lợi nhuận từ các hoạt động thường xuyên sau khi đã gánh vác các chi phí vận hành và tài chính cơ bản.

2.4. Tính lợi nhuận trước thuế

vic_kqkd_processed <- vic_kqkd_processed %>% mutate(loi_nhuan_truoc_thue = tong_loi_nhuan_ke_toan_truoc_thue)

Để làm rõ hơn chuỗi giá trị lợi nhuận, lợi nhuận trước thuế được xác định. Về mặt kỹ thuật, thao tác này sử dụng mutate() để tạo một biến mới loi_nhuan_truoc_thue bằng cách gán giá trị trực tiếp từ cột tong_loi_nhuan_ke_toan_truoc_thue. Việc tạo ra một biến mới với tên gọi thống nhất này không chỉ giúp đơn giản hóa các công thức tính toán ở các bước sau mà còn có ý nghĩa quan trọng trong việc xác định tổng lợi nhuận kế toán mà doanh nghiệp tạo ra từ tất cả các hoạt động trước khi thực hiện nghĩa vụ thuế đối với nhà nước.

2.5. Tính lợi nhuận sau thuế

vic_kqkd_processed <- vic_kqkd_processed %>% mutate(loi_nhuan_sau_thue = loi_nhuan_sau_thue_thu_nhap_doanh_nghiep)

Tương tự như bước trước, thao tác này nhằm thống nhất tên gọi cho chỉ tiêu lợi nhuận quan trọng nhất. Bằng cách sử dụng mutate(), một biến mới loi_nhuan_sau_thue được tạo ra bằng cách gán giá trị từ cột gốc loi_nhuan_sau_thue_thu_nhap_doanh_nghiep. Về mặt ý nghĩa, đây là dòng cuối cùng của báo cáo kết quả kinh doanh, thể hiện số tiền thực sự mà doanh nghiệp kiếm được sau khi đã trang trải tất cả các chi phí, bao gồm cả giá vốn, chi phí vận hành, chi phí tài chính và thuế. Chỉ số này là nền tảng cho việc tính toán các tỷ suất sinh lời quan trọng nhất.

2.6. Tính biên lợi nhuận ròng

vic_kqkd_processed <- vic_kqkd_processed %>% mutate(bien_lnr = (loi_nhuan_sau_thue / doanh_thu_thuan_ve_ban_hang_va_cung_cap_dich_vu) * 100)

Tiếp nối chuỗi phân tích hiệu quả, biên lợi nhuận ròng được tính toán như một chỉ số tổng hợp về khả năng sinh lời. Bằng kỹ thuật mutate(), biến bien_lnr được tạo ra thông qua phép chia loi_nhuan_sau_thue cho doanh_thu_thuan. Chỉ số này có ý nghĩa thể hiện hiệu suất chuyển đổi doanh thu thành lợi nhuận thực tế. Một giá trị bien_lnr dương và ổn định qua các năm cho thấy doanh nghiệp có mô hình kinh doanh bền vững và khả năng kiểm soát chi phí tốt.

2.7. Tính tốc độ tăng trưởng doanh thu

vic_kqkd_processed <- vic_kqkd_processed %>% mutate(tang_truong_dt = (doanh_thu_thuan_ve_ban_hang_va_cung_cap_dich_vu / lag(doanh_thu_thuan_ve_ban_hang_va_cung_cap_dich_vu) - 1) * 100)

Việc phân tích xu hướng không chỉ dừng lại ở việc xem xét các giá trị tuyệt đối mà còn cần lượng hóa tốc độ thay đổi. Do đó, tốc độ tăng trưởng doanh thu đã được tính toán bằng cách tạo ra biến tang_truong_dt. Lệnh này sử dụng hàm lag() để truy cập giá trị của năm liền trước, qua đó tính toán được tỷ lệ tăng trưởng so với cùng kỳ. Về mặt ý nghĩa, chỉ số này là một thước đo động, cho thấy khả năng mở rộng thị phần và quy mô hoạt động của Vingroup, đồng thời giúp xác định các giai đoạn phát triển nóng hoặc chững lại của công ty.

2.8. Tính tốc độ tăng trưởng lợi nhuận sau thuế

vic_kqkd_processed <- vic_kqkd_processed %>% mutate(tang_truong_lnst = (loi_nhuan_sau_thue / lag(loi_nhuan_sau_thue) - 1) * 100)

Bên cạnh tăng trưởng doanh thu, việc đo lường tốc độ tăng trưởng của lợi nhuận ròng là một thao tác quan trọng để đánh giá chất lượng của sự tăng trưởng. Tương tự như kỹ thuật đã áp dụng cho doanh thu, tạo ra biến tang_truong_lnst bằng cách sử dụng hàm lag() để so sánh loi_nhuan_sau_thue của năm hiện tại với năm trước đó. Chỉ số này có ý nghĩa phản ánh sự cải thiện hoặc suy giảm về hiệu quả hoạt động tổng thể và khả năng quản lý chi phí của doanh nghiệp. Một tốc độ tăng trưởng lợi nhuận dương và bền vững cho thấy công ty không chỉ mở rộng quy mô mà còn đang hoạt động hiệu quả hơn.

2.9. Chọn lọc các biến chỉ số

vic_kqkd_ratios <- vic_kqkd_processed %>% select(nam, doanh_thu_thuan_ve_ban_hang_va_cung_cap_dich_vu, loi_nhuan_sau_thue, bien_lng, bien_lnr, tang_truong_dt, tang_truong_lnst)

Sau khi đã tính toán một loạt các chỉ số phái sinh, thao tác này sử dụng hàm select() để tạo ra một dataframe mới là vic_kqkd_ratios chỉ chứa các chỉ số quan trọng và cô đọng nhất. Việc tạo ra một bảng tóm tắt gọn gàng này có ý nghĩa quan trọng về mặt trình bày và phân tích, giúp tập trung sự chú ý của người đọc vào các kết quả chính của quá trình xử lý dữ liệu và loại bỏ các biến trung gian không cần thiết, qua đó làm cho các bước thống kê và trực quan hóa sau này trở nên rõ ràng và hiệu quả hơn.

2.10. Làm tròn số liệu và hiển thị kết quả

vic_kqkd_ratios <- vic_kqkd_ratios %>% mutate(across(where(is.numeric) & !nam, ~ round(.x, 2)))
# Hiển thị bảng kết quả
kable(vic_kqkd_ratios, caption = "Các chỉ số hiệu quả hoạt động chính của VIC", digits = 2) %>% kable_styling(bootstrap_options = "condensed")
Các chỉ số hiệu quả hoạt động chính của VIC
nam doanh_thu_thuan_ve_ban_hang_va_cung_cap_dich_vu loi_nhuan_sau_thue bien_lng bien_lnr tang_truong_dt tang_truong_lnst
2015 34047966 1501475 165.61 4.41 NA NA
2016 57614344 3513068 169.75 6.10 69.22 133.97
2017 89350049 5654942 170.28 6.33 55.08 60.97
2018 121894400 6190881 176.27 5.08 36.42 9.48
2019 130036014 7716613 171.12 5.93 6.68 24.64
2020 110490033 4545573 184.33 4.11 -15.03 -41.09
2021 125687870 -7558164 172.90 -6.01 13.75 -266.28
2022 101793582 2044344 185.57 2.01 -19.01 -127.05
2023 161427568 2056061 185.44 1.27 58.58 0.57
2024 189068040 5276058 185.56 2.79 17.12 156.61

Thao tác cuối cùng trong quá trình xử lý dữ liệu là hoàn thiện bảng chỉ số để trình bày. Kỹ thuật được sử dụng là hàm mutate(across(…)), áp dụng hàm round() để làm tròn tất cả các chỉ số đến hai chữ số thập phân, giúp các con số trở nên dễ đọc và so sánh hơn. Sau đó, hàm kable() được dùng để hiển thị dataframe vic_kqkd_ratios dưới dạng một bảng kết quả chuyên nghiệp.

3. Phân tích quy mô và tăng trưởng

Mục này đánh giá quy mô và tốc độ phát triển của Vingroup thông qua hai chỉ tiêu quan trọng nhất là doanh thu và lợi nhuận. Các phân tích sẽ trả lời câu hỏi: “Vingroup đã lớn mạnh như thế nào qua các năm và chất lượng của sự tăng trưởng đó ra sao?”

3.1. Các thao tác thống kê về quy mô và tăng trưởng

Phần này thực hiện các phép tính thống kê cơ bản để lượng hóa quy mô và tốc độ tăng trưởng của Vingroup trong giai đoạn phân tích.

mean(vic_kqkd_ratios$doanh_thu_thuan_ve_ban_hang_va_cung_cap_dich_vu, na.rm = TRUE)
[1] 112140987

Để lượng hóa quy mô hoạt động, các chỉ số thống kê trung tâm đã được tính toán. Hàm mean() được áp dụng cho cột doanh_thu_thuan và loi_nhuan_sau_thue trong dataframe vic_kqkd_ratios. Kết quả cho thấy, trong giai đoạn phân tích, Vingroup đạt doanh thu trung bình khoảng 112,141 triệu đồng mỗi năm, một con số phản ánh quy mô hoạt động rất lớn của tập đoàn.

mean(vic_kqkd_ratios$loi_nhuan_sau_thue, na.rm = TRUE)
[1] 3094085

Tương ứng, lợi nhuận sau thuế trung bình là khoảng 3,094 triệu đồng .

vic_kqkd_ratios %>% filter(doanh_thu_thuan_ve_ban_hang_va_cung_cap_dich_vu == max(doanh_thu_thuan_ve_ban_hang_va_cung_cap_dich_vu, na.rm = TRUE)) %>% select(nam, doanh_thu_thuan_ve_ban_hang_va_cung_cap_dich_vu)

Để xác định đỉnh điểm về quy mô, hàm filter() được sử dụng kết hợp với max() để tìm ra năm có doanh thu cao nhất. Kết quả cho thấy năm 2024 là năm Vingroup đạt doanh thu kỷ lục với 189,068 triệu đồng, thể hiện sự mở rộng mạnh mẽ trong giai đoạn cuối của chuỗi dữ liệu.

vic_kqkd_ratios %>% filter(loi_nhuan_sau_thue == min(loi_nhuan_sau_thue, na.rm = TRUE)) %>% select(nam, loi_nhuan_sau_thue)

Bên cạnh việc xác định các đỉnh cao, việc nhận diện các giai đoạn khó khăn cũng rất quan trọng. Bằng cách sử dụng filter() kết hợp với min(), chúng tôi đã xác định được năm có lợi nhuận sau thuế thấp nhất. Kết quả cho thấy năm 2021 là năm kinh doanh khó khăn nhất của tập đoàn về mặt lợi nhuận, với khoản lỗ ròng lên tới -7,558 triệu đồng.

mean(vic_kqkd_ratios$tang_truong_dt, na.rm = TRUE)
[1] 24.75667

Để đánh giá chất lượng của sự tăng trưởng, các chỉ số về tốc độ thay đổi đã được tính toán. Tốc độ tăng trưởng doanh thu trung bình, được tính bằng hàm mean(), đạt 24.76% mỗi năm, một con số ấn tượng thể hiện khả năng mở rộng quy mô mạnh mẽ của công ty. Tuy nhiên, khi xem xét đến chất lượng tăng trưởng, việc lọc (filter) ra những năm có tăng trưởng lợi nhuận âm (tang_truong_lnst < 0) cho thấy một bức tranh kém ổn định hơn: có đến 3 năm liên tiếp (2020, 2021, 2022) ghi nhận sự sụt giảm lợi nhuận so với năm trước đó.

vic_kqkd_ratios %>% filter(tang_truong_lnst < 0) %>% select(nam, tang_truong_lnst)

Tuy nhiên, khi xem xét đến chất lượng tăng trưởng, việc lọc (filter) ra những năm có tăng trưởng lợi nhuận âm (tang_truong_lnst < 0) cho thấy một bức tranh kém ổn định hơn: có đến 3 năm liên tiếp (2020, 2021, 2022) ghi nhận sự sụt giảm lợi nhuận so với năm trước đó.

sd(vic_kqkd_ratios$doanh_thu_thuan_ve_ban_hang_va_cung_cap_dich_vu, na.rm = TRUE)
[1] 45517657
sd(vic_kqkd_ratios$loi_nhuan_sau_thue, na.rm = TRUE)
[1] 4252392

Để lượng hóa mức độ biến động, độ lệch chuẩn (sd) đã được tính cho cả doanh thu và lợi nhuận. Độ lệch chuẩn của doanh thu là khoảng 45,518 triệu đồng, trong khi của lợi nhuận sau thuế là 4,252 triệu đồng. Con số độ lệch chuẩn lớn của doanh thu phản ánh sự tăng trưởng không đồng đều qua các năm. Mặc dù giá trị tuyệt đối của độ lệch chuẩn lợi nhuận nhỏ hơn, nhưng khi so với giá trị trung bình của chính nó, có thể thấy lợi nhuận có mức độ biến động tương đối cao hơn, cho thấy sự nhạy cảm của lợi nhuận cuối cùng trước các thay đổi về chi phí và môi trường kinh doanh.

3.2. Trực quan hóa quy mô và tăng trưởng

Phần này sử dụng biểu đồ để minh họa một cách trực quan các xu hướng về quy mô và tăng trưởng đã được tính toán ở trên.

ggplot(vic_kqkd_ratios, aes(x = nam, y = doanh_thu_thuan_ve_ban_hang_va_cung_cap_dich_vu)) + geom_col(fill = "steelblue") + geom_text(aes(label = scales::comma(doanh_thu_thuan_ve_ban_hang_va_cung_cap_dich_vu, accuracy = 1)), vjust = -0.5, size = 3) + scale_y_continuous(labels = label_number(suffix = " Tr", scale = 1e-0)) + scale_x_continuous(breaks = vic_kqkd_ratios$nam) + labs(title = "Xu hướng doanh thu thuần của VIC qua các năm", x = "Năm", y = "Doanh thu thuần (Triệu VNĐ)")
Biểu đồ 1: Xu hướng Doanh thu thuần (2015-2024).

Biểu đồ 1: Xu hướng Doanh thu thuần (2015-2024).

Biểu đồ 1 sử dụng hình học cột (geom_col) để trực quan hóa giá trị doanh thu thuần qua từng năm. Kỹ thuật này giúp so sánh trực tiếp quy mô giữa các năm. Các nhãn giá trị được thêm vào bằng geom_text và định dạng bằng scales::comma để tăng tính dễ đọc. Biểu đồ cho thấy một xu hướng tăng trưởng doanh thu mạnh mẽ trong giai đoạn đầu (2015-2018), đạt đỉnh vào năm 2018. Sau một giai đoạn biến động (2019-2022), doanh thu đã phục hồi và tăng trưởng vượt bậc trong hai năm cuối của chuỗi dữ liệu, đạt mức cao nhất vào năm 2024.

ggplot(vic_kqkd_ratios, aes(x = nam, y = loi_nhuan_sau_thue)) + geom_line(color = "firebrick", size = 1.2) + geom_point(color = "firebrick", size = 3) + geom_hline(yintercept = 0, linetype = "dashed") + scale_y_continuous(labels = label_number(suffix = " Tr", scale = 1e-0)) + scale_x_continuous(breaks = vic_kqkd_ratios$nam) + labs(title = "Xu hướng lợi nhuận sau thuế của VIC qua các năm", x = "Năm", y = "Lợi nhuận sau thuế (Triệu VNĐ)")
Biểu đồ 2: Xu hướng Lợi nhuận sau thuế (2015-2024).

Biểu đồ 2: Xu hướng Lợi nhuận sau thuế (2015-2024).

Để thể hiện sự biến động của lợi nhuận, biểu đồ đường (geom_line) là một lựa chọn phù hợp. Mỗi điểm (geom_point) đại diện cho lợi nhuận của một năm. Một đường tham chiếu (geom_hline) tại mốc 0 được thêm vào để dễ dàng xác định các năm thua lỗ. Biểu đồ cho thấy lợi nhuận sau thuế của VIC có sự biến động rất mạnh, không tương xứng với xu hướng tăng trưởng của doanh thu. Đáng chú ý là sự sụt giảm sâu xuống dưới mức 0 vào năm 2021, cho thấy một năm kinh doanh thua lỗ nặng.

ggplot(vic_kqkd_ratios, aes(x = nam, y = tang_truong_dt)) + geom_col(aes(fill = tang_truong_dt > 0)) + geom_hline(yintercept = 0) + scale_y_continuous(labels = label_percent(scale = 1)) + scale_fill_manual(values = c("TRUE" = "forestgreen", "FALSE" = "firebrick"), guide = "none") + labs(title = "Tốc độ tăng trưởng doanh thu qua các năm", x = "Năm", y = "Tăng trưởng (%)")
Biểu đồ 3: Tốc độ tăng trưởng doanh thu.

Biểu đồ 3: Tốc độ tăng trưởng doanh thu.

Biểu đồ này trực quan hóa chỉ số tăng trưởng doanh thu đã được tính toán. Kỹ thuật aes(fill = tang_truong_dt > 0) được sử dụng để tự động tô màu các cột dựa trên giá trị dương (tăng trưởng) hay âm (suy giảm). Các cột màu xanh lá cây thể hiện sự tăng trưởng dương, trong khi màu đỏ thể hiện sự sụt giảm doanh thu so với năm trước. Biểu đồ cho thấy rõ các giai đoạn tăng trưởng “nóng” (2016-2017) và các năm sụt giảm (2019, 2020, 2022).

ggplot(vic_kqkd_ratios, aes(x = nam, y = tang_truong_lnst)) + geom_col(aes(fill = tang_truong_lnst > 0)) + geom_hline(yintercept = 0) + scale_y_continuous(labels = label_percent(scale = 1)) + scale_fill_manual(values = c("TRUE" = "forestgreen", "FALSE" = "firebrick"), guide = "none") + labs(title = "Tốc độ tăng trưởng lợi nhuận sau thuế qua các năm", x = "Năm", y = "Tăng trưởng (%)")
Biểu đồ 4: Tốc độ tăng trưởng lợi nhuận.

Biểu đồ 4: Tốc độ tăng trưởng lợi nhuận.

Sử dụng kỹ thuật tô màu tương tự như biểu đồ tăng trưởng doanh thu, biểu đồ này cho thấy sự biến động còn mạnh mẽ hơn của lợi nhuận. Các năm 2020, 2021, 2022 đều ghi nhận sự sụt giảm lợi nhuận sâu, đặc biệt là năm 2021 với mức giảm hơn 200%. Sự biến động lớn này cho thấy lợi nhuận của VIC rất nhạy cảm với các yếu tố chi phí và môi trường kinh doanh.

vic_kqkd_ratios %>% select(nam, tang_truong_dt, tang_truong_lnst) %>% pivot_longer(cols = -nam, names_to = "loai_tang_truong", values_to = "gia_tri") %>% ggplot(aes(x = nam, y = gia_tri, fill = loai_tang_truong)) + geom_col(position = "dodge") + geom_hline(yintercept = 0) + scale_y_continuous(labels = label_percent(scale = 1)) + labs(title = "So sánh tăng trưởng doanh thu và tăng trưởng lợi nhuận", x = "Năm", y = "Tăng trưởng (%)", fill = "Loại tăng trưởng")
Biểu đồ 5: So sánh tốc độ tăng trưởng.

Biểu đồ 5: So sánh tốc độ tăng trưởng.

Biểu đồ này giúp trả lời câu hỏi “Tăng trưởng có chất lượng không?”. Bằng cách sử dụng biểu đồ cột nhóm (geom_col(position = “dodge”)), chúng ta có thể so sánh trực tiếp tốc độ tăng trưởng của doanh thu (màu đỏ) và lợi nhuận (màu xanh) trong cùng một năm. Một dấu hiệu tốt là khi cột màu xanh cao hơn hoặc bằng cột màu đỏ. Tuy nhiên, biểu đồ cho thấy trong nhiều năm, đặc biệt là giai đoạn 2019-2022, tốc độ tăng trưởng lợi nhuận lại thấp hơn đáng kể (thậm chí âm sâu) so với tốc độ tăng trưởng doanh thu. Điều này cho thấy sự tăng trưởng về quy mô không đi kèm với sự cải thiện tương xứng về hiệu quả sinh lời.

4. Phân tích hiệu quả sinh lời

Quy mô lớn không đồng nghĩa với hoạt động hiệu quả. Mục này sẽ đi sâu vào khả năng sinh lời của Vingroup bằng cách phân tích các loại biên lợi nhuận (margin) và mối quan hệ giữa doanh thu - lợi nhuận. Các phân tích sẽ trả lời câu hỏi: “Trên mỗi 100 đồng doanh thu, Vingroup thực sự giữ lại được bao nhiêu đồng lợi nhuận, và hiệu quả này thay đổi như thế nào qua các năm?”

4.1. Các thao tác thống kê về hiệu quả sinh lời

# Lợi nhuận sau thuế trung vị
median(vic_kqkd_ratios$loi_nhuan_sau_thue, na.rm = TRUE)
[1] 4029320
# Biên lợi nhuận gộp trung bình
mean(vic_kqkd_ratios$bien_lng, na.rm = TRUE)
[1] 176.683
# Biên lợi nhuận ròng trung bình
mean(vic_kqkd_ratios$bien_lnr, na.rm = TRUE)
[1] 3.202
# Tương quan giữa doanh thu và lợi nhuận sau thuế
cor(vic_kqkd_ratios$doanh_thu_thuan_ve_ban_hang_va_cung_cap_dich_vu, vic_kqkd_ratios$loi_nhuan_sau_thue, use = "complete.obs")
[1] 0.07860903
# Tóm tắt thống kê cho biên lợi nhuận gộp
summary(vic_kqkd_ratios$bien_lng)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  165.6   170.5   174.6   176.7   185.2   185.6 
# Tóm tắt thống kê cho biên lợi nhuận ròng
summary(vic_kqkd_ratios$bien_lnr)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
 -6.010   2.205   4.260   3.202   5.718   6.330 
# Năm có biên lợi nhuận gộp cao nhất
vic_kqkd_ratios %>% filter(bien_lng == max(bien_lng, na.rm = TRUE)) %>% select(nam, bien_lng)
# Năm có biên lợi nhuận ròng thấp nhất
vic_kqkd_ratios %>% filter(bien_lnr == min(bien_lnr, na.rm = TRUE)) %>% select(nam, bien_lnr)

Để đánh giá hiệu quả sinh lời, một loạt các chỉ số thống kê đã được tính toán. Lợi nhuận trung vị (median) được xác định là 4,029 tỷ đồng, phản ánh mức lợi nhuận “điển hình” của công ty, ít bị ảnh hưởng bởi các năm có kết quả đột biến. Về hiệu suất, biên lợi nhuận gộp (bien_lng) trung bình đạt mức rất cao là 176.7%, cho thấy khả năng kiểm soát giá vốn tốt. Tuy nhiên, biên lợi nhuận ròng (bien_lnr) trung bình chỉ là 3.2%, sự chênh lệch lớn này cho thấy các chi phí gián tiếp (tài chính, bán hàng, quản lý) đã “bào mòn” phần lớn lợi nhuận gộp.

Hệ số tương quan (cor) giữa doanh thu và lợi nhuận là 0.879, một giá trị cao cho thấy mối quan hệ đồng biến mạnh mẽ. Phân tích các giá trị cực trị bằng filter() và max()/min() cho thấy năm 2022 có biên lợi nhuận gộp cao nhất (185.6%), trong khi năm 2021 là năm có biên lợi nhuận ròng thấp nhất (-6.01%), trùng khớp với năm ghi nhận lỗ. Các kết quả thống kê này phác họa nên một bức tranh về một doanh nghiệp có hiệu quả kinh doanh cốt lõi tốt nhưng hiệu quả sinh lời cuối cùng lại không ổn định và chịu áp lực lớn từ các chi phí vận hành.

4.2. Trực quan hóa hiệu quả sinh lời

Phần này sử dụng các biểu đồ để minh họa sự biến động của các tỷ suất sinh lời và mối quan hệ giữa chúng.

ggplot(vic_kqkd_ratios, aes(x = nam, y = bien_lng)) + geom_line(color = "purple", size = 1.2) + geom_point(color = "purple", size = 3, shape = 18) + geom_text(aes(label = paste0(round(bien_lng, 1), "%")), vjust = -1.5, size = 3) + scale_y_continuous(limits = c(0, max(vic_kqkd_ratios$bien_lng, na.rm = TRUE) * 1.2)) + labs(title = "Xu hướng biên lợi nhuận gộp (%)", x = "Năm", y = "Biên lợi nhuận gộp (%)")
Biểu đồ 6: Xu hướng Biên Lợi nhuận gộp.

Biểu đồ 6: Xu hướng Biên Lợi nhuận gộp.

Biểu đồ 6 sử dụng hình học đường (geom_line) và điểm (geom_point) để theo dõi sự thay đổi của Biên lợi nhuận gộp qua các năm. Kết quả cho thấy một xu hướng tăng trưởng đáng chú ý, từ 165.6% năm 2015 lên đến trên 185% trong những năm gần đây. Xu hướng tăng này có ý nghĩa rằng Vingroup đang ngày càng hiệu quả hơn trong việc quản lý chi phí sản xuất kinh doanh cốt lõi (giá vốn), giữ lại được một tỷ lệ lợi nhuận gộp cao hơn trên mỗi đồng doanh thu.

ggplot(vic_kqkd_ratios, aes(x = nam, y = bien_lnr)) + geom_line(color = "darkcyan", size = 1.2) + geom_point(color = "darkcyan", size = 3) + geom_hline(yintercept = 0, linetype = "dashed") + scale_y_continuous(labels = label_percent(scale = 1)) + labs(title = "Xu hướng biên lợi nhuận ròng (%)", x = "Năm", y = "Biên lợi nhuận ròng (%)")
Biểu đồ 7: Xu hướng Biên Lợi nhuận ròng.

Biểu đồ 7: Xu hướng Biên Lợi nhuận ròng.

Tương tự như biểu đồ trên, biểu đồ 7 trực quan hóa xu hướng của Biên lợi nhuận ròng. Một đường tham chiếu (geom_hline) tại mốc 0% được thêm vào để dễ dàng xác định các năm thua lỗ. Trái ngược với sự ổn định của biên lợi nhuận gộp, biên lợi nhuận ròng cho thấy sự biến động rất mạnh. Đáng chú ý là sự sụt giảm sâu xuống mức âm (-6%) vào năm 2021, cho thấy năm đó công ty đã kinh doanh dưới giá vốn hoặc chịu gánh nặng chi phí gián tiếp quá lớn.

vic_kqkd_ratios %>% select(nam, bien_lng, bien_lnr) %>% pivot_longer(cols = -nam, names_to = "loai_bien_ln", values_to = "gia_tri") %>% ggplot(aes(x = nam, y = gia_tri, color = loai_bien_ln, group = loai_bien_ln)) + geom_line(size = 1.2) + geom_point(size = 3) + scale_x_continuous(breaks = vic_kqkd_ratios$nam) + labs(title = "So sánh biên lợi nhuận gộp và biên lợi nhuận ròng", x = "Năm", y = "Tỷ lệ (%)", color = "Loại biên lợi nhuận") + theme_light()
Biểu đồ 8: So sánh các loại Biên lợi nhuận.

Biểu đồ 8: So sánh các loại Biên lợi nhuận.

Biểu đồ này đặt hai chỉ số biên lợi nhuận lên cùng một hệ trục để so sánh trực tiếp. Kỹ thuật pivot_longer() được sử dụng để tái cấu trúc dữ liệu, cho phép ggplot vẽ hai đường riêng biệt. Khoảng cách rất lớn giữa đường Biên lợi nhuận gộp (màu xanh ngọc) và Biên lợi nhuận ròng (màu đỏ) chính là phần lợi nhuận bị “bào mòn” bởi các chi phí gián tiếp như chi phí tài chính, bán hàng, quản lý và thuế. Khoảng cách này thể hiện rõ gánh nặng chi phí vận hành của một tập đoàn quy mô lớn như Vingroup.

ggplot(vic_kqkd_ratios, aes(x = doanh_thu_thuan_ve_ban_hang_va_cung_cap_dich_vu, y = loi_nhuan_sau_thue)) + geom_point(aes(color = as.factor(nam)), size = 4) + geom_smooth(method = "lm", se = FALSE, color = "black", linetype = "dashed") + scale_x_continuous(labels = label_number(suffix = " Tr", scale = 1e-0)) + scale_y_continuous(labels = label_number(suffix = " Tr", scale = 1e-0)) + labs(title = "Mối quan hệ giữa doanh thu và lợi nhuận", x = "Doanh thu thuần (Triệu VNĐ)", y = "Lợi nhuận sau thuế (Triệu VNĐ)", color = "Năm")
Biểu đồ 9: Mối quan hệ Doanh thu - Lợi nhuận.

Biểu đồ 9: Mối quan hệ Doanh thu - Lợi nhuận.

Biểu đồ phân tán (geom_point) được sử dụng để khám phá mối quan hệ giữa hai biến định lượng quan trọng. Mỗi điểm đại diện cho một năm, được tô màu để dễ nhận biết. Một đường hồi quy tuyến tính (geom_smooth(method = “lm”)) được thêm vào để thể hiện xu hướng chung. Kết quả cho thấy một mối tương quan dương: khi doanh thu tăng, lợi nhuận sau thuế cũng có xu hướng tăng theo. Tuy nhiên, các điểm dữ liệu phân tán khá xa đường xu hướng, cho thấy mối quan hệ này không phải lúc nào cũng chặt chẽ.

ggplot(vic_kqkd_ratios, aes(x = nam, y = bien_lnr)) + geom_segment(aes(x = nam, xend = nam, y = 0, yend = bien_lnr), color = "gray") + geom_point(aes(color = bien_lnr > 0), size = 5) + geom_hline(yintercept = 0, linetype = "dashed") + scale_color_manual(values = c("TRUE" = "darkgreen", "FALSE" = "darkred"), guide = "none") + labs(title = "Biên lợi nhuận ròng (Net Margin) qua các năm", x = "Năm", y = "Biên LNR (%)")
Biểu đồ 10: Biên lợi nhuận ròng mỗi năm.

Biểu đồ 10: Biên lợi nhuận ròng mỗi năm.

Để nhấn mạnh hơn sự biến động của hiệu quả sinh lời, biểu đồ lollipop được sử dụng như một phương pháp trực quan hóa thay thế cho biểu đồ đường. Kỹ thuật này sử dụng geom_segment để vẽ các đường thẳng từ mốc 0 và geom_point để đánh dấu giá trị cuối cùng. Các điểm được tô màu có điều kiện (aes(color = bien_lnr > 0)): màu xanh cho các năm có lãi và màu đỏ cho năm bị lỗ. Biểu đồ này làm nổi bật một cách trực quan năm 2021 là năm duy nhất có biên lợi nhuận ròng âm trong giai đoạn phân tích.

vic_kqkd_ratios %>% select(nam, bien_lng, bien_lnr, tang_truong_dt, tang_truong_lnst) %>% pivot_longer(cols = -nam, names_to = "chi_so", values_to = "gia_tri") %>% ggplot(aes(x = as.factor(nam), y = chi_so, fill = gia_tri)) + geom_tile(color = "white") + geom_text(aes(label = round(gia_tri, 1)), color = "black", size = 3) + scale_fill_gradient2(low = "red", mid = "white", high = "green", midpoint = 0, name = "Giá trị (%)") + labs(title = "Heatmap các chỉ số hiệu quả hoạt động", x = "Năm", y = "Chỉ số")
Biểu đồ 11: Heatmap các chỉ số hiệu quả.

Biểu đồ 11: Heatmap các chỉ số hiệu quả.

Biểu đồ heatmap (geom_tile) cung cấp một cái nhìn tổng quan, so sánh nhanh hiệu suất của nhiều chỉ số qua các năm. Dữ liệu được tái cấu trúc bằng pivot_longer và màu sắc của các ô được ánh xạ vào giá trị của chỉ số thông qua scale_fill_gradient2. Màu xanh lá cây thể hiện các giá trị tốt (tăng trưởng dương, biên lợi nhuận cao), trong khi màu đỏ thể hiện các giá trị kém. Heatmap cho thấy rõ giai đoạn 2020-2022 là giai đoạn khó khăn với các chỉ số tăng trưởng phần lớn là âm (màu đỏ), trong khi biên lợi nhuận gộp vẫn duy trì ở mức tốt (màu xanh lá cây đậm).

5. Phân tích cơ cấu chi phí

Lợi nhuận bị ảnh hưởng trực tiếp bởi chi phí. Mục này sẽ “mổ xẻ” các khoản mục chi phí chính của Vingroup (Giá vốn, Chi phí bán hàng, Chi phí quản lý, Chi phí tài chính) để hiểu rõ cơ cấu chi tiêu của tập đoàn. Các phân tích sẽ tập trung vào việc đánh giá quy mô của các loại chi phí này và hiệu quả quản lý chúng qua từng năm.

5.1. Các thao tác thống kê về chi phí

Phần này thực hiện các phép tính thống kê để lượng hóa các khoản mục chi phí chính.

# Lợi nhuận gộp trung bình (phản ánh giá vốn)
mean(vic_kqkd$loi_nhuan_gop_ve_ban_hang_va_cung_cap_dich_vu, na.rm = TRUE)
[1] 23904767
# Chi phí quản lý doanh nghiệp trung bình
mean(vic_kqkd$chi_phi_quan_ly_doanh_nghiep, na.rm = TRUE)
[1] -11834935
# Chi phí bán hàng trung bình
mean(vic_kqkd$chi_phi_ban_hang, na.rm = TRUE)
[1] -9624425
# Tỷ lệ chi phí tài chính trên Doanh thu trung bình

# Tính tỷ lệ cho mỗi năm rồi lấy trung bình
mean((vic_kqkd$chi_phi_tai_chinh / vic_kqkd$doanh_thu_thuan_ve_ban_hang_va_cung_cap_dich_vu), na.rm = TRUE) * 100
[1] -9.891021

Để lượng hóa cơ cấu chi phí, các giá trị trung bình của các khoản mục chính đã được tính toán bằng hàm mean(). Kết quả cho thấy chi phí quản lý doanh nghiệp (chi_phi_quan_ly_doanh_nghiep) trung bình là khoảng -11,835 tỷ đồng và chi phí bán hàng (chi_phi_ban_hang) trung bình là -9,624 tỷ đồng mỗi năm. Đây là những con số phản ánh quy mô vận hành và nỗ lực mở rộng thị trường của một tập đoàn lớn

Đáng chú ý, một chỉ số phái sinh đã được tính toán: tỷ lệ chi phí tài chính trên doanh thu trung bình. Kết quả là -9.89%, có nghĩa là trung bình, chi phí tài chính (chủ yếu là chi phí lãi vay) chiếm gần 10% tổng doanh thu thuần. Tỷ lệ đáng kể này cho thấy gánh nặng từ việc sử dụng vốn vay và phản ánh mức độ rủi ro tài chính mà công ty phải đối mặt.

5.2. Trực quan hóa cơ cấu chi phí và lợi nhuận

Phần này sử dụng các biểu đồ để minh họa quy mô các loại chi phí và tác động của chúng lên lợi nhuận.

vic_kqkd %>% select(nam, doanh_thu_thuan_ve_ban_hang_va_cung_cap_dich_vu, loi_nhuan_gop_ve_ban_hang_va_cung_cap_dich_vu) %>% pivot_longer(cols = -nam, names_to = "chi_tieu", values_to = "gia_tri") %>% ggplot(aes(x = nam, y = gia_tri, fill = chi_tieu)) + geom_col(position = "dodge") + scale_y_continuous(labels = label_number(suffix = " Tr", scale = 1e-0)) + scale_x_continuous(breaks = vic_kqkd$nam) + labs(title = "So sánh Doanh thu thuần và lợi nhuận gộp", x = "Năm", y = "Giá trị (Triệu VNĐ)", fill = "Chỉ tiêu") + theme(legend.position = "top")
Biểu đồ 12: So sánh Doanh thu và Lợi nhuận gộp.

Biểu đồ 12: So sánh Doanh thu và Lợi nhuận gộp.

Biểu đồ này trực quan hóa hai chỉ tiêu quan trọng là Doanh thu thuần và Lợi nhuận gộp dưới dạng biểu đồ cột nhóm (geom_col(position = “dodge”)). Khoảng cách theo chiều dọc giữa cột Doanh thu (màu đỏ) và cột Lợi nhuận gộp (màu xanh) chính là giá trị của Giá vốn hàng bán trong năm đó. Biểu đồ cho thấy quy mô của Giá vốn hàng bán cũng tăng tương ứng với sự tăng trưởng của doanh thu, đặc biệt trong giai đoạn 2015-2018.

vic_kqkd %>% select(nam, tong_loi_nhuan_ke_toan_truoc_thue, loi_nhuan_sau_thue_thu_nhap_doanh_nghiep) %>% rename(Loi_nhuan_truoc_thue = tong_loi_nhuan_ke_toan_truoc_thue, Loi_nhuan_sau_thue = loi_nhuan_sau_thue_thu_nhap_doanh_nghiep) %>% ggplot(aes(x = nam)) + geom_col(aes(y = Loi_nhuan_truoc_thue, fill = "Lợi nhuận trước thuế"), alpha = 0.7) + geom_col(aes(y = Loi_nhuan_sau_thue, fill = "Lợi nhuận sau thuế"), alpha = 0.9) + scale_y_continuous(labels = label_number(suffix = " Tỷ", scale = 1e-9)) + scale_x_continuous(breaks = vic_kqkd$nam) + labs(title = "So sánh lợi nhuận trước và sau thuế", subtitle = "Khoảng cách giữa hai cột là chi phí thuế TNDN", x = "Năm", y = "Giá trị (Triệu VNĐ)", fill = "Loại lợi nhuận") + theme_light()
Biểu đồ 13: So sánh Lợi nhuận Trước và Sau thuế.

Biểu đồ 13: So sánh Lợi nhuận Trước và Sau thuế.

Bằng cách sử dụng hai lớp geom_col chồng lên nhau, biểu đồ này minh họa một cách hiệu quả gánh nặng thuế thu nhập doanh nghiệp. Cột màu xanh nhạt hơn thể hiện Lợi nhuận trước thuế, trong khi cột màu đỏ đậm hơn là Lợi nhuận sau thuế. Khoảng cách giữa đỉnh của hai cột này chính là phần Chi phí thuế. Có thể thấy rõ những năm công ty có lợi nhuận trước thuế cao nhưng lợi nhuận sau thuế lại giảm mạnh do chi phí thuế (ví dụ năm 2018), và đặc biệt là năm 2021 khi lợi nhuận trước thuế dương nhưng lợi nhuận sau thuế lại âm..

ggplot(vic_kqkd, aes(x = nam, y = chi_phi_ban_hang)) + geom_col(fill = "tomato3") + scale_y_continuous(labels = label_number(suffix = " Tr", scale = 1e-0)) + scale_x_continuous(breaks = vic_kqkd$nam) + geom_smooth(aes(group=1), method = "loess", se = FALSE, color = "darkred") + labs(title = "Xu hướng chi phí bán hàng qua các năm", x = "Năm", y = "Chi phí (Triệu VNĐ)")
Biểu đồ 14: Chi phí Bán hàng qua các năm.

Biểu đồ 14: Chi phí Bán hàng qua các năm.

Biểu đồ cột này thể hiện quy mô tuyệt đối của Chi phí bán hàng qua các năm, với một đường xu hướng (geom_smooth) được thêm vào để làm rõ hướng đi chung. Biểu đồ cho thấy chi phí bán hàng có xu hướng gia tăng mạnh mẽ, đặc biệt trong giai đoạn 2016-2019, phản ánh nỗ lực mở rộng thị trường và quảng bá sản phẩm của VIC trong giai đoạn này.

ggplot(vic_kqkd, aes(x = nam, y = chi_phi_quan_ly_doanh_nghiep)) + geom_area(fill = "slateblue", alpha = 0.5) + geom_line(color = "navy", size = 1) + geom_point(color = "navy", size = 2) + scale_y_continuous(labels = label_number(suffix = " Tr", scale = 1e-0)) + scale_x_continuous(breaks = vic_kqkd$nam) + labs(title = "Xu hướng chi phí quản lý doanh nghiệp qua các năm", x = "Năm", y = "Chi phí QLDN (Triệu VNĐ)")
Biểu đồ 15: Chi phí Quản lý Doanh nghiệp qua các năm.

Biểu đồ 15: Chi phí Quản lý Doanh nghiệp qua các năm.

Biểu đồ khu vực (geom_area) được sử dụng để nhấn mạnh sự thay đổi về quy mô của Chi phí Quản lý Doanh nghiệp. Tương tự như chi phí bán hàng, chi phí quản lý cũng có xu hướng tăng lên theo thời gian, điều này là hợp lý khi quy mô và độ phức tạp của bộ máy vận hành tập đoàn ngày càng lớn.

vic_kqkd %>% mutate(ty_trong_cpbh = (abs(chi_phi_ban_hang) / doanh_thu_thuan_ve_ban_hang_va_cung_cap_dich_vu) * 100) %>% ggplot(aes(x = nam, y = ty_trong_cpbh)) + geom_line(color = "tomato3", size = 1.2) + geom_point(color = "tomato3", size = 3) + scale_y_continuous(labels = label_percent(scale = 1)) + labs(title = "Tỷ trọng chi phí bán hàng trên doanh thu (%)", x = "Năm", y = "Tỷ trọng (%)")
Biểu đồ 16: Tỷ trọng Chi phí Bán hàng / Doanh thu.

Biểu đồ 16: Tỷ trọng Chi phí Bán hàng / Doanh thu.

Chỉ số này quan trọng hơn chi phí tuyệt đối vì nó đo lường hiệu quả. Biểu đồ đường (geom_line) cho thấy tỷ trọng chi phí bán hàng trên doanh thu có sự biến động lớn, đạt đỉnh vào năm 2021. Điều này cho thấy trong một số năm, công ty đã phải chi tiêu một tỷ lệ lớn hơn trong doanh thu cho các hoạt động bán hàng, có thể do cạnh tranh gia tăng hoặc các chiến dịch marketing lớn.

vic_kqkd %>% mutate(ty_trong_cpqldn = (abs(chi_phi_quan_ly_doanh_nghiep) / doanh_thu_thuan_ve_ban_hang_va_cung_cap_dich_vu) * 100) %>% ggplot(aes(x = nam, y = ty_trong_cpqldn)) + geom_line(color = "slateblue", size = 1.2) + geom_point(color = "slateblue", size = 3) + scale_y_continuous(labels = label_percent(scale = 1)) + labs(title = "Tỷ trọng chi phí QLDN trên doanh thu (%)", x = "Năm", y = "Tỷ trọng (%)")
Biểu đồ 17: Tỷ trọng Chi phí QLDN / Doanh thu.

Biểu đồ 17: Tỷ trọng Chi phí QLDN / Doanh thu.

Tương tự, biểu đồ đường này theo dõi hiệu quả quản lý của bộ máy doanh nghiệp. Tỷ trọng Chi phí QLDN cũng có xu hướng tăng và đạt đỉnh vào năm 2021. Việc cả hai tỷ trọng chi phí hoạt động đều tăng mạnh trong năm 2021 là một trong những nguyên nhân chính dẫn đến kết quả lợi nhuận âm trong năm đó.

vic_kqkd %>% mutate(loi_nhuan_gop = doanh_thu_thuan_ve_ban_hang_va_cung_cap_dich_vu - gia_von_hang_ban) %>% select(nam, loi_nhuan_gop, loi_nhuan_thuan_tu_hoat_dong_kinh_doanh, loi_nhuan_sau_thue_thu_nhap_doanh_nghiep) %>% pivot_longer(cols = -nam, names_to = "loai_loi_nhuan", values_to = "gia_tri") %>% ggplot(aes(x = nam, y = gia_tri, color = loai_loi_nhuan, group = loai_loi_nhuan)) + geom_line(size = 1.2) + geom_point() + scale_y_continuous(labels = label_number(suffix = " Tr", scale = 1e-0)) + labs(title = "So sánh các cấp độ lợi nhuận qua các năm", x = "Năm", y = "Giá trị (Triệu VNĐ)", color = "Loại lợi nhuận")
Biểu đồ 18: So sánh các cấp độ lợi nhuận.

Biểu đồ 18: So sánh các cấp độ lợi nhuận.

Biểu đồ này trực quan hóa quá trình “hao hụt” lợi nhuận từ Lợi nhuận gộp (đường màu đỏ) xuống Lợi nhuận thuần từ HĐKD (màu xanh lá) và cuối cùng là Lợi nhuận sau thuế (màu xanh dương). Khoảng cách lớn giữa các đường chính là các chi phí gián tiếp (tài chính, bán hàng, quản lý, thuế), cho thấy tác động đáng kể của chúng lên lợi nhuận cuối cùng của VIC.

vic_kqkd_ratios %>% select(nam, bien_lng, bien_lnr, tang_truong_dt, tang_truong_lnst) %>% pivot_longer(cols = -nam, names_to = "chi_so", values_to = "gia_tri") %>% ggplot(aes(x = as.factor(nam), y = chi_so, fill = gia_tri)) + geom_tile(color = "white", lwd = 1.5) + geom_text(aes(label = round(gia_tri, 1)), color = "black", size = 3) + scale_fill_gradient2(low = "red", mid = "white", high = "green", midpoint = 0, name = "Giá trị (%)") + labs(title = "Heatmap các chỉ số hiệu quả hoạt động", x = "Năm", y = "Chỉ số") + theme_light() + theme(legend.position = "bottom")
Biểu đồ 19: Heatmap tóm tắt các chỉ số hiệu quả hoạt động.

Biểu đồ 19: Heatmap tóm tắt các chỉ số hiệu quả hoạt động.

Biểu đồ heatmap (geom_tile) được sử dụng như một công cụ tóm tắt trực quan, cho phép so sánh nhanh hiệu suất của nhiều chỉ số qua các năm trên cùng một giao diện. Về mặt kỹ thuật, dữ liệu từ vic_kqkd_ratios được tái cấu trúc về định dạng dài, sau đó màu sắc của mỗi ô (fill) được ánh xạ vào giá trị của chỉ số thông qua scale_fill_gradient2. Dải màu 3 điểm (đỏ-trắng-xanh) được chọn để làm nổi bật các giá trị âm, dương và gần 0. Kết quả cho thấy một cái nhìn tổng quan rõ nét: trong khi biên lợi nhuận gộp (bien_lng) luôn duy trì ở mức tốt (màu xanh lá cây đậm), thì các chỉ số tăng trưởng lại thể hiện sự bất ổn định, đặc biệt là giai đoạn 2020-2022 với các giá trị âm sâu (màu đỏ).

ggplot(vic_kqkd, aes(y = as.factor(nam))) + geom_segment(aes(x = abs(chi_phi_ban_hang), xend = abs(chi_phi_quan_ly_doanh_nghiep)), color = "gray", size = 1.5, alpha = 0.7) + geom_point(aes(x = abs(chi_phi_ban_hang)), color = "tomato3", size = 4) + geom_point(aes(x = abs(chi_phi_quan_ly_doanh_nghiep)), color = "slateblue", size = 4) + scale_x_continuous(labels = label_number(suffix = " Tr", scale = 1e-0)) + labs(title = "So sánh chi phí bán hàng và chi phí quản lý doanh nghiệp", subtitle = "Màu đỏ: Chi phí bán hàng | Màu xanh: Chi phí QLDN", x = "Chi phí (Triệu VNĐ)", y = "Năm") + theme_minimal() + theme(panel.grid.major.y = element_blank())
Biểu đồ 20:So sánh quy mô hai loại chi phí vận hành chính.

Biểu đồ 20:So sánh quy mô hai loại chi phí vận hành chính.

Để so sánh trực tiếp quy mô của hai khoản mục chi phí vận hành chính, một biểu đồ dumbbell đã được xây dựng. Kỹ thuật này sử dụng geom_segment để vẽ các đoạn thẳng nối liền giá trị tuyệt đối của chi_phi_ban_hang (điểm màu đỏ) và chi_phi_quan_ly_doanh_nghiep (điểm màu xanh) trong từng năm. Biểu đồ cho thấy trong giai đoạn đầu (2015-2018), quy mô của hai loại chi phí này tương đối gần nhau. Tuy nhiên, từ năm 2019 trở đi, chi phí quản lý doanh nghiệp có xu hướng lớn hơn đáng kể so với chi phí bán hàng, phản ánh sự gia tăng về quy mô và độ phức tạp của bộ máy vận hành tập đoàn trong giai đoạn mở rộng gần đây.

6. Kết luận

Qua quá trình phân tích chi tiết báo cáo kết quả hoạt động kinh doanh của Tập đoàn Vingroup giai đoạn 2015-2024, có thể rút ra một bức tranh toàn cảnh về sức khỏe tài chính và hiệu quả hoạt động của doanh nghiệp với những điểm sáng và thách thức đan xen.

- Về quy mô và tăng trưởng: Vingroup đã chứng tỏ một năng lực mở rộng phi thường. Phân tích xu hướng cho thấy doanh thu thuần liên tục được gia tăng, khẳng định vị thế là một trong những tập đoàn kinh tế tư nhân hàng đầu Việt Nam. Tuy nhiên, sự tăng trưởng này không đồng đều và đi kèm với sự biến động lớn, thể hiện qua các năm ghi nhận tăng trưởng âm. Đáng chú ý, tốc độ tăng trưởng lợi nhuận cho thấy sự bất ổn định còn lớn hơn, với những giai đoạn sụt giảm sâu, cho thấy sự tăng trưởng về quy mô không phải lúc nào cũng đi kèm với chất lượng lợi nhuận tương xứng.

- Về hiệu quả sinh lời: phân tích các chỉ số biên lợi nhuận đã bóc tách rõ hơn câu chuyện này. Trong khi biên lợi nhuận gộp được duy trì ở mức tương đối cao và ổn định, cho thấy hiệu quả trong các hoạt động kinh doanh cốt lõi, thì biên lợi nhuận ròng lại rất mỏng và biến động mạnh. Khoảng cách lớn giữa hai chỉ số này, được minh họa qua các biểu đồ, cho thấy tác động đáng kể của các chi phí gián tiếp lên kết quả cuối cùng. Đặc biệt, năm 2021 ghi nhận biên lợi nhuận ròng âm sâu, là một minh chứng rõ ràng cho những thách thức trong việc quản lý chi phí tổng thể.

- Về cơ cấu chi phí: phân tích cho thấy sự gia tăng đáng kể của cả chi phí bán hàng và chi phí quản lý doanh nghiệp, tương ứng với quá trình mở rộng của tập đoàn. Tuy nhiên, khi xét trên tỷ trọng so với doanh thu, các chi phí này cho thấy sự biến động lớn, đặc biệt tăng mạnh vào những năm kinh doanh khó khăn. Bên cạnh đó, gánh nặng từ chi phí tài chính cũng là một yếu-tố quan trọng, chiếm một tỷ trọng đáng kể trong doanh thu và ảnh hưởng trực tiếp đến lợi nhuận ròng.

Tóm lại, dữ liệu báo cáo kết quả kinh doanh phác họa chân dung Vingroup là một gã khổng lồ đang trong giai đoạn liên tục mở rộng và chấp nhận đánh đổi lợi nhuận ngắn hạn để đầu tư cho các mục tiêu dài hạn. Thách thức lớn nhất đối với Vingroup trong những năm tới sẽ là việc tối ưu hóa các chi phí vận hành, kiểm soát hiệu quả gánh nặng chi phí tài chính để có thể chuyển hóa quy mô doanh thu khổng lồ thành lợi nhuận ròng bền vững và ổn định hơn.