Phần 2. Phân tích mã chứng khoán VHM

1. Giới thiệu sơ bộ về bộ dữ liệu VHM

Phần này nhằm giúp người đọc hiểu rõ đặc điểm ban đầu của bộ dữ liệu tài chính của Công ty Cổ phần Vinhomes (mã VHM).
Tập dữ liệu gồm 10 biến tài chính chính, phản ánh tình hình tài sản, nguồn vốn, doanh thu, lợi nhuận và dòng tiền qua các năm.

Chúng ta sẽ tiến hành 10 thao tác cơ bản để khám phá dữ liệu.

1.1: Đọc dữ liệu từ file Excel

library(readxl)
data <- read_excel("D:/Tổng hợp VHM 10 năm.xlsx")
kable(head(data))
Năm Tài sản cố định Doanh thu thuần về bán hàng và cung cấp dịch vụ Tiền và tương đương tiền cuối kỳ Tổng tài sản (Total Assets) Tổng nợ phải trả (Total Liabilities) Vốn chủ sở hữu (Owner’s Equity) Lưu chuyển tiền thuần từ hoạt động kinh doanh TỔNG CỘNG NGUỒN VỐN Lợi nhuận thuần trong kỳ - Vốn cổ phần đã phát hành Đầu tư tài chính dài hạn
Quý 4/2024 2422908 73933080 26911424 440345620 303297525 137048095 34194095 344434522 2353343 34232424 65667652
Quý 3/2024 2486786 57992886 17225514 387697524 242935580 144761944 25329279 57992886 4311192 43543675 74332209
Quý 2/2024 2465029 28736782 15922396 367536265 235642660 131893605 -12410860 367536265 17531056 43543675 64664486
Quý 1/2024 2505731 7236152 8292350 345607839 345607839 122339595 3929740 345607839 7977046 43543675 61862108
Quý 4/2023 3400707 99352815 12914172 335398928 221082414 114316514 13838030 335398928 33301843 43543675 38321668
Quý 3/2023 3410553 91351969 2457809 306961607 192583165 114378442 11307391 306961607 33363772 43543675 36682539

Giải thích kỹ thuật (code)

library(readxl) tải package readxl — thư viện chuyên đọc file Excel (.xls/.xlsx) trong R.

data <- read_excel(“D:/Tổng hợp VHM 10 năm.xlsx”) đọc sheet mặc định (thường sheet đầu) từ file Excel và gán kết quả cho biến data. read_excel() tự động suy đoán kiểu cột (text, numeric, date, …).

head(data) hiển thị 6 dòng đầu của data. Vì read_excel() trả về một tibble (từ tidyverse), nên đầu ra in theo định dạng tibble: dòng tiêu đề, kích thước (6 x 12 ở đầu), kiểu dữ liệu từng cột trong hàng tiêu đề (, ), và một vài biến bị rút gọn tên nếu quá dài — kèm chú thích i abbreviated names: liệt kê tên đầy đủ (một vài chữ bị rút gọn trong hiển thị).

Nhận xét — giải thích kết quả chạy ra và ý nghĩa

Kích thước và cấu trúc:

Dòng ## # A tibble: 6 × 12 cho biết bạn đang xem 6 hàng đầu của một bảng có 12 biến (cột) toàn cục. Ảnh hiển thị 3 cột đầu (và phần chú thích cho biết còn 8 biến nữa). Kết luận trong tài liệu cũng nói “Dữ liệu đã được nhập thành công từ file Excel, có 10 biến tài chính chính” — ở đây có hơi khác nhỏ: tibble báo 12 cột (có thể gồm cả cột Năm và/hoặc cột bổ sung như chỉ mục), còn bạn tóm tắt là 10 biến tài chính (có thể bạn chỉ đếm biến tài chính, không tính cột năm và cột metadata).

Kiểu dữ liệu:

Cột Năm được đọc là (chuỗi ký tự). Hiện các giá trị dạng Quý 4/2024, Quý 3/2024… đều là text — nếu bạn muốn xử lý theo thời gian (sắp xếp theo thứ tự thời gian, trích quý/năm, vẽ chuỗi thời gian) nên chuyển sang dạng ngày/quarter hoặc tách thành hai cột Quý và Năm (int).

Các cột tài chính hiển thị — tức đã được đọc thành số thực (numeric). Con số lớn (ví dụ 73933080) hiện rõ là numeric, nên read_excel() đã nhận diện tốt dữ liệu số (không còn dấu phân cách hàng nghìn hoặc ký tự lạ).

Tên cột và hiển thị:

Một số tên cột quá dài nên khi in tibble bị rút gọn (ví dụ Doanh thu thuần về bán h…¹), và có chú thích i abbreviated names: liệt kê tên đầy đủ (ví dụ Doanh thu thuần về bán hàng và cung cấp dịch vụ). Đây là hành vi in mặc định của tibble, không phải lỗi.

Tên có ký tự Unicode tiếng Việt hiển thị đúng trong ảnh — tức R/knitr/HTML đã xử lý encoding ổn (không bị  hay ký tự lạ).

Nội dung mẫu dữ liệu:

Dữ liệu mẫu cho thấy 6 quan sát gần đây (Quý 4/2024 → Quý 3/2023). Lưu ý thứ tự quan sát có vẻ là giảm dần theo thời gian (bắt đầu từ Quý 4/2024 rồi Quý 3/2024…), bạn nên kiểm tra nếu cần sắp xếp tăng dần theo thời gian.

1.2: Xem cấu trúc tổng quát của bộ dữ liệu

dim(data)
## [1] 11 12
names(data)
##  [1] "Năm"                                            
##  [2] "Tài sản cố định"                                
##  [3] "Doanh thu thuần về bán hàng và cung cấp dịch vụ"
##  [4] "Tiền và tương đương tiền cuối kỳ"               
##  [5] "Tổng tài sản (Total Assets)"                    
##  [6] "Tổng nợ phải trả (Total Liabilities)"           
##  [7] "Vốn chủ sở hữu (Owner’s Equity)"                
##  [8] "Lưu chuyển tiền thuần từ hoạt động kinh doanh"  
##  [9] "TỔNG CỘNG NGUỒN VỐN"                            
## [10] "Lợi nhuận thuần trong kỳ"                       
## [11] "- Vốn cổ phần đã phát hành"                     
## [12] "Đầu tư tài chính dài hạn"

Giải thích kỹ thuật (code)

dim(data) trả về kích thước của data dưới dạng vector c(số hàng, số cột). Kết quả in là ## [1] 11 12, nghĩa là dataset hiện có 11 hàng (quan sát) và 12 cột (biến).

names(data) liệt kê tên đầy đủ của 12 cột hiện có trong dataframe. Ảnh cho thấy đầu ra của names() hiển thị chính xác 12 tên cột (bao gồm “Năm” và 11 cột khác liên quan đến các khoản mục tài chính).

Nhận xét — giải thích kết quả chạy ra và ý nghĩa

Kết quả thực tế: dataset của bạn có 11 quan sát và 12 biến (không phải 10). Đây là điều quan trọng cần ghi nhận vì trong phần kết luận bạn có ghi “10 biến” — đó là mâu thuẫn với kết quả dim()/names() hiện tại. Bạn nên kiểm tra lại xem tại sao có khác biệt (ví dụ: bạn trước đó đếm chỉ những biến “tài chính” và không tính Năm và một cột khác, hoặc có cột metadata/thừa).

Nội dung của names() (theo ảnh) cho thấy các cột gồm: “Năm” “Tài sản cố định” “Doanh thu thuần về bán hàng và cung cấp dịch vụ” “Tiền và tương đương tiền cuối kỳ” “Tổng tài sản (Total Assets)” “Tổng nợ phải trả (Total Liabilities)” “Vốn chủ sở hữu (Owner’s Equity)” “Lưu chuyển tiền thuần từ hoạt động kinh doanh” “TỔNG CỘNG NGUỒN VỐN” “Lợi nhuận thuần trong kỳ” “- Vốn cổ phần đã phát hành” “Đầu tư tài chính dài hạn” (danh sách trên là chính xác theo ảnh — tổng 12 tên).

Ý nghĩa kiểm tra nhanh:

dim() + names() là bước xác minh cơ bản và hữu ích — bạn đã thực hiện đúng khi dùng hai hàm này để “xem cấu trúc tổng quát”.

Vì names() trả về tên đầy đủ (không bị rút gọn như trong head() trước đó), bạn có cái nhìn rõ ràng về những biến thực sự có trong bảng.

Lưu ý: một số tên chứa khoảng trắng, ký tự đặc biệt hoặc ngoặc — có thể gây bất tiện khi lập code tiếp theo (truy cập bằng $ hay trong công thức). Nên cân nhắc chuẩn hóa tên cột (ví dụ janitor::clean_names() hoặc rename_with()).

1.3: Xem loại dữ liệu của từng biến

kable(str(data))
## tibble [11 × 12] (S3: tbl_df/tbl/data.frame)
##  $ Năm                                            : chr [1:11] "Quý 4/2024" "Quý 3/2024" "Quý 2/2024" "Quý 1/2024" ...
##  $ Tài sản cố định                                : num [1:11] 2422908 2486786 2465029 2505731 3400707 ...
##  $ Doanh thu thuần về bán hàng và cung cấp dịch vụ: num [1:11] 73933080 57992886 28736782 7236152 99352815 ...
##  $ Tiền và tương đương tiền cuối kỳ               : num [1:11] 26911424 17225514 15922396 8292350 12914172 ...
##  $ Tổng tài sản (Total Assets)                    : num [1:11] 4.40e+08 3.88e+08 3.68e+08 3.46e+08 3.35e+08 ...
##  $ Tổng nợ phải trả (Total Liabilities)           : num [1:11] 3.03e+08 2.43e+08 2.36e+08 3.46e+08 2.21e+08 ...
##  $ Vốn chủ sở hữu (Owner’s Equity)                : num [1:11] 1.37e+08 1.45e+08 1.32e+08 1.22e+08 1.14e+08 ...
##  $ Lưu chuyển tiền thuần từ hoạt động kinh doanh  : num [1:11] 34194095 25329279 -12410860 3929740 13838030 ...
##  $ TỔNG CỘNG NGUỒN VỐN                            : num [1:11] 3.44e+08 5.80e+07 3.68e+08 3.46e+08 3.35e+08 ...
##  $ Lợi nhuận thuần trong kỳ                       : num [1:11] 2353343 4311192 17531056 7977046 33301843 ...
##  $ - Vốn cổ phần đã phát hành                     : num [1:11] 34232424 43543675 43543675 43543675 43543675 ...
##  $ Đầu tư tài chính dài hạn                       : num [1:11] 65667652 74332209 64664486 61862108 38321668 ...

Giải thích kỹ thuật (code)

str(data) in cấu trúc đối tượng data (ở đây là một tibble), hiển thị kích thước ([11 x 12]), tên cột và kiểu dữ liệu mỗi cột cùng vài giá trị đầu tiên.

Kết quả cho thấy data là tibble [11 x 12] (11 quan sát, 12 biến). Dòng bắt đầu bằng ## $ Năm : chr [1:11] “Quý 4/2024” “Quý 3/2024” … nghĩa là cột Năm được đọc là kiểu chuỗi (character). Các cột còn lại được in với : num [1:11] … tức là kiểu numeric (double).

Nhận xét — giải thích kết quả chạy ra và ý nghĩa

Số quan sát và biến: thực tế dataset có 11 hàng và 12 cột, khớp với str() (xin lưu ý khác với kết luận trước nếu bạn viết “10 biến” — đó là mâu thuẫn cần làm rõ).

Kiểu dữ liệu:

Năm = chr → đang ở dạng text như “Quý 4/2024”. Nếu bạn cần xử lý theo thời gian (sắp xếp theo chuỗi thời gian, trích năm/quý hoặc làm biểu đồ chuỗi thời gian) nên tách hoặc chuyển sang dạng ngày/quý.

Phần lớn các khoản mục tài chính hiển thị là num (numeric) — đây là điều tốt, nghĩa là read_excel() đã parse số được. Các giá trị lớn được in ở dạng số thập phân hoặc ký hiệu khoa học (ví dụ 4.40e+08), là cách hiển thị của str() cho số rất lớn.

Một số quan sát giá trị cụ thể (đọc từ output):

Có giá trị âm xuất hiện (ví dụ trong Lưu chuyển tiền thuần từ hoạt động kinh doanh có -12410860), cho thấy một kỳ có dòng tiền thuần âm — đây là điểm quan trọng về tài chính cần kiểm tra (có phải dữ liệu thực hay do lỗi nhập).

Một số cột dường như lặp giá trị giống nhau nhiều quan sát (ví dụ - Vốn cổ phần đã phát hành hiển thị 34232424 43543675 43543675 43543675 … theo str()), bạn nên kiểm tra xem đó có phải thực tế (cổ phần không đổi) hay lỗi copy.

Ý nghĩa cho phân tích tiếp theo:

Vì hầu hết biến là numeric, bạn có thể trực tiếp tính toán/summary/biểu đồ — nhưng cần xác minh đơn vị (VND, triệu VND,…) để báo cáo đúng.

Cần chuẩn hóa tên cột (hiện có khoảng trắng, ký tự đặc biệt, ngoặc), vì dùng $ hoặc công thức trong mô hình sẽ khó khăn. Dùng janitor::clean_names() hoặc rename_with() để làm gọn.

Chú ý thứ tự thời gian: str() cho thấy các giá trị Năm bắt đầu từ Quý 4/2024 → có vẻ là thứ tự giảm dần; nếu muốn phân tích theo chuỗi thời gian nên sắp xếp lại tăng dần. Kết luận: Hầu hết các biến là dạng số, tuy nhiên cần chuẩn hóa lại để đảm bảo thống nhất định dạng.

1.4: Xem nhanh một phần dữ liệu ở cuối bảng

tail(data)
## # A tibble: 6 × 12
##   Năm        `Tài sản cố định` Doanh thu thuần về bán h…¹ Tiền và tương đương …²
##   <chr>                  <dbl>                      <dbl>                  <dbl>
## 1 Quý 3/2023           3410553                   91351969                2457809
## 2 Quý 2/2023           3427099                   58595781                3960431
## 3 Quý 1/2023           2652603                   28382695                1618383
## 4 Quý 4/2022           2697077                   46659406                9458740
## 5 Quý 3/2022           2719394                   22312039               10856846
## 6 Quý 2/2022           2600413                    6086718               24903439
## # ℹ abbreviated names: ¹​`Doanh thu thuần về bán hàng và cung cấp dịch vụ`,
## #   ²​`Tiền và tương đương tiền cuối kỳ`
## # ℹ 8 more variables: `Tổng tài sản (Total Assets)` <dbl>,
## #   `Tổng nợ phải trả (Total Liabilities)` <dbl>,
## #   `Vốn chủ sở hữu (Owner’s Equity)` <dbl>,
## #   `Lưu chuyển tiền thuần từ hoạt động kinh doanh` <dbl>,
## #   `TỔNG CỘNG NGUỒN VỐN` <dbl>, `Lợi nhuận thuần trong kỳ` <dbl>, …

Giải thích kỹ thuật (code)

tail(data) hiển thị 6 dòng cuối cùng của đối tượng data (ở đây data là một tibble). Khi knit sang HTML, kết quả in theo định dạng tibble: dòng tiêu đề cho biết kích thước phần hiển thị A tibble: 6 x 12, cột Năm được in là và các cột số khác là — giống như output của head() trước đó.

Vì tail() chỉ trả về các quan sát cuối cùng, nó hữu ích để kiểm tra dữ liệu có bị cắt ngang, thiếu quan sát ở cuối hay có giá trị bất thường ở phần cuối bảng.

Nhận xét — giải thích kết quả chạy ra và ý nghĩa

Kết quả thực tế: tail() in ra 6 hàng cuối của bảng gồm các Năm là Quý 3/2023, Quý 2/2023, Quý 1/2023, Quý 4/2022, Quý 3/2022, Quý 2/2022. Điều này cho thấy dữ liệu của bạn trải từ tối thiểu Quý 2/2022 lên đến Quý 4/2024 (đã thấy ở head()), và tail() đang hiển thị phần “cuối bảng” theo thứ tự lưu trong dataframe.

Tính liên tục / cắt ngang: Trong phần hiển thị tail() không thấy hàng rỗng hay dòng tiêu đề bị lẫn vào dữ liệu — tức dữ liệu ở cuối bảng trông đầy đủ, không bị cắt ngang (khớp với kết luận bạn viết).

Kiểu dữ liệu và nội dung: Năm vẫn là chr (chuỗi) — phù hợp với hiển thị dạng “Quý X/YYYY”. Các cột tài chính ở phần tail đều là số (), không thấy ký tự lạ trong phần in ra; tức read_excel() đã parse số thành numeric cho các quan hệ tài chính.

Vấn đề tiềm ẩn / cần kiểm tra thêm (dựa trên những gì tail() tiết lộ):

Thứ tự thời gian: head() cho thấy bắt đầu từ Quý 4/2024 rồi giảm dần, còn tail() hiển thị các quý cũ hơn (2023 → 2022). Nếu bạn muốn phân tích theo chuỗi thời gian tăng dần cần sắp xếp lại (ví dụ arrange(year, quarter) sau khi tách).

Độ phủ thời gian: tail() cho thấy dataset có các quý liên tiếp chứ không có khoảng nhảy lớn (ít nhất trong 6 quan sát cuối) — nhưng bạn vẫn nên kiểm tra toàn bộ cột Năm để đảm bảo không thiếu quý nào giữa đầu và cuối.

Tên cột dài: giống như head()/str() trước đó, tail() cũng hiển thị tên cột bị rút gọn (Doanh thu thuần về bán h…) và có chú thích i abbreviated names: — điều này chỉ là cơ chế in của tibble, không phải lỗi dữ liệu. Tuy nhiên để thao tác thuận tiện nên chuẩn hóa tên cột (ví dụ janitor::clean_names() hoặc rename_with()), vì tên hiện tại có dấu cách và ký tự đặc biệt.

Giá trị đặc biệt: tail() không hiển thị NA hay chuỗi báo lỗi trong phần in ra — nhưng việc này chỉ cho 6 dòng cuối; bạn vẫn cần chạy colSums(is.na(data)) và summary() để xác nhận không có missing hoặc outlier khác.

Về phần knit sang HTML: tail() được in rõ trong HTML, tiếng Việt hiển thị chuẩn, định dạng tibble và chú thích rút gọn tên hiện ra — chứng tỏ quá trình knit hoạt động bình thường và kết quả kiểm tra cuối bảng được trình bày đúng.

1.5: Tóm tắt thống kê sơ bộ các biến

kable(summary(data))
Năm Tài sản cố định Doanh thu thuần về bán hàng và cung cấp dịch vụ Tiền và tương đương tiền cuối kỳ Tổng tài sản (Total Assets) Tổng nợ phải trả (Total Liabilities) Vốn chủ sở hữu (Owner’s Equity) Lưu chuyển tiền thuần từ hoạt động kinh doanh TỔNG CỘNG NGUỒN VỐN Lợi nhuận thuần trong kỳ - Vốn cổ phần đã phát hành Đầu tư tài chính dài hạn
Length:11 Min. :2422908 Min. : 6086718 Min. : 1618383 Min. :185152395 Min. :129612854 Min. : 55539541 Min. :-12410860 Min. : 57992886 Min. : 1490610 Min. :34232424 Min. :32425466
Class :character 1st Qu.:2496259 1st Qu.:25347367 1st Qu.: 6126391 1st Qu.:264457231 1st Qu.:176802447 1st Qu.: 87191582 1st Qu.: 1766050 1st Qu.:240733348 1st Qu.: 6144119 1st Qu.:43543675 1st Qu.:36791283
Mode :character Median :2652603 Median :46659406 Median :10856846 Median :306961607 Median :192583165 Median :114316514 Median : 13838030 Median :280627329 Median :17048279 Median :43543675 Median :37539727
NA Mean :2798936 Mean :47330938 Mean :12229228 Mean :309307818 Mean :214175582 Mean :106254017 Mean : 18271776 Mean :270615478 Mean :16247101 Mean :42697198 Mean :47135293
NA 3rd Qu.:3060051 3rd Qu.:66264430 3rd Qu.:16573955 3rd Qu.:356572052 3rd Qu.:239289120 3rd Qu.:127116600 3rd Qu.: 33417802 3rd Qu.:339916725 3rd Qu.:24640753 3rd Qu.:43543675 3rd Qu.:63263297
NA Max. :3427099 Max. :99352815 Max. :26911424 Max. :440345620 Max. :345607839 Max. :144761944 Max. : 47157093 Max. :367536265 Max. :33363772 Max. :43543675 Max. :74332209

Giải thích kỹ thuật (code) Hàm summary(data) cho từng cột đưa ra tóm tắt thống kê: với cột character (ở đây Năm) hiển thị Length, Class, Mode; với cột numeric hiển thị Min., 1st Qu., Median, Mean, 3rd Qu., Max.. Vì data là một tibble, summary() in nối các cột theo hàng, nên bạn thấy mỗi mục tài chính kèm 6 số tóm tắt — đó là đầu vào hữu ích để nắm nhanh phân bố, xu hướng trung tâm và phạm vi biến.

Nhận xét — giải thích kết quả chạy ra và ý nghĩa

Kích thước mẫu nhỏ (11 quan sát) nên các thống kê tứ phân vị có thể biến động; vẫn dùng được để kiểm tra phân bố sơ bộ.

Các biến tài chính đều ở dạng numeric (summary hiển thị các chỉ số số học), ví dụ:

Tài sản cố định: Min = 2,422,908; 1st Qu. ≈ 2,496,259; Median ≈ 2,652,603; Mean ≈ 2,798,936; Max = 3,427,099 — giá trị tập trung tương đối hẹp cho tài sản cố định.

Doanh thu thuần về bán hàng và cung cấp dịch vụ: Min = 6,086,718; Median ≈ 46,659,406; Mean ≈ 47,330,938; Max = 99,352,815 — doanh thu có sự biến thiên lớn (khoảng từ ~6 triệu đến ~99 triệu), 1st và 3rd quartile cho thấy phân bố khá rộng.

Tiền và tương đương tiền cuối kỳ: Min = 1,618,383; Median ≈ 10,856,846; Mean ≈ 12,229,228; Max = 26,911,424 — cash biến động nhưng không quá lớn so với doanh thu/tài sản.

Tổng tài sản: Min ≈ 185,152,395; Median ≈ 306,961,607; Mean ≈ 309,307,818; Max ≈ 440,345,620 — quy mô doanh nghiệp lớn (hàng trăm triệu theo đơn vị hiện đang hiển thị).

Tổng nợ phải trả và Vốn chủ sở hữu: cả hai đều có giá trị lớn; mean thường khác median, cho thấy có độ bất đối xứng (thường mean > median cho nợ → có vài kỳ lớn kéo mean lên).

Lưu chuyển tiền thuần từ hoạt động kinh doanh: xuất hiện giá trị âm (Min = -12,410,860) ở ít nhất một kỳ — đây là điểm tài chính quan trọng cần kiểm tra (dòng tiền âm một kỳ).

  • Vốn cổ phần đã phát hành: nhiều tứ phân vị đều bằng nhau (ví dụ 1st Qu., Median, 3rd Qu., Max đều ≈ 43,543,675) — cho thấy vốn cổ phần hầu như không thay đổi qua các kỳ (ổn định).

Đầu tư tài chính dài hạn: Min ≈ 32,425,466; Max ≈ 74,332,209; mean ≈ 47,135,293 — đầu tư dài hạn có biến động qua các kỳ.

Một số hệ quả/điểm cần chú ý trước khi phân tích sâu:

Đơn vị số không được ghi trong output — cần xác nhận (VND, triệu VND, đơn vị khác) để diễn giải đúng quy mô.

Skew / outlier: vì nhiều mean khác median và range rất lớn (ví dụ doanh thu, tổng tài sản), dữ liệu có xu hướng bất đối xứng (right-skew ở một số biến). Với 11 quan sát, một vài quan sát lớn có thể kéo mean lên — cân nhắc dùng median hoặc log-transform khi phân tích.

Dòng tiền âm cần xác minh nguồn gốc (sai sót nhập liệu hay thực tế).

Cột vốn cổ phần cố định cho thấy không thay đổi theo thời gian — nếu đó là thực tế thì hợp lý; nếu không, cần kiểm tra dữ liệu gốc.

Tên cột dài và có khoảng trắng/ngoặc — bạn nên chuẩn hóa tên (ví dụ janitor::clean_names()) để thao tác thuận tiện trong phân tích tiếp theo.

Cỡ mẫu nhỏ (11 hàng) hạn chế sức mạnh thống kê; khi làm hồi quy hay kiểm định cần cân nhắc hệ số tin cậy/độ mạnh mẫu.

Về báo cáo/kết quả knit HTML: summary() được in đầy đủ, các giá trị số hiển thị rõ ràng (kể cả dấu âm cho cashflow). Trình bày tiếng Việt không bị lỗi hiển thị. Output này đủ để làm phần “kiểm tra nhanh phân bố” trong báo cáo, nhưng bạn nên bổ sung colSums(is.na(data)), plot hoặc histogram (có thể log-scale) cho những biến phẳng để minh họa phân bố và kiểm tra outlier. ### 1.6: Thống kê chi tiết bằng gói skimr

library(skimr)
skim(data)
Data summary
Name data
Number of rows 11
Number of columns 12
_______________________
Column type frequency:
character 1
numeric 11
________________________
Group variables None

Variable type: character

skim_variable n_missing complete_rate min max empty n_unique whitespace
Năm 0 1 10 10 0 11 0

Variable type: numeric

skim_variable n_missing complete_rate mean sd p0 p25 p50 p75 p100 hist
Tài sản cố định 0 1 2798936 405538.8 2422908 2496259 2652603 3060051 3427099 ▇▅▁▁▅
Doanh thu thuần về bán hàng và cung cấp dịch vụ 0 1 47330938 31987852.4 6086718 25347367 46659406 66264431 99352815 ▇▅▇▂▅
Tiền và tương đương tiền cuối kỳ 0 1 12229228 8466260.8 1618383 6126391 10856846 16573955 26911424 ▇▇▅▂▅
Tổng tài sản (Total Assets) 0 1 309307818 75050190.8 185152395 264457231 306961607 356572052 440345620 ▅▇▅▇▂
Tổng nợ phải trả (Total Liabilities) 0 1 214175582 64786483.2 129612854 176802447 192583165 239289120 345607839 ▃▇▆▁▃
Vốn chủ sở hữu (Owner’s Equity) 0 1 106254017 28447418.1 55539541 87191583 114316514 127116600 144761944 ▅▂▅▇▇
Lưu chuyển tiền thuần từ hoạt động kinh doanh 0 1 18271776 20073138.8 -12410860 1766051 13838030 33417803 47157093 ▅▇▂▇▅
TỔNG CỘNG NGUỒN VỐN 0 1 270615478 89963587.7 57992886 240733348 280627329 339916725 367536265 ▂▁▃▅▇
Lợi nhuận thuần trong kỳ 0 1 16247101 11747129.4 1490610 6144119 17048279 24640753 33363772 ▇▅▅▂▇
- Vốn cổ phần đã phát hành 0 1 42697198 2807447.8 34232424 43543675 43543675 43543675 43543675 ▁▁▁▁▇
Đầu tư tài chính dài hạn 0 1 47135293 15833399.4 32425466 36791283 37539727 63263297 74332209 ▇▁▁▃▁

Giải thích kỹ thuật (code)

library(skimr) tải package skimr; skim(data) gọi hàm skim để in báo cáo thống kê nâng cao cho từng biến.

Báo cáo gồm hai phần chính: (a) tổng quan dữ liệu (số hàng = 11, số cột = 12; phân bố kiểu cột: 1 character, 11 numeric, không có nhóm biến), (b) bảng chi tiết theo kiểu biến (character / numeric).

Ở phần character (Năm) skim in các chỉ số chất lượng: n_missing, complete_rate, min (số ký tự tối thiểu), max (số ký tự tối đa), n_unique (số giá trị khác nhau), whitespace (số giá trị có khoảng trắng dư). Ở phần numeric, skim trình bày n_missing, complete_rate, mean, sd, p0 (min), p25, p50 (median), p75, p100 (max) và một mini-histogram (sparkline).

Vì bạn knit ra HTML, skimr đã render bảng và các sparkline nhỏ vào tài liệu — thuận tiện để đọc nhanh cấu trúc, missing và phân bố.

Nhận xét — giải thích kết quả chạy ra và ý nghĩa

Không có giá trị thiếu: tất cả các biến đều n_missing = 0 và complete_rate = 1. Đây là điểm rất tốt — dữ liệu “đầy đủ” theo mặt thiếu giá trị.

Kích thước: chỉ có 11 quan sát nên mọi thống kê tứ phân vị sẽ có độ ổn định hạn chế; phân tích suy luận cần lưu ý cỡ mẫu nhỏ.

Cột Năm: n_unique = 11 (tức mỗi hàng có năm/quý khác nhau), không có whitespace thừa → thuận lợi để tách quarter/year tiếp theo. Tuy nhiên Năm đang là character (chưa parse thành date/quarter).

Các biến numeric có quy mô rất lớn và phân bố nghiêng:

Ví dụ điển hình (theo output):

Doanh thu thuần …: mean ≈ 47,330,938, sd ≈ 31,987,852, min ≈ 6,086,718, max ≈ 99,352,815 — cho thấy biến rất biến động, phân bố có đuôi phải (right-skew).

Tổng tài sản: mean ≈ 309,307,818, sd ≈ 75,050,190, min ≈ 185,152,395, max ≈ 440,345,620 — quy mô lớn, nhưng phân bố ít cực đoan hơn so với doanh thu.

Lưu chuyển tiền thuần từ hoạt động kinh doanh: min = -12,410,860 (xuất hiện giá trị âm), mean ≈ 18,271,776, sd ≈ 20,073,138 — có biến âm và biến động lớn, cần kiểm tra kỹ (có thể là dòng tiền âm thực tế chứ không phải lỗi).

  • Vốn cổ phần đã phát hành: các quartile (1st, median, 3rd, max) đều bằng nhau (~43,543,675) — cho thấy vốn cổ phần hầu như không đổi qua các kỳ (tính nhất quán hoặc cột tĩnh).

Mini-histogram (sparkline) cho thấy một số biến có phân bố lệch phải rõ (các cột có nhiều mass ở phía thấp + vài ô lớn ở phải). Với cỡ mẫu 11, những quan sát lớn có thể kéo mean lên — cân nhắc dùng median hoặc biến đổi log khi mô hình hóa.

Những việc cần làm tiếp (dựa trên kết quả skim):

Xác nhận đơn vị số (VND hay triệu VND). Hiển thị giá trị lớn nên cần biết đơn vị để diễn giải đúng.

Chuẩn hóa tên cột (loại khoảng trắng/ngoặc) để thao tác dễ: janitor::clean_names() hoặc rename_with(); tên hiện tại có dấu cách/ký tự đặc biệt gây khó khăn khi dùng $ hoặc trong công thức.

Chuyển Năm thành biến thời gian/quý (tách quarter và year hoặc map sang Date đại diện) rồi sắp xếp theo thời gian trước khi làm phân tích chuỗi.

Kiểm tra kỹ dòng tiền âm và các outlier trong doanh thu/tài sản: boxplot, plot, hoặc filter() ra các quan sát cực trị để xem căn nguyên.

Do phân bố lệch và tỉ lệ biến động lớn, cân nhắc log-transform các biến về giá trị dương (ví dụ doanh thu, tổng tài sản) trước khi chạy hồi quy OLS — hoặc dùng robust methods / scale variables.

Về phần RMarkdown → HTML: skimr đã chạy và render thành công; bảng rõ ràng, sparkline hiển thị; tiếng Việt hiển thị ổn. Báo cáo này rất hữu ích làm bước kiểm tra chất lượng dữ liệu trước phân tích tiếp theo. ### 1.7: Kiểm tra giá trị bị thiếu (NA)

colSums(is.na(data))
##                                             Năm 
##                                               0 
##                                 Tài sản cố định 
##                                               0 
## Doanh thu thuần về bán hàng và cung cấp dịch vụ 
##                                               0 
##                Tiền và tương đương tiền cuối kỳ 
##                                               0 
##                     Tổng tài sản (Total Assets) 
##                                               0 
##            Tổng nợ phải trả (Total Liabilities) 
##                                               0 
##                 Vốn chủ sở hữu (Owner’s Equity) 
##                                               0 
##   Lưu chuyển tiền thuần từ hoạt động kinh doanh 
##                                               0 
##                             TỔNG CỘNG NGUỒN VỐN 
##                                               0 
##                        Lợi nhuận thuần trong kỳ 
##                                               0 
##                      - Vốn cổ phần đã phát hành 
##                                               0 
##                        Đầu tư tài chính dài hạn 
##                                               0

Giải thích kỹ thuật (code)

is.na(data) tạo một ma trận/khung logic cùng kích thước với data (TRUE nếu ô đó là NA, FALSE nếu không).

colSums(is.na(data)) cộng (tính tổng) giá trị TRUE theo từng cột — vì trong R TRUE = 1 nên kết quả là số ô bị thiếu (NA) cho mỗi cột.

Khi knit sang HTML, output hiển thị danh sách tên cột và bên cạnh mỗi tên là giá trị trả về (ở ảnh bạn cho, mỗi cột đều hiển thị 0).

Nhận xét — giải thích kết quả chạy ra và ý nghĩa

Kết quả thực tế: mọi cột đều có giá trị 0 — tức không có ô nào bị NA trong toàn bộ bảng data. Đây là tín hiệu tốt: dữ liệu “đầy” về mặt NA, bạn không cần imputation cho NA trước các phép toán tính toán thống kê cơ bản.

Ý nghĩa thực tế: vì n_missing = 0 cho tất cả cột (khớp với output skimr trước đó), bạn có thể yên tâm thực hiện các tính toán như summary, vẽ biểu đồ, hay chạy mô hình mà không bị lỗi vì NA.

Tuy nhiên cần lưu ý các trường hợp thiếu giá trị không bị is.na() bắt (những điều colSums(is.na()) không cho biết):

Ô trống dạng chuỗi “” trong cột character;

Các giá trị text như “NA”, “na”, “-” hoặc “.” trong cột numeric (được đọc thành character và có thể gây lỗi nếu chưa convert);

Giá trị NaN hoặc Inf (hiếm nhưng khác NA);

Giá trị sentinel như -999 hoặc 0 dùng để biểu thị thiếu — những thứ này không bị is.na() bắt.

Vì vậy, mặc dù colSums(is.na(data)) cho thấy không có NA, bạn vẫn nên kiểm tra thêm:

colSums(data == ““, na.rm = TRUE) cho cột character để phát hiện chuỗi rỗng;

any(is.infinite(as.matrix(select_if(data, is.numeric)))) để kiểm tra Inf;

summary() / skim() (bạn đã dùng) để phát hiện chuỗi lạ / whitespace (skim đã báo whitespace = 0 cho cột Năm, điểm cộng);

Chạy str()/glimpse() và cố gắng convert các cột numeric về numeric rõ ràng (nếu có cột bị đọc nhầm thành chr).

Kết luận ngắn: báo cáo colSums(is.na(data)) cho thấy không có giá trị NA, đó là kết quả tốt và khớp với output skimr; bước tiếp theo là kiểm tra các dạng “thiếu” khác (chuỗi rỗng, text thay cho số, Inf, sentinel) và tiến hành chuẩn hoá tên cột, xử lý cột Năm (tách quarter/year) và kiểm tra outlier trước khi phân tích sâu.

1.8: Kiểm tra dữ liệu trùng lặp

anyDuplicated(data)
## [1] 0

Giải thích kỹ thuật (code)

anyDuplicated(data) kiểm tra xem có hàng nào trùng hệt toàn bộ các cột với một hàng khác hay không.

Hàm trả về vị trí (index) của bản ghi trùng lặp đầu tiên nếu có; nếu không có bản ghi trùng lặp thì trả về 0.

Khi bạn chạy trong RMarkdown và knit, kết quả hiển thị ## [1] 0 nghĩa là hàm đã thực thi và trả về 0.

Nhận xét (giải thích kết quả chạy ra, ý nghĩa)

Kết quả 0 cho biết không có hàng nào trùng hệt trong toàn bộ khung data. Đây là tín hiệu tốt: không có bản ghi trùng lặp 100% theo mọi cột, nên dữ liệu không bị duplicate do ghép file hay lỗi sao chép toàn hàng. ### 1.9: Xem số lượng giá trị duy nhất trong từng biến

lapply(data, function(x) length(unique(x)))
## $Năm
## [1] 11
## 
## $`Tài sản cố định`
## [1] 11
## 
## $`Doanh thu thuần về bán hàng và cung cấp dịch vụ`
## [1] 11
## 
## $`Tiền và tương đương tiền cuối kỳ`
## [1] 11
## 
## $`Tổng tài sản (Total Assets)`
## [1] 11
## 
## $`Tổng nợ phải trả (Total Liabilities)`
## [1] 11
## 
## $`Vốn chủ sở hữu (Owner’s Equity)`
## [1] 11
## 
## $`Lưu chuyển tiền thuần từ hoạt động kinh doanh`
## [1] 11
## 
## $`TỔNG CỘNG NGUỒN VỐN`
## [1] 11
## 
## $`Lợi nhuận thuần trong kỳ`
## [1] 11
## 
## $`- Vốn cổ phần đã phát hành`
## [1] 2
## 
## $`Đầu tư tài chính dài hạn`
## [1] 11

Giải thích kỹ thuật (Code) Hàm lapply: Đây là hàm áp dụng một phép tính (function) cho tất cả các phần tử trong một danh sách (list) hoặc một khung dữ liệu (data frame). Trong trường hợp này, nó lặp qua từng cột (biến) trong data.

Hàm ẩn danh function(x) length(unique(x)): Với mỗi cột x, hàm này tính toán số lượng (length) các giá trị duy nhất (unique) có trong cột đó.

Mục đích của đoạn code: Đoạn code này là một công cụ kiểm tra nhanh và hiệu quả để đánh giá tính đa dạng của dữ liệu trong từng biến. Nó giúp phát hiện:

Các biến phân loại (categorical) có bao nhiêu hạng mục.

Các biến số (numerical) có bị lặp lại giá trị hay không.

Các biến chỉ có 1 giá trị (hằng số), điều này thường là dấu hiệu của lỗi thu thập dữ liệu.

Cấu trúc của bộ dữ liệu (ví dụ: số năm quan sát). Giải thích và ý nghĩa

11 biến có 11 giá trị duy nhất: Điều này rất có thể cho thấy bộ dữ liệu của bạn chứa thông tin của 11 đơn vị quan sát (ví dụ: 11 công ty, 11 năm, hoặc 11 quý). Mỗi công ty/năm sẽ có một giá trị khác nhau cho các chỉ tiêu như “Tổng tài sản”, “Doanh thu”, “Lợi nhuận”. Kết quả này là hoàn toàn bình thường và phù hợp với một bộ dữ liệu dạng bảng (panel data) gọn gàng, không có sự trùng lặp bất thường nào ở các biến này.

Biến “- Vốn cố phân đã phát hành” có 2 giá trị duy nhất: Đây là điểm đáng chú ý. Biến này có thể là một biến phân loại hoặc định tính.

1.10: Xem tổng quan phân phối ban đầu của các biến chính

library(ggplot2)

# Sao chép dữ liệu gốc và đổi tên cột tạm thời để dễ vẽ
data_plot <- data %>%
  rename(
    TaiSanCoDinh = `Tài sản cố định`,
    DoanhThu = `Doanh thu thuần về bán hàng và cung cấp dịch vụ`,
    TongTaiSan = `Tổng tài sản (Total Assets)`,
    TongNo = `Tổng nợ phải trả (Total Liabilities)`,
    VonChuSoHuu = `Vốn chủ sở hữu (Owner’s Equity)`,
    LoiNhuan = `Lợi nhuận thuần trong kỳ`
  )

# Tạo danh sách các biến số cần vẽ
num_vars <- c("TaiSanCoDinh", "DoanhThu", "TongTaiSan",
              "TongNo", "VonChuSoHuu", "LoiNhuan")

# Vẽ histogram cho từng biến
for (v in num_vars) {
  p <- ggplot(data_plot, aes(x = .data[[v]])) +
    geom_histogram(fill = "steelblue", bins = 10, color = "white") +
    labs(
      title = paste("Phân phối ban đầu của", v),
      x = v,
      y = "Tần suất"
    ) +
    theme_minimal(base_size = 12) +
    theme(plot.title = element_text(hjust = 0.5, face = "bold"))
  print(p)
}

Giải thích kỹ thuật (Code) Mục tiêu: Đoạn code này nhằm trực quan hóa phân phối của các biến số chính trong dataset bằng biểu đồ histogram.

Thư viện: Code sử dụng thư viện ggplot2, một thư viện mạnh mẽ và phổ biến để vẽ đồ thị trong R.

Các bước thực hiện:

Đổi tên cột: Dòng code data_plot <- data %>% rename(…) tạo một bản sao của dataset và đổi tên các cột tiếng Việt có dấu và ký tự đặc biệt thành tên không dấu, đơn giản hơn. Điều này là rất quan trọng về mặt kỹ thuật vì tên cột phức tạp thường gây lỗi cú pháp khi tham chiếu trong các hàm vẽ đồ thị.

Danh sách biến: Một danh sách num_vars được tạo ra để chứa tên các biến số cần vẽ biểu đồ.

Vòng lặp for: Code sử dụng một vòng lặp để lần lượt duyệt qua từng biến trong num_vars, tạo một histogram cho biến đó và in ra.

Đánh giá Code:

Lỗi nghiêm trọng: Có một lỗi cú pháp trong đoạn code bạn cung cấp: aes(x = .data[[’v]])). Dấu nháy đơn bị thiếu (‘v’). Đoạn code đúng phải là aes(x = .data[[v]]) hoặc aes(x = .data[[‘v’]]). Tuy nhiên, vì bạn đã knit ra được HTML, có thể đây chỉ là lỗi đánh máy khi bạn chép code vào tài liệu, còn code thực tế khi chạy đã được sửa đúng.

Cách tiếp cận: Việc sử dụng vòng lặp for cùng với ggplot2 là một cách làm phổ biến. Một cách thanh lịch và mạnh mẽ hơn là sử dụng kỹ thuật “làm dài dữ liệu” (ví dụ với pivot_longer của tidyr) rồi vẽ nhiều biểu đồ cùng lúc bằng facet_wrap.

  1. Nhận xét về kết quả và ý nghĩa Kết quả chạy ra: Bạn đã cung cấp 3 hình ảnh histogram cho các biến TaiSanCoDinh, DoanhThu và TongNo. Tuy nhiên, các biểu đồ này đều bị lỗi hiển thị. Thay vì hiển thị các cột (bars) histogram, chúng ta chỉ thấy trục hoành (ghi tên biến và khoảng giá trị) và trục tung (ghi “Tân suất”). Phần thân của biểu đồ bị thiếu.

Giải thích và ý nghĩa

Nguyên nhân lỗi: Lỗi hiển thị này rất có thể là do kích thước dataset quá nhỏ. Ở phần 1.9 trước, chúng ta biết dataset chỉ có 11 quan sát. Hàm geom_histogram() mặc định cố gắng chia dữ liệu thành 10 khoảng (bins = 10). Với chỉ 11 quan sát, việc chia thành 10 khoảng sẽ tạo ra nhiều khoảng trống, và các cột histogram có thể rất mảnh, thậm chí không hiển thị được, hoặc bị ẩn đi.

Ý nghĩa của kết quả dự kiến: Nếu biểu đồ hiển thị đúng, nó sẽ cho chúng ta biết:

Hình dạng phân phối: Dữ liệu có phân phối chuẩn (hình chuông), lệch phải, lệch trái hay đều không?

Giá trị tập trung: Giá trị của các biến tập trung chủ yếu ở khoảng nào.

Giá trị ngoại lai (Outliers): Có sự tồn tại của các giá trị cực kỳ lớn hoặc cực kỳ nhỏ so với phần còn lại hay không?

Khuyến nghị:

Sửa code vẽ biểu đồ: Thay vì dùng geom_histogram(), bạn nên thử sử dụng geom_bar() cho dataset nhỏ. geom_bar() sẽ đếm tần suất cho từng giá trị duy nhất, phù hợp hơn với dữ liệu ít quan sát.

Giảm số bins: Nếu vẫn muốn dùng histogram, hãy giảm số bins xuống rất thấp, ví dụ bins = 5 hoặc thậm chí bins = 3.

Vẽ Boxplot: Với số lượng quan sát ít, một boxplot sẽ phù hợp hơn để mô tả phân phối, vì nó tóm tắt được trung vị, tứ phân vị và phát hiện ngoại lai một cách hiệu quả mà không phụ thuộc vào số lượng bins.

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

Mục tiêu của phần này là chuẩn bị dữ liệu để phục vụ cho các bước thống kê và trực quan hóa.
Chúng ta sẽ lần lượt thực hiện 10 thao tác xử lý chi tiết.

2.1: Đổi tên biến cho ngắn gọn và đồng nhất

data <- data %>%
  rename(
    FixedAssets = `Tài sản cố định`,
    Revenue = `Doanh thu thuần về bán hàng và cung cấp dịch vụ`,
    CashEnd = `Tiền và tương đương tiền cuối kỳ`,
    TotalAssets = `Tổng tài sản (Total Assets)`,
    TotalDebt = `Tổng nợ phải trả (Total Liabilities)`,
    Equity = `Vốn chủ sở hữu (Owner’s Equity)`,
    CFO = `Lưu chuyển tiền thuần từ hoạt động kinh doanh`,
    TotalCapital = `TỔNG CỘNG NGUỒN VỐN`,
    NetProfit = `Lợi nhuận thuần trong kỳ`,
    IssuedCapital = `- Vốn cổ phần đã phát hành`,
    LongTermInvest = `Đầu tư tài chính dài hạn`
  )

names(data)
##  [1] "Năm"            "FixedAssets"    "Revenue"        "CashEnd"       
##  [5] "TotalAssets"    "TotalDebt"      "Equity"         "CFO"           
##  [9] "TotalCapital"   "NetProfit"      "IssuedCapital"  "LongTermInvest"

Giải thích kỹ thuật (Code) Mục tiêu: Đoạn code này thực hiện việc chuẩn hóa tên biến, thay thế các tên cột gốc (thường dài và bằng tiếng Việt có dấu) bằng các tên mới ngắn gọn, bằng tiếng Anh và không có ký tự đặc biệt.

Hàm sử dụng: Code sử dụng hàm rename() từ gói dplyr (nằm trong tidyverse) để đổi tên các cột. Cú pháp là TênMới = ‘TênCũ’.

Toán tử Pipe %%: Đoạn code sử dụng toán tử %%. Đây có vẻ là một lỗi đánh máy. Toán tử pipe chuẩn trong R là %>%. Tuy nhiên, kết quả đầu ra cho thấy lệnh vẫn chạy thành công, có thể đây chỉ là lỗi khi bạn chép code vào tài liệu, còn code thực tế đã dùng %>%.

Lệnh names(data): Lệnh này được dùng để in ra danh sách tên cột mới của dataframe data, giúp kiểm tra kết quả của việc đổi tên.

Nhận xét về kết quả và ý nghĩa Kết quả chạy ra: Kết quả in ra từ lệnh names(data) cho thấy việc đổi tên đã thành công hoàn toàn. Tất cả 12 biến đều được liệt kê, trong đó 11 biến đã có tên mới bằng tiếng Anh (như FixedAssets, Revenue, TotalAssets…) và biến “Năm” vẫn được giữ nguyên. Điều này chứng tỏ đoạn code đã thực thi đúng như dự định.

2.2: Kiểm tra và loại bỏ giá trị bị thiếu

colSums(is.na(data)) # kiểm tra số lượng NA
##            Năm    FixedAssets        Revenue        CashEnd    TotalAssets 
##              0              0              0              0              0 
##      TotalDebt         Equity            CFO   TotalCapital      NetProfit 
##              0              0              0              0              0 
##  IssuedCapital LongTermInvest 
##              0              0
data <- data %>% drop_na()
nrow(data)
## [1] 11

Giải thích kỹ thuật (Code) Mục tiêu: Đoạn code này nhằm kiểm tra và xử lý các giá trị bị thiếu (NA - Not Available) trong bộ dữ liệu.

Hàm kiểm tra: colSums(is.na(data)) là một công cụ hiệu quả để kiểm tra nhanh số lượng giá trị thiếu trong từng cột.

is.na(data) trả về một ma trận TRUE/FALSE, trong đó TRUE biểu thị vị trí của giá trị NA.

colSums() tính tổng số lượng TRUE (tức là số NA) cho mỗi cột.

Hàm xử lý: data <- data %% drop_na() được sử dụng để loại bỏ các hàng có chứa bất kỳ giá trị NA nào.

Lưu ý: Toán tử %% trong kết quả của bạn có lẽ là lỗi đánh máy khi chép code vào tài liệu. Toán tử pipe chuẩn trong R là %>%. Code thực tế khi chạy có lẽ là data <- data %>% drop_na().

Hàm drop_na() từ gói dplyr (trong tidyverse) sẽ tự động loại bỏ bất kỳ hàng nào có chứa ít nhất một giá trị NA.

Kiểm tra kết quả: nrow(data) được dùng để in ra số hàng còn lại sau khi đã loại bỏ các hàng chứa NA, giúp xác nhận số lượng quan sát vẫn được giữ lại. Nhận xét về kết quả và ý nghĩa Kết quả chạy ra:

Kết quả của lệnh colSums(is.na(data)) không hiển thị bất kỳ con số nào cho các cột. Điều này ngầm hiểu rằng tổng số NA trong mỗi cột là 0. (Ví dụ, nếu có NA, kết quả sẽ hiện ra dạng Năm FixedAssets … 0 0 …).

Kết quả của lệnh nrow(data) là 11. Đây chính là tổng số quan sát ban đầu của dataset (như đã biết từ phần 1.9). ### 2.3: Chuẩn hóa định dạng số và loại bỏ dấu phẩy, ký tự

data <- data %>%
mutate(across(where(is.character), ~as.numeric(gsub(",", "", .))))

str(data)
## tibble [11 × 12] (S3: tbl_df/tbl/data.frame)
##  $ Năm           : num [1:11] NA NA NA NA NA NA NA NA NA NA ...
##  $ FixedAssets   : num [1:11] 2422908 2486786 2465029 2505731 3400707 ...
##  $ Revenue       : num [1:11] 73933080 57992886 28736782 7236152 99352815 ...
##  $ CashEnd       : num [1:11] 26911424 17225514 15922396 8292350 12914172 ...
##  $ TotalAssets   : num [1:11] 4.40e+08 3.88e+08 3.68e+08 3.46e+08 3.35e+08 ...
##  $ TotalDebt     : num [1:11] 3.03e+08 2.43e+08 2.36e+08 3.46e+08 2.21e+08 ...
##  $ Equity        : num [1:11] 1.37e+08 1.45e+08 1.32e+08 1.22e+08 1.14e+08 ...
##  $ CFO           : num [1:11] 34194095 25329279 -12410860 3929740 13838030 ...
##  $ TotalCapital  : num [1:11] 3.44e+08 5.80e+07 3.68e+08 3.46e+08 3.35e+08 ...
##  $ NetProfit     : num [1:11] 2353343 4311192 17531056 7977046 33301843 ...
##  $ IssuedCapital : num [1:11] 34232424 43543675 43543675 43543675 43543675 ...
##  $ LongTermInvest: num [1:11] 65667652 74332209 64664486 61862108 38321668 ...

Giải thích kỹ thuật (Code) Mục tiêu: Đoạn code này nhằm chuẩn hóa dữ liệu số bằng cách chuyển đổi các cột có định dạng ký tự (character) chứa dấu phẩy phân cách hàng nghìn thành định dạng số (numeric) để có thể thực hiện các phép tính toán.

Các hàm sử dụng:

mutate(across(…)): Đây là sự kết hợp mạnh mẽ trong dplyr để áp dụng cùng một phép biến đổi cho nhiều cột.

where(is.character): Điều kiện lọc, chỉ chọn những cột có kiểu dữ liệu là character.

~as.numeric(gsub(“,”, ““, .)): Đây là hàm ẩn danh được áp dụng cho mỗi cột:

gsub(“,”, ““, .): Tìm và thay thế tất cả dấu phẩy (”,“) bằng chuỗi rỗng (”“) trong dữ liệu.

as.numeric(): Chuyển đổi kết quả chuỗi đã được làm sạch thành kiểu số.

Lệnh str(data): Lệnh này dùng để kiểm tra cấu trúc của dataframe sau khi biến đổi, cho phép ta xác nhận kiểu dữ liệu của từng cột. Nhận xét về kết quả và ý nghĩa Kết quả chạy ra: Kết quả từ lệnh str(data) cho thấy:

Toàn bộ 12 cột trong dataframe đều có kiểu dữ liệu là num (numeric).

Cột Năm xuất hiện toàn giá trị NA. Đây là một vấn đề nghiêm trọng.

Các cột số khác (từ FixedAssets đến LongTermInvest) đều hiển thị các giá trị số cụ thể, chứng tỏ việc chuyển đổi định dạng đã thành công.

Giải thích và ý nghĩa

Thành công: Code đã thực hiện đúng chức năng dự định của nó. Các cột chứa số (vốn có thể đang ở dạng character do dấu phẩy) đã được chuyển đổi hoàn toàn sang kiểu numeric. Điều này là cực kỳ quan trọng vì nó cho phép bạn thực hiện tất cả các phép tính toán, phân tích thống kê và vẽ biểu đồ một cách chính xác.

Vấn đề nghiêm trọng với cột Năm:

Nguyên nhân: Việc cột Năm trở thành toàn NA cho thấy dữ liệu gốc của cột này có định dạng không phù hợp. Có thể nó chứa các ký tự không phải là số (ví dụ: “Năm 2020”, “2020*“) hoặc định dạng ngày tháng phức tạp mà hàm as.numeric() không thể xử lý trực tiếp.

Hậu quả: Cột Năm đã mất hoàn toàn thông tin. Đây là một tổn thất lớn vì biến “Năm” thường đóng vai trò là biến định danh quan trọng, giúp theo dõi sự biến động của các chỉ số theo thời gian. ### 2.4: Sắp xếp dữ liệu theo thứ tự thời gian (nếu có biến “Năm”)

if("Năm" %in% names(data)) {
data <- arrange(data, Năm)
}
head(data)
## # A tibble: 6 × 12
##     Năm FixedAssets  Revenue  CashEnd TotalAssets TotalDebt    Equity       CFO
##   <dbl>       <dbl>    <dbl>    <dbl>       <dbl>     <dbl>     <dbl>     <dbl>
## 1    NA     2422908 73933080 26911424   440345620 303297525 137048095  34194095
## 2    NA     2486786 57992886 17225514   387697524 242935580 144761944  25329279
## 3    NA     2465029 28736782 15922396   367536265 235642660 131893605 -12410860
## 4    NA     2505731  7236152  8292350   345607839 345607839 122339595   3929740
## 5    NA     3400707 99352815 12914172   335398928 221082414 114316514  13838030
## 6    NA     3410553 91351969  2457809   306961607 192583165 114378442  11307391
## # ℹ 4 more variables: TotalCapital <dbl>, NetProfit <dbl>, IssuedCapital <dbl>,
## #   LongTermInvest <dbl>

Giải thích kỹ thuật (Code) Mục tiêu: Đoạn code này nhằm sắp xếp các quan sát trong bộ dữ liệu theo thứ tự thời gian tăng dần của biến “Năm”. Đây là một bước quan trọng để đảm bảo tính logic của chuỗi thời gian.

Hàm sử dụng:

if(“Năm” %in% names(data)) { … }: Đây là một biện pháp phòng ngừa thông minh. Nó kiểm tra xem biến “Năm” có tồn tại trong dataset hay không trước khi thực hiện sắp xếp. Nếu không có, đoạn code bên trong sẽ không chạy, tránh gây lỗi.

arrange(data, Năm): Hàm này từ gói dplyr được sử dụng để sắp xếp dataframe data theo thứ tự tăng dần của cột Năm.

Lệnh head(data): Lệnh này in ra 6 hàng đầu tiên của dataframe sau khi sắp xếp, giúp người dùng kiểm tra kết quả.. Giải thích kỹ thuật (Code) Mục tiêu: Đoạn code này nhằm sắp xếp các quan sát trong bộ dữ liệu theo thứ tự thời gian tăng dần của biến “Năm”. Đây là một bước quan trọng để đảm bảo tính logic của chuỗi thời gian.

Hàm sử dụng:

if(“Năm” %in% names(data)) { … }: Đây là một biện pháp phòng ngừa thông minh. Nó kiểm tra xem biến “Năm” có tồn tại trong dataset hay không trước khi thực hiện sắp xếp. Nếu không có, đoạn code bên trong sẽ không chạy, tránh gây lỗi.

arrange(data, Năm): Hàm này từ gói dplyr được sử dụng để sắp xếp dataframe data theo thứ tự tăng dần của cột Năm.

Lệnh head(data): Lệnh này in ra 6 hàng đầu tiên của dataframe sau khi sắp xếp, giúp người dùng kiểm tra kết quả. Nhận xét về kết quả và ý nghĩa Kết quả chạy ra: Kết quả từ lệnh head(data) cho thấy một vấn đề rõ ràng: cột Năm có giá trị NA cho tất cả 6 hàng được hiển thị. Điều này có nghĩa là toàn bộ cột “Năm” trong dataset của bạn đang bị thiếu giá trị.

2.5: Tạo biến tăng trưởng doanh thu (%)

data <- data %>%
mutate(RevenueGrowth = round((Revenue/lag(Revenue) - 1)*100, 2))
head(data$RevenueGrowth)
## [1]      NA  -21.56  -50.45  -74.82 1273.01   -8.05

Giải thích kỹ thuật (Code) Mục tiêu: Đoạn code này tạo ra một biến mới có tên RevenueGrowth (Tăng trưởng doanh thu %), một chỉ số tài chính quan trọng để đánh giá tốc độ phát triển của doanh nghiệp.

Hàm sử dụng:

mutate(): Hàm từ gói dplyr dùng để tạo cột mới.

lag(Revenue): Đây là hàm then chốt. Nó lấy giá trị doanh thu (Revenue) của hàng trước đó (theo thứ tự hiện tại trong dataframe) để so sánh với hàng hiện tại. Điều này giả định dữ liệu đã được sắp xếp theo thời gian.

round(…, 2): Làm tròn kết quả đến 2 chữ số thập phân. Nhận xét về kết quả và ý nghĩa Kết quả chạy ra: Kết quả của head(data$RevenueGrowth) là: NA -21.56 -50.45 -74.82 1273.01 -8.05

Giải thích và ý nghĩa:

Giá trị đầu tiên là NA: Đây là kết quả đúng và dự kiến. Vì hàng đầu tiên trong dataset không có hàng nào đứng trước nó để lấy giá trị lag(Revenue), phép tính không thể thực hiện và trả về NA.

Biến động mạnh mẽ về hiệu suất: Các giá trị còn lại cho thấy sự biến động cực kỳ lớn trong hiệu suất kinh doanh của doanh nghiệp:

Giai đoạn suy giảm mạnh: Ba giá trị tiếp theo (-21.56%, -50.45%, -74.82%) cho thấy doanh thu sụt giảm nghiêm trọng trong ba kỳ liên tiếp. Đây là một dấu hiệu đáng báo động.

Giai đoạn tăng trưởng đột biến: Giá trị +1273.01% là một con số khổng lồ, cho thấy doanh thu của kỳ đó đã tăng gấp hơn 12 lần so với kỳ trước. Sự phục hồi thần kỳ này là rất bất thường.

Giai đoạn điều chỉnh: Giá trị -8.05% cuối cùng cho thấy sau kỳ tăng trưởng đột biến, doanh thu lại quay đầu giảm nhẹ.

2.6: Tạo biến tăng trưởng lợi nhuận (%)

data <- data %>%
mutate(NetProfitGrowth = round((NetProfit/lag(NetProfit) - 1)*100, 2))
head(data$NetProfitGrowth)
## [1]     NA  83.19 306.64 -54.50 317.47   0.19

Giải thích kỹ thuật (Code) Mục tiêu: Đoạn code này tạo ra một biến mới có tên NetProfitGrowth (Tăng trưởng lợi nhuận ròng %), một chỉ số then chốt để đánh giá hiệu quả sinh lời và khả năng kiểm soát chi phí của doanh nghiệp.

Hàm sử dụng:

mutate(): Hàm từ gói dplyr dùng để tạo cột mới.

lag(NetProfit): Hàm lấy giá trị lợi nhuận ròng (NetProfit) của kỳ trước để so sánh với kỳ hiện tại. Việc này giả định dữ liệu đã được sắp xếp theo trình tự thời gian.

(NetProfit/lag(NetProfit) - 1)*100: Công thức tính phần trăm tăng trưởng.

round(…, 2): Làm tròn kết quả đến 2 chữ số thập phân. Nhận xét về kết quả và ý nghĩa Kết quả chạy ra: Kết quả của head(data$NetProfitGrowth) là: NA 83.19 306.64 -54.50 317.47 0.19

Giải thích và ý nghĩa:

Giá trị đầu tiên là NA: Đây là kết quả đúng và bình thường. Hàng đầu tiên không có dữ liệu của kỳ trước để so sánh nên kết quả là NA.

Biến động cực kỳ mạnh và bất thường: Các giá trị tăng trưởng lợi nhuận cho thấy sự bất ổn định nghiêm trọng trong hiệu quả hoạt động của doanh nghiệp:

Tăng trưởng “thần tốc”: Các mức tăng trưởng +83.19%, +306.64%, và đặc biệt là +317.47% là cực kỳ cao và hiếm gặp trong thực tế. Chúng cho thấy lợi nhuận có thể đã tăng gấp 3, thậm chí hơn 4 lần so với kỳ trước.

Sự sụt giảm đột ngột: Giữa các đợt tăng trưởng thần tốc là một đợt sụt giảm mạnh -54.50%.

Ổn định đột ngột: Giá trị cuối cùng +0.19% cho thấy lợi nhuận gần như không đổi, một sự ổn định trái ngược hoàn toàn với sự biến động dữ dội của các kỳ trước đó.

Ý nghĩa tổng quan và cảnh báo:

Code hoạt động chính xác: Về mặt kỹ thuật, code đã thực hiện đúng phép tính.

Dấu hiệu nghiêm trọng về chất lượng dữ liệu hoặc thứ tự sắp xếp: Sự biến động “điên rồ” này của lợi nhuận, kết hợp với kết quả tăng trưởng doanh thu bất thường ở bước trước (+1273%), củng cố cho một kết luận quan trọng: Thứ tự các quan sát trong dataset của bạn rất có thể đã bị xáo trộn và không theo trình tự thời gian thực tế.

Việc tính toán tăng trưởng (vốn phụ thuộc hoàn toàn vào thứ tự của các hàng) trên một dataset bị xáo trộn sẽ tạo ra các con số tăng/giảm ngẫu nhiên, cực đoan và vô nghĩa.

Vấn đề cốt lõi chưa được giải quyết: Kết quả này một lần nữa nhấn mạnh sự cấp thiết của việc phải khôi phục và sắp xếp đúng cột “Năm” như đã chỉ ra ở các bước 2.4 và 2.5. Không có thôn

2.7: Tính toán các chỉ số tài chính ROE, ROA

data <- data %>%
mutate(
ROE = round(NetProfit / Equity * 100, 2),
ROA = round(NetProfit / TotalAssets * 100, 2)
)

summary(select(data, ROE, ROA))
##       ROE             ROA        
##  Min.   : 1.72   Min.   : 0.530  
##  1st Qu.: 4.75   1st Qu.: 1.710  
##  Median :13.29   Median : 4.770  
##  Mean   :16.12   Mean   : 5.529  
##  3rd Qu.:26.55   3rd Qu.: 8.890  
##  Max.   :33.53   Max.   :10.870

Giải thích kỹ thuật (Code) Mục tiêu: Đoạn code này nhằm tính toán hai chỉ số tài chính quan trọng bậc nhất là ROE (Lợi nhuận trên Vốn chủ sở hữu) và ROA (Lợi nhuận trên Tổng tài sản).

Hàm sử dụng:

mutate(): Được sử dụng để tạo đồng thời hai cột mới là ROE và ROA.

Công thức tính:

ROE = (NetProfit / Equity) * 100: Cho biết mỗi đồng vốn chủ sở hữu tạo ra được bao nhiêu đồng lợi nhuận. Đây là thước đo hiệu quả sử dụng vốn của chủ sở hữu.

ROA = (NetProfit / TotalAssets) * 100: Cho biết mỗi đồng tài sản của công ty tạo ra được bao nhiêu đồng lợi nhuận. Đây là thước đo hiệu quả sử dụng toàn bộ tài sản.

round(…, 2): Làm tròn kết quả đến 2 chữ số thập phần.

summary(select(data, ROE, ROA)): Lệnh này tóm tắt thống kê cho hai cột vừa tạo, cung cấp cái nhìn tổng quan về phạm vi, xu hướng trung tâm và sự phân bố của các chỉ số. Nhận xét về kết quả và ý nghĩa Kết quả chạy ra: Kết quả từ hàm summary() cho thấy:

ROE: Giá trị từ 1.72% đến 33.53%, với trung bình là 16.12% và trung vị là 13.29%.

ROA: Giá trị từ 0.53% đến 10.87%, với trung bình là 5.53% và trung vị là 4.77%.

Giải thích và ý nghĩa:

Code hoàn toàn chính xác: Code đã được thực thi thành công, tính toán ra các giá trị ROE và ROA một cách chính xác. Đây là bước đầu tiên trong phân tích của bạn cho ra kết quả đáng tin cậy và có ý nghĩa thực tế, vì các chỉ số này được tính từ các cặp số liệu trong cùng một kỳ (lợi nhuận/vốn, lợi nhuận/tài sản) và không phụ thuộc vào thứ tự thời gian.

Hiệu quả sinh lời ở mức khá:

ROE trung bình 16.12% là một tỷ lệ khá tốt. Nó cho thấy trung bình, công ty tạo ra được 16.12 đồng lợi nhuận trên 100 đồng vốn chủ sở hữu. Đây là dấu hiệu tích cực cho các cổ đông.

ROA trung bình 5.53% cho thấy hiệu quả sử dụng toàn bộ tài sản ở mức trung bình.

Sự chênh lệch giữa ROE và ROA: Sự khác biệt đáng kể giữa ROE (16.12%) và ROA (5.53%) thường cho thấy công ty đang sử dụng đòn bẩy tài chính (vay nợ) ở một mức độ nhất định. Nợ vay giúp khuếch đại lợi nhuận cho vốn chủ sở hữu (làm ROE cao hơn), nhưng cũng đi kèm với rủi ro tài chính cao hơn.

Biến động lớn về hiệu suất: Phạm vi giá trị từ Min đến Max của cả ROE và ROA đều khá rộng (ROE từ 1.72% đến 33.53%). Điều này cho thấy hiệu quả hoạt động của công ty không ổn định và có sự biến động mạnh giữa các kỳ. Sự biến động này là phù hợp với những gì đã thấy ở chỉ số tăng trưởng lợi nhuận và doanh thu trước đó.

2.8: Tạo biến phân loại “Giai đoạn tăng trưởng” dựa vào tốc độ tăng doanh thu

data <- data %>%
mutate(GrowthPhase = case_when(
RevenueGrowth > 10 ~ "Tăng trưởng mạnh",
RevenueGrowth >= 0 & RevenueGrowth <= 10 ~ "Tăng trưởng ổn định",
RevenueGrowth < 0 ~ "Suy giảm"
))

table(data$GrowthPhase)
## 
##         Suy giảm Tăng trưởng mạnh 
##                8                2

Giải thích kỹ thuật (Code) Mục tiêu: Đoạn code này nhằm phân loại từng quan sát (thường là từng năm) vào một “Giai đoạn tăng trưởng” dựa trên tốc độ tăng trưởng doanh thu (RevenueGrowth).

Hàm sử dụng:

mutate(): Dùng để tạo cột mới GrowthPhase.

case_when(): Đây là hàm cực kỳ hữu ích trong dplyr để thực hiện nhiều phép kiểm tra có điều kiện. Nó hoạt động theo logic “nếu… thì…”:

Nếu RevenueGrowth > 10 -> Phân loại là “Tăng trưởng mạnh”.

Nếu RevenueGrowth nằm trong khoảng từ 0 đến 10 -> Phân loại là “Tăng trưởng ổn định”.

Nếu RevenueGrowth < 0 -> Phân loại là “Suy giảm”.

Lệnh table(data$GrowthPhase): Lệnh này tạo ra một bảng tần suất, cho biết có bao nhiêu quan sát (năm) rơi vào mỗi giai đoạn tăng trưởng. Nhận xét về kết quả và ý nghĩa Kết quả chạy ra: Kết quả từ lệnh table() là:

Suy giảm: 8

Tăng trưởng mạnh: 2

Lưu ý: Giai đoạn “Tăng trưởng ổn định” không xuất hiện trong kết quả, có nghĩa là không có năm nào có mức tăng trưởng từ 0% đến 10%.

2.9: Chuẩn hóa dữ liệu (scale) cho các biến lớn để phục vụ mô hình và đồ thị

scaled_data <- data %>%
select(Revenue, NetProfit, TotalAssets, TotalDebt, Equity) %>%
scale(center = TRUE, scale = TRUE)

scaled_data <- as.data.frame(scaled_data)
head(scaled_data)
##      Revenue  NetProfit TotalAssets  TotalDebt    Equity
## 1  0.8316326 -1.1827364  1.74600226  1.3756256 1.0824912
## 2  0.3333124 -1.0160702  1.04449710  0.4439197 1.3536528
## 3 -0.5812881  0.1092995  0.77586009  0.3313512 0.9012976
## 4 -1.2534379 -0.7040064  0.48367660  2.0286987 0.5654495
## 5  1.6263010  1.4518221  0.34764882  0.1066091 0.2834175
## 6  1.3761796  1.4570940 -0.03126189 -0.3332858 0.2855945

Giải thích kỹ thuật (Code) Mục tiêu: Đoạn code này thực hiện chuẩn hóa (standardization) dữ liệu cho các biến số có giá trị tuyệt đối lớn. Mục đích là đưa chúng về cùng một thang đo để so sánh trực quan và chuẩn bị cho các mô hình máy học.

Hàm sử dụng:

select(): Lựa chọn ra 5 biến số cần được chuẩn hóa.

scale(center = TRUE, scale = TRUE): Đây là hàm then chốt.

center = TRUE: Thực hiện “dịch chuyển” dữ liệu bằng cách trừ đi giá trị trung bình của biến. Kết quả là các biến mới sẽ có trung bình bằng 0.

scale = TRUE: Thực hiện “co dãn” dữ liệu bằng cách chia cho độ lệch chuẩn của biến. Kết quả là các biến mới sẽ có độ lệch chuẩn bằng 1.

as.data.frame(): Chuyển đổi kết quả từ ma trận (matrix) trả về bởi scale() thành một dataframe để dễ dàng thao tác tiếp. Nhận xét về kết quả và ý nghĩa Kết quả chạy ra: Kết quả từ lệnh head(scaled_data) cho thấy một bảng với các giá trị số thực, có cả giá trị âm và dương. Các con số này không còn là đơn vị tiền tệ gốc nữa mà là số lần độ lệch chuẩn so với giá trị trung bình.

2.10: Gộp dữ liệu đã chuẩn hóa vào bảng chính để tiện phân tích tổng hợp

data_final <- bind_cols(data, scaled_data)
head(data_final)
## # A tibble: 6 × 22
##     Năm FixedAssets Revenue...3 CashEnd TotalAssets...5 TotalDebt...6 Equity...7
##   <dbl>       <dbl>       <dbl>   <dbl>           <dbl>         <dbl>      <dbl>
## 1    NA     2422908    73933080  2.69e7       440345620     303297525  137048095
## 2    NA     2486786    57992886  1.72e7       387697524     242935580  144761944
## 3    NA     2465029    28736782  1.59e7       367536265     235642660  131893605
## 4    NA     2505731     7236152  8.29e6       345607839     345607839  122339595
## 5    NA     3400707    99352815  1.29e7       335398928     221082414  114316514
## 6    NA     3410553    91351969  2.46e6       306961607     192583165  114378442
## # ℹ 15 more variables: CFO <dbl>, TotalCapital <dbl>, NetProfit...10 <dbl>,
## #   IssuedCapital <dbl>, LongTermInvest <dbl>, RevenueGrowth <dbl>,
## #   NetProfitGrowth <dbl>, ROE <dbl>, ROA <dbl>, GrowthPhase <chr>,
## #   Revenue...18 <dbl>, NetProfit...19 <dbl>, TotalAssets...20 <dbl>,
## #   TotalDebt...21 <dbl>, Equity...22 <dbl>

Giải thích kỹ thuật (Code) Mục tiêu: Đoạn code này nhằm hợp nhất dữ liệu gốc (data) và dữ liệu đã được chuẩn hóa (scaled_data) thành một dataframe duy nhất (data_final). Điều này tạo ra một dataset tổng hợp, vừa giữ lại thông tin gốc vừa có sẵn dữ liệu đã được xử lý cho các phân tích chuyên sâu.

Hàm sử dụng:

bind_cols(): Hàm này từ gói dplyr được sử dụng để nối các cột của hai dataframe lại với nhau theo hàng. Nó giả định rằng hai dataframe có cùng số hàng và thứ tự hàng là khớp nhau. Nhận xét về kết quả và ý nghĩa Kết quả chạy ra: Kết quả từ lệnh head(data_final) cho thấy:

Thành công về mặt kỹ thuật: Việc gộp dữ liệu đã thành công. Dataframe data_final bây giờ có tới 22 cột, bao gồm đầy đủ các cột gốc và các cột đã được chuẩn hóa.

Vấn đề về tên cột: Có một vấn đề nhỏ phát sinh: một số cột bị trùng tên (ví dụ: Revenue, NetProfit, TotalAssets…). Hàm bind_cols() đã tự động xử lý điều này bằng cách thêm hậu tố …3, …10, …18… để phân biệt. Điều này có thể gây khó khăn cho việc theo dõi.

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

Phần này trình bày các phân tích mô tả và thống kê cơ bản nhằm giúp hiểu sâu hơn về đặc điểm tài chính của mã cổ phiếu VHM.
Mỗi thao tác được viết riêng, đi kèm với diễn giải và kết luận cụ thể.

3.1: Xem lại các biến đã có trong dữ liệu

names(data)
##  [1] "Năm"             "FixedAssets"     "Revenue"         "CashEnd"        
##  [5] "TotalAssets"     "TotalDebt"       "Equity"          "CFO"            
##  [9] "TotalCapital"    "NetProfit"       "IssuedCapital"   "LongTermInvest" 
## [13] "RevenueGrowth"   "NetProfitGrowth" "ROE"             "ROA"            
## [17] "GrowthPhase"

Giải thích kỹ thuật (Code) Mục tiêu: Đoạn code này nhằm kiểm tra lại cấu trúc cuối cùng của bộ dữ liệu sau tất cả các bước xử lý, đảm bảo rằng tất cả các biến dự định đã được tạo ra và có mặt trong dataset.

Hàm sử dụng:

names(data): Đây là một hàm cơ bản và hiệu quả trong R. Nó trả về một danh sách (vector) chứa tên của tất cả các cột (biến) có trong dataframe data. Nhận xét về kết quả và ý nghĩa Kết quả chạy ra: Kết quả in ra cho thấy dataframe data có 17 biến. Danh sách bao gồm:

Các biến gốc đã được đổi tên (FixedAssets, Revenue, TotalAssets…).

Các biến mới được tạo ra trong quá trình xử lý:

Các biến tăng trưởng (RevenueGrowth, NetProfitGrowth).

Các chỉ số tài chính (ROE, ROA).

Biến phân loại (GrowthPhase).

Giải thích và ý nghĩa:

Code hoàn toàn chính xác: Lệnh đã chạy thành công và liệt kê đầy đủ tất cả các biến.

Xác nhận một quy trình xử lý dữ liệu hoàn chỉnh: Kết quả này là bằng chứng cho thấy quy trình xử lý dữ liệu từ 2.1 đến 2.8 của bạn đã được thực thi một cách có hệ thống và thành công. Dữ liệu không chỉ còn là các số liệu thô mà đã được làm giàu thêm bằng các chỉ số phân tích có ý nghĩa.

3.2: Tính trung bình (Mean)

colMeans(select(data, where(is.numeric)), na.rm = TRUE)
##             Năm     FixedAssets         Revenue         CashEnd     TotalAssets 
##             NaN    2.798936e+06    4.733094e+07    1.222923e+07    3.093078e+08 
##       TotalDebt          Equity             CFO    TotalCapital       NetProfit 
##    2.141756e+08    1.062540e+08    1.827178e+07    2.706155e+08    1.624710e+07 
##   IssuedCapital  LongTermInvest   RevenueGrowth NetProfitGrowth             ROE 
##    4.269720e+07    4.713529e+07    9.702000e+01    5.710800e+01    1.612091e+01 
##             ROA 
##    5.529091e+00

Giải thích kỹ thuật (Code) Mục tiêu: Đoạn code này nhằm kiểm tra lại cấu trúc cuối cùng của bộ dữ liệu sau tất cả các bước xử lý, đảm bảo rằng tất cả các biến dự định đã được tạo ra và có mặt trong dataset.

Hàm sử dụng:

names(data): Đây là một hàm cơ bản và hiệu quả trong R. Nó trả về một danh sách (vector) chứa tên của tất cả các cột (biến) có trong dataframe data. Nhận xét về kết quả và ý nghĩa Kết quả chạy ra: Kết quả in ra cho thấy dataframe data có 17 biến. Danh sách bao gồm:

Các biến gốc đã được đổi tên (FixedAssets, Revenue, TotalAssets…).

Các biến mới được tạo ra trong quá trình xử lý:

Các biến tăng trưởng (RevenueGrowth, NetProfitGrowth).

Các chỉ số tài chính (ROE, ROA).

Biến phân loại (GrowthPhase).

Giải thích và ý nghĩa:

Code hoàn toàn chính xác: Lệnh đã chạy thành công và liệt kê đầy đủ tất cả các biến.

Xác nhận một quy trình xử lý dữ liệu hoàn chỉnh: Kết quả này là bằng chứng cho thấy quy trình xử lý dữ liệu từ 2.1 đến 2.8 của bạn đã được thực thi một cách có hệ thống và thành công. Dữ liệu không chỉ còn là các số liệu thô mà đã được làm giàu thêm bằng các chỉ số phân tích có ý nghĩa.

3.3: Tính trung vị (Median)

apply(select(data, where(is.numeric)), 2, median, na.rm = TRUE)
##             Năm     FixedAssets         Revenue         CashEnd     TotalAssets 
##              NA     2652603.000    46659406.000    10856846.000   306961607.000 
##       TotalDebt          Equity             CFO    TotalCapital       NetProfit 
##   192583165.000   114316514.000    13838030.000   280627329.000    17048279.000 
##   IssuedCapital  LongTermInvest   RevenueGrowth NetProfitGrowth             ROE 
##    43543675.000    37539727.000         -43.155         -16.905          13.290 
##             ROA 
##           4.770

Giải thích kỹ thuật (Code) Mặc dù bạn không cung cấp đoạn code gốc, nhưng dựa vào kết quả đầu ra (các giá trị trung vị được liệt kê cho từng biến), tôi có thể suy luận rằng code bạn đã chạy là một lệnh để tính toán các giá trị trung vị (median) cho tất cả các biến số trong dataset. Cú pháp có thể là:

apply(data, 2, median, na.rm = TRUE) hoặc

sapply(data, median, na.rm = TRUE) hoặc

data %>% summarise(across(where(is.numeric), median, na.rm = TRUE))

Tham số na.rm = TRUE là rất quan trọng để bỏ qua các giá trị NA (như ở biến Năm) trong quá trình tính toán. Kết luận “Sự chênh lệch giữa mean và median cho thấy biến động lớn” của bạn là hoàn toàn chính xác. Sự biến động này không chỉ lớn mà còn nghiêng hẳn về phía tiêu cực.

3.4: Tính độ lệch chuẩn (Standard Deviation)

apply(select(data, where(is.numeric)), 2, sd, na.rm = TRUE)
##             Năm     FixedAssets         Revenue         CashEnd     TotalAssets 
##              NA    4.055388e+05    3.198785e+07    8.466261e+06    7.505019e+07 
##       TotalDebt          Equity             CFO    TotalCapital       NetProfit 
##    6.478648e+07    2.844742e+07    2.007314e+07    8.996359e+07    1.174713e+07 
##   IssuedCapital  LongTermInvest   RevenueGrowth NetProfitGrowth             ROE 
##    2.807448e+06    1.583340e+07    4.151573e+02    1.494121e+02    1.184407e+01 
##             ROA 
##    4.026623e+00

Giải thích kỹ thuật (Code) Mục tiêu: Đoạn code này nhằm tính toán độ lệch chuẩn (Standard Deviation - sd) cho tất cả các biến số trong dataset, để đo lường mức độ biến động hoặc phân tán của dữ liệu xung quanh giá trị trung bình.

Hàm sử dụng:

select(data, where(is.numeric)): Lọc ra chỉ các cột có kiểu dữ liệu là số từ dataframe data.

apply(…, 2, sd, na.rm = TRUE): Áp dụng hàm sd() (độ lệch chuẩn) theo chiều cột (2) cho dataframe đã được lọc. Tham số na.rm = TRUE rất quan trọng, nó bỏ qua các giá trị NA (như ở cột Năm) trong quá trình tính toán, ngăn ngừa kết quả trả về là NA. Kết luận: Nhận xét của bạn về “biến động tương đối” là đúng, nhưng có lẽ cần nhấn mạnh hơn về mức độ cực đoan của sự biến động, đặc biệt là ở các chỉ số tăng trưởng. Kết quả này không chỉ phản ánh “chu kỳ ngành” mà còn là một dấu hiệu cảnh báo mạnh mẽ về tính phi điển hình và khả năng không đáng tin cậy của dữ liệu tăng trưởng, có thể do thứ tự thời gian bị sai.

3.5: Tính hệ số biến động (Coefficient of Variation)

cv <- apply(select(data, where(is.numeric)), 2, sd, na.rm=TRUE) /
apply(select(data, where(is.numeric)), 2, mean, na.rm=TRUE) * 100
cv
##             Năm     FixedAssets         Revenue         CashEnd     TotalAssets 
##              NA       14.489032       67.583389       69.229726       24.263917 
##       TotalDebt          Equity             CFO    TotalCapital       NetProfit 
##       30.249239       26.773028      109.858721       33.244066       72.302928 
##   IssuedCapital  LongTermInvest   RevenueGrowth NetProfitGrowth             ROE 
##        6.575251       33.591388      427.908942      261.630792       73.470251 
##             ROA 
##       72.826123

Giải thích kỹ thuật (Code) Mục tiêu: Đoạn code này nhằm tính toán Hệ số biến động (Coefficient of Variation - CV) cho tất cả các biến số. CV là một thước đo thống kê chuẩn hóa, dùng để so sánh mức độ biến động tương đối giữa các biến có đơn vị và giá trị trung bình khác nhau.

Công thức và Hàm sử dụng:

Công thức: CV = (Độ lệch chuẩn / Giá trị trung bình) * 100%

Cách triển khai trong code: Code sử dụng hai lệnh apply riêng biệt:

apply(…, 2, sd, na.rm=TRUE): Tính độ lệch chuẩn (sd) cho tất cả các cột số.

apply(…, 2, mean, na.rm=TRUE): Tính giá trị trung bình (mean) cho tất cả các cột số.

Sau đó thực hiện phép chia và nhân 100% như trong công thức.

Tham số na.rm=TRUE là rất quan trọng, giúp bỏ qua các giá trị NA (như ở cột Năm) để phép tính không bị lỗi. **Kết luận tổng th*ể:** Phân tích CV đã cung cấp một bức tranh rõ ràng: Doanh nghiệp có một cơ cấu tài chính ổn định (phần xác) nhưng hiệu quả hoạt động lại cực kỳ bất ổn (phần hồn). Sự bất ổn này ở mức rất cao, đặc biệt là ở các chỉ số tăng trưởng, khiến cho việc đánh giá hiệu suất thực tế trở nên rất khó khăn.

3.6: Xác định giá trị lớn nhất và nhỏ nhất

sapply(select(data, where(is.numeric)), range, na.rm = TRUE)
##       Năm FixedAssets  Revenue  CashEnd TotalAssets TotalDebt    Equity
## [1,]  Inf     2422908  6086718  1618383   185152395 129612854  55539541
## [2,] -Inf     3427099 99352815 26911424   440345620 345607839 144761944
##            CFO TotalCapital NetProfit IssuedCapital LongTermInvest
## [1,] -12410860     57992886   1490610      34232424       32425466
## [2,]  47157093    367536265  33363772      43543675       74332209
##      RevenueGrowth NetProfitGrowth   ROE   ROA
## [1,]        -74.82          -91.26  1.72  0.53
## [2,]       1273.01          317.47 33.53 10.87

Giải thích kỹ thuật (Code) Mục tiêu: Đoạn code này nhằm xác định giá trị nhỏ nhất (Min) và lớn nhất (Max) của tất cả các biến số trong dataset, cung cấp cái nhìn về biên độ dao động của dữ liệu.

Hàm sử dụng:

select(data, where(is.numeric)): Lọc ra chỉ các cột có kiểu dữ liệu là số.

sapply(…, range, na.rm = TRUE): Áp dụng hàm range() cho từng cột số. Hàm range() trả về một vector chứa giá trị Min và Max. Tham số na.rm = TRUE giúp bỏ qua các giá trị NA. Kết luận “Mở rộng quy mô mạnh” là chưa đầy đủ: Trong khi sự chênh lệch giữa doanh thu cao nhất và thấp nhất (~93 triệu) có thể gợi ý về sự mở rộng, nó không nói lên được xu hướng. Kết hợp với sự hỗn loạn của chỉ số tăng trưởng, có thể kết luận chính xác hơn là: “Doanh nghiệp có sự biến động cực lớn về quy mô và hiệu suất, với các giai đoạn suy giảm thảm hại và tăng trưởng đột biến khó lý giải, chứ không phải là một lộ trình mở rộng quy mô ổn định và lành mạnh.”

3.7: Thống kê tần suất giai đoạn tăng trưởng

table(data$GrowthPhase)
## 
##         Suy giảm Tăng trưởng mạnh 
##                8                2

Giải thích kỹ thuật (Code) Mục tiêu: Đoạn code này nhằm đếm số lượng quan sát (số năm) thuộc về mỗi loại trong biến phân loại GrowthPhase (Giai đoạn tăng trưởng) đã được tạo ra ở bước 2.8.

Hàm sử dụng:

table(data$GrowthPhase): Đây là hàm cơ bản và hiệu quả trong R để tạo ra một bảng tần suất (frequency table) cho một biến phân loại (factor hoặc character). Nó sẽ liệt kê tất cả các nhóm (levels) có trong biến và số lần mỗi nhóm xuất hiện Kết luận “Đa số các năm (80%) doanh nghiệp ở trong trạng thái suy giảm, chỉ có 20% thời gian đạt được tăng trưởng mạnh. Điều này phản ánh một chiến lược kinh doanh hoặc môi trường hoạt động cực kỳ bất ổn và kém hiệu quả trong việc duy trì tăng trưởng.” ### 3.8: Tính tương quan giữa các biến chính

cor_matrix <- cor(select(data, Revenue, NetProfit, TotalAssets, Equity, TotalDebt, CFO),
use="complete.obs")
cor_matrix
##                 Revenue  NetProfit TotalAssets     Equity  TotalDebt
## Revenue      1.00000000  0.5793870   0.4463470  0.4490948  0.0831706
## NetProfit    0.57938704  1.0000000  -0.1580261 -0.0685033 -0.2859228
## TotalAssets  0.44634700 -0.1580261   1.0000000  0.9601331  0.8281691
## Equity       0.44909484 -0.0685033   0.9601331  1.0000000  0.7799226
## TotalDebt    0.08317060 -0.2859228   0.8281691  0.7799226  1.0000000
## CFO         -0.04162314 -0.2243304  -0.3109418 -0.4674591 -0.2898639
##                     CFO
## Revenue     -0.04162314
## NetProfit   -0.22433038
## TotalAssets -0.31094175
## Equity      -0.46745909
## TotalDebt   -0.28986389
## CFO          1.00000000

Giải thích kỹ thuật (Code) Mục tiêu: Đoạn code này nhằm tính toán ma trận tương quan (correlation matrix) để đo lường mối quan hệ tuyến tính giữa các biến số tài chính quan trọng.

Hàm sử dụng:

select(data, …): Lựa chọn ra 6 biến số cụ thể để phân tích tương quan.

cor(…, use=“complete.obs”): Hàm tính toán hệ số tương quan Pearson. Tham số use=“complete.obs” rất quan trọng, nó chỉ thị cho R sử dụng phương pháp “loại bỏ từng cặp” (pairwise deletion), nghĩa là nó sẽ tính toán hệ số tương quan cho mỗi cặp biến bằng cách sử dụng tất cả các quan sát có dữ liệu đầy đủ cho cặp đó. Điều này giúp tận dụng tối đa dữ liệu ngay cả khi có một số giá trị bị thiếu ở các biến khác. Nhận xét về kết quả và ý nghĩa Kết quả chạy ra: Ma trận tương quan hiển thị hệ số tương quan giữa 6 biến. Một số mối quan hệ nổi bật:

Revenue và NetProfit: 0.579 (Tương quan thuận, mức độ trung bình)

TotalAssets và Equity: 0.960 (Tương quan thuận, rất mạnh)

NetProfit và TotalAssets: -0.158 (Tương quan nghịch, rất yếu)

NetProfit và Equity: -0.069 (Gần như không có tương quan)

Hầu hết các biến với CFO (Dòng tiền hoạt động): Có xu hướng tương quan nghịch.

3.9: Hồi quy tuyến tính giữa Doanh thu và Lợi nhuận

model <- lm(NetProfit ~ Revenue, data=data)
summary(model)
## 
## Call:
## lm(formula = NetProfit ~ Revenue, data = data)
## 
## Residuals:
##       Min        1Q    Median        3Q       Max 
## -19553961  -3068416   3377452   6055215  11155888 
## 
## Coefficients:
##              Estimate Std. Error t value Pr(>|t|)  
## (Intercept) 6.176e+06  5.618e+06   1.099   0.3001  
## Revenue     2.128e-01  9.977e-02   2.133   0.0618 .
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 10090000 on 9 degrees of freedom
## Multiple R-squared:  0.3357, Adjusted R-squared:  0.2619 
## F-statistic: 4.548 on 1 and 9 DF,  p-value: 0.06176

Giải thích: Mô hình tuyến tính kiểm tra mối quan hệ giữa lợi nhuận (phụ thuộc) và doanh thu (độc lập). Kết luận: Hệ số hồi quy dương và ý nghĩa → doanh thu là yếu tố quyết định chính tới lợi nhuận của VHM.

3.10: Phân tích xu hướng lợi nhuận theo năm

# Kiểm tra xem dữ liệu có cột "Năm" hay không
if(!("Năm" %in% names(data))) {
  # Nếu không có, tạo cột Năm giả định (ví dụ từ 2015 trở đi)
  data$Năm <- seq(from = 2015, length.out = nrow(data))
}

# Tính lợi nhuận bình quân theo năm
profit_trend <- data %>%
  group_by(Năm) %>%
  summarize(NetProfit_mean = mean(NetProfit, na.rm = TRUE),
            NetProfit_min = min(NetProfit, na.rm = TRUE),
            NetProfit_max = max(NetProfit, na.rm = TRUE),
            .groups = 'drop')

# Hiển thị kết quả
knitr::kable(profit_trend, caption = "Bảng: Xu hướng lợi nhuận thuần theo năm của VHM")
Bảng: Xu hướng lợi nhuận thuần theo năm của VHM
Năm NetProfit_mean NetProfit_min NetProfit_max
NA 16247101 1490610 33363772

Giải thích kỹ thuật (Code) Mục tiêu: Đoạn code này nhằm tính toán Tốc độ tăng trưởng kép hằng năm (CAGR - Compound Annual Growth Rate) của doanh thu. Đây là một chỉ số quan trọng để đánh giá tốc độ tăng trưởng trung bình hàng năm trong một khoảng thời gian.

Công thức và Hàm sử dụng:

Công thức CAGR: CAGR = (Giá trị cuối / Giá trị đầu)^(1 / Số kỳ) - 1

Triển khai trong code:

years <- nrow(data): Giả định số năm bằng số hàng trong dataset (11).

last(data$Revenue): Lấy giá trị doanh thu của hàng cuối cùng trong dataset.

first(data$Revenue): Lấy giá trị doanh thu của hàng đầu tiên trong dataset.

((last/first)^(1/(years-1)) - 1)*100: Áp dụng công thức CAGR và chuyển sang phần trăm. Nhận xét về kết quả và ý nghĩa Kết quả chạy ra: Kết quả của biến CAGR là -22.09695%.

Giải thích và ý nghĩa:

Code hoạt động đúng về mặt kỹ thuật: Code đã thực thi và tính toán ra một con số CAGR dựa trên dữ liệu hiện có.

Mâu thuẫn NGHIÊM TRỌNG giữa kết quả và kết luận: Đây là một lỗi sai cơ bản trong việc diễn giải. Kết quả là một số ÂM (-22.1%), nhưng kết luận của bạn lại ghi “CAGR dương và cao → tốc độ tăng trưởng doanh thu bền vững”. Điều này là hoàn toàn trái ngược.

3.11: Tính tăng trưởng trung bình doanh thu (CAGR)

years <- nrow(data)
CAGR <- ((last(data$Revenue)/first(data$Revenue))^(1/(years-1)) - 1)*100
CAGR
## [1] -22.09695

Giải thích: Tốc độ tăng trưởng kép (CAGR) đo lường tốc độ tăng đều mỗi năm của doanh thu. Kết luận: CAGR dương và cao → tốc độ tăng trưởng doanh thu bền vững.

3.12: Tỷ lệ nợ trên vốn chủ sở hữu (Debt-to-Equity)

mean(data$TotalDebt / data$Equity, na.rm=TRUE)
## [1] 2.037566

Giải thích kỹ thuật (Code) Mục tiêu: Đoạn code này nhằm tính toán tỷ lệ Nợ trên Vốn chủ sở hữu (Debt-to-Equity Ratio - D/E), một chỉ số tài chính quan trọng dùng để đánh giá mức độ sử dụng đòn bẩy tài chính và rủi ro tài chính của một doanh nghiệp.

Hàm sử dụng:

mean(data\(TotalDebt / data\)Equity, na.rm=TRUE): Code trực tiếp tính tỷ lệ D/E cho từng quan sát (TotalDebt / Equity), sau đó tính giá trị trung bình của tất cả các tỷ lệ đó trong toàn bộ dataset. Tham số na.rm=TRUE đảm bảo loại bỏ các giá trị NA nếu có để phép tính không bị lỗi. Nhận xét về kết quả và ý nghĩa Kết quả chạy ra: Kết quả trả về là 2.037566.

Giải thích và ý nghĩa:

Code hoàn toàn chính xác: Code đã thực thi đúng và cho ra kết quả tính toán chính xác.

Mâu thuẫn nghiêm trọng giữa kết quả và kết luận: Đây là một lỗi sai cơ bản trong việc diễn giải chỉ số tài chính. Kết quả là 2.04, nhưng kết luận của bạn lại ghi “Tỷ lệ nợ/vốn chủ thấp… an toàn”. Điều này là hoàn toàn trái ngược.

3.13: Tỷ trọng tài sản cố định trong tổng tài sản

mean(data$FixedAssets / data$TotalAssets, na.rm=TRUE)
## [1] 0.009614649

Giải thích kỹ thuật (Code) Mục tiêu: Đoạn code này nhằm tính toán tỷ trọng Tài sản cố định trong Tổng tài sản, một chỉ số phản ánh cơ cấu đầu tư của doanh nghiệp.

Hàm sử dụng:

mean(data\(FixedAssets / data\)TotalAssets, na.rnn=TRUE): Code tính tỷ lệ FixedAssets / TotalAssets cho từng quan sát, sau đó lấy trung bình của tất cả các tỷ lệ đó.

Lỗi chính tả: Có một lỗi nhỏ trong code: na.rnn=TRUE phải là na.rm=TRUE. Tuy nhiên, kết quả vẫn được tính ra, có thể do code thực tế khi chạy đã được sửa đúng. Kết luận “hợp lý” và “cân bằng” là không chính xác: Với tỷ trọng chưa đến 1%, không thể kết luận là “đầu tư cân bằng giữa tài sản cố định và lưu động”. Thay vào đó, nó cho thấy cơ cấu tài sản nghiêng hẳn về Tài sản ngắn hạn (tài sản lưu động) một cách áp đảo. Điều này có thể do một số nguyên nhân:

3.14: Trung bình ROE và ROA

mean(data$ROE, na.rm=TRUE)
## [1] 16.12091
mean(data$ROA, na.rm=TRUE)
## [1] 5.529091

Giải thích kỹ thuật (Code) Mục tiêu: Đoạn code này nhằm tính toán giá trị trung bình của hai chỉ số tài chính quan trọng: ROE (Lợi nhuận trên Vốn chủ sở hữu) và ROA (Lợi nhuận trên Tổng tài sản).

Hàm sử dụng:

mean(data$ROE, na.rm=TRUE): Tính giá trị trung bình của cột ROE, loại bỏ các giá trị NA.

mean(data$ROA, na.rm=TRUE): Tính giá trị trung bình của cột ROA, loại bỏ các giá trị NA.. Nhận xét về kết quả và ý nghĩa Kết quả chạy ra:

ROE trung bình: 16.12%

ROA trung bình: 5.53%

Giải thích và ý nghĩa:

Code hoàn toàn chính xác: Code đã thực thi đúng và cho ra kết quả tính toán chính xác.

3.15: So sánh trung bình ROE theo giai đoạn tăng trưởng

data %>% group_by(GrowthPhase) %>%
summarize(Mean_ROE = mean(ROE, na.rm=TRUE))
## # A tibble: 3 × 2
##   GrowthPhase      Mean_ROE
##   <chr>               <dbl>
## 1 Suy giảm            14.1 
## 2 Tăng trưởng mạnh    31.3 
## 3 <NA>                 1.72

Giải thích kỹ thuật (Code) Mục tiêu: Đoạn code này nhằm so sánh hiệu quả sinh lời (thông qua chỉ số ROE) giữa các giai đoạn tăng trưởng khác nhau của doanh nghiệp.

Hàm sử dụng:

group_by(GrowthPhase): Nhóm dữ liệu theo các hạng mục trong biến phân loại GrowthPhase (“Suy giảm”, “Tăng trưởng mạnh”).

summarize(Mean_ROE = mean(ROE, na.rnn=TRUE)): Tính giá trị ROE trung bình cho từng nhóm.

Nhận xét về kết quả và ý nghĩa

Giải thích và ý nghĩa:

Code về cơ bản là chính xác: Mặc dù có lỗi chính tả trong phiên bản chép lại, code đã thực thi và cho ra kết quả tính toán mong muốn.

Kết quả phù hợp với lý thuyết (CÓ ĐIỀU KIỆN): Nhận xét của bạn là đúng: ROE trong giai đoạn “Tăng trưởng mạnh” (31.3%) cao hơn rõ rệt so với giai đoạn “Suy giảm” (14.1%). Điều này là hoàn toàn hợp lý, vì tăng trưởng doanh thu thường đi kèm với lợi nhuận cao hơn, dẫn đến hiệu suất sinh lời trên vốn chủ (ROE) cũng cao hơn. Nó “phản ánh hiệu quả đầu tư vốn” trong các giai đoạn đó ### 3.16: Tính tổng doanh thu và lợi nhuận tích lũy

sum(data$Revenue, na.rm=TRUE)
## [1] 520640323
sum(data$NetProfit, na.rm=TRUE)
## [1] 178718106

Giải thích kỹ thuật (Code) Mục tiêu: Đoạn code này nhằm tính toán tổng doanh thu và tổng lợi nhuận tích lũy trong toàn bộ giai đoạn quan sát (11 năm).

Hàm sử dụng:

sum(data$Revenue, na.rm=TRUE): Tính tổng tất cả các giá trị trong cột Revenue, bỏ qua các giá trị NA.

sum(data$NetProfit, na.rm=TRUE): Tính tổng tất cả các giá trị trong cột NetProfit, bỏ qua các giá trị NA. Nhận xét về kết quả và ý nghĩa Kết quả chạy ra:

Tổng Doanh thu: 520,640,323

Tổng Lợi nhuận: 178,718,106

Giải thích và ý nghĩa:

Code hoàn toàn chính xác: Code đã thực thi đúng và cho ra kết quả tính toán chính xác.

Đánh giá quy mô tích lũy:

Các con số tuyệt đối này cho thấy quy mô hoạt động của doanh nghiệp là rất lớn khi xét trên toàn bộ chu kỳ 11 năm.

3.17: So sánh hệ số ROE và ROA qua từng năm

if("Năm" %in% names(data)) {
data %>% select(Năm, ROE, ROA)
}
## # A tibble: 11 × 3
##      Năm   ROE   ROA
##    <dbl> <dbl> <dbl>
##  1    NA  1.72  0.53
##  2    NA  2.98  1.11
##  3    NA 13.3   4.77
##  4    NA  6.52  2.31
##  5    NA 29.1   9.93
##  6    NA 29.2  10.9 
##  7    NA 21.4   7.85
##  8    NA 13.0   4.44
##  9    NA 33.5  10.6 
## 10    NA 24.0   7.61
## 11    NA  2.68  0.81

Giải thích kỹ thuật (Code) Mục tiêu: Đoạn code này nhằm trích xuất và hiển thị các giá trị ROE và ROA theo từng năm để phân tích xu hướng biến động của chúng theo thời gian.

Hàm sử dụng:

if(“Năm” %in% names(data)) { … }: Kiểm tra sự tồn tại của cột Năm trước khi thực hiện lệnh.

data %>% select(Năm, ROE, ROA): Lựa chọn và hiển thị ba cột quan trọng là Năm, ROE và ROA từ dataset. Kết luận thực tế Thay vì “khả năng sinh lời duy trì vững”, dữ liệu này cho thấy một thực tế hoàn toàn trái ngược: Khả năng sinh lời của doanh nghiệp cực kỳ bất ổn, với những biến động khó lường giữa các kỳ. Sự thiếu vắng thông tin năm càng làm tăng thêm rủi ro khi diễn giải dữ liệu này.

3.18: Phân tích hệ số tương quan giữa dòng tiền và lợi nhuận

cor(data$CFO, data$NetProfit, use="complete.obs")
## [1] -0.2243304

Giải thích kỹ thuật (Code) Mục tiêu: Đoạn code này nhằm tính toán hệ số tương quan giữa Dòng tiền thuần từ hoạt động kinh doanh (CFO) và Lợi nhuận thuần (NetProfit), để đánh giá mối quan hệ giữa lợi nhuận kế toán và dòng tiền thực tế từ hoạt động cốt lõi.

Hàm sử dụng:

cor(data\(CFO, data\)NetProfit, use=“complete.obs”): Tính hệ số tương quan Pearson giữa hai biến CFO và NetProfit. Tham số use=“complete.obs” đảm bảo chỉ sử dụng các cặp quan sát có dữ liệu đầy đủ cho cả hai biến. Nhận xét về kết quả và ý nghĩa Kết luận: “Hệ số tương quan âm và yếu giữa CFO và NetProfit cho thấy chất lượng lợi nhuận có vấn đề. Lợi nhuận kế toán của VHM không đi kèm với dòng tiền mặt tương ứng từ hoạt động kinh doanh, có thể do ảnh hưởng của các khoản mục kế toán phi tiền mặt lớn (như khấu hao, các khoản phải thu tăng mạnh) hoặc sự phụ thuộc vào các nguồn thu nhập bất thường.”

3.19: Tính tỷ lệ dòng tiền/doanh thu

mean(data$CFO / data$Revenue, na.rm=TRUE)
## [1] 1.024692

Giải thích kỹ thuật (Code) Mục tiêu: Đoạn code này nhằm tính toán tỷ lệ giữa Dòng tiền thuần từ hoạt động kinh doanh (CFO) và Doanh thu (Revenue), một chỉ số cho biết khả năng chuyển đổi doanh thu thành tiền mặt thực tế từ hoạt động cốt lõi của doanh nghiệp.

Hàm sử dụng:

mean(data\(CFO / data\)Revenue, na.rm=TRUE): Tính tỷ lệ CFO/Revenue cho từng kỳ, sau đó lấy giá trị trung bình của tất cả các tỷ lệ đó. Tham số na.rm=TRUE đảm bảo bỏ qua các giá trị NA. Nhận xét về kết quả và ý nghĩa Kết quả chạy ra: Kết quả trả về là 1.024692, tương đương 102.47%.

Giải thích và ý nghĩa:

Code hoàn toàn chính xác: Code đã thực thi đúng và cho ra kết quả tính toán chính xác.

3.20: Tính tốc độ tăng trưởng lợi nhuận bình quân

mean(data$NetProfitGrowth, na.rm=TRUE)
## [1] 57.108

Giải thích kỹ thuật (Code) Mục tiêu: Đoạn code này nhằm tính toán tốc độ tăng trưởng lợi nhuận trung bình hàng năm.

Hàm sử dụng:

mean(data$NetProfitGrowth, na.rnn=TRUE): Tính giá trị trung bình của cột NetProfitGrowth.

Lỗi chính tả: Có một lỗi nhỏ trong code: na.rnn=TRUE phải là na.rm=TRUE. Tuy nhiên, kết quả vẫn được tính ra, có thể do code thực tế khi chạy đã được sửa đúng.

4. Trực quan hóa dữ liệu tài chính của VHM

Phần này nhằm minh họa các xu hướng, cấu trúc và mối quan hệ trong dữ liệu tài chính của VHM.
Mỗi biểu đồ gồm tối thiểu 5 layer và đi kèm phân tích & kết luận ngắn gọn.

🔹 Chuẩn bị chung cho phần vẽ

library(ggplot2)
theme_custom <- theme_minimal(base_size = 13) +
  theme(plot.title = element_text(hjust = 0.5, face = "bold"))

4.1: Doanh thu thuần theo năm

ggplot(data, aes(x = Năm, y = Revenue)) +
geom_line(color = "steelblue", size = 1.3) +
geom_point(size = 3, color = "darkorange") +
geom_smooth(method = "lm", se = FALSE, color = "red") +
geom_text(aes(label = scales::comma(Revenue)), vjust = -0.5, size = 3) +
labs(title = "Biểu đồ 1. Doanh thu thuần của VHM qua các năm",
x = "Năm", y = "Doanh thu (tỷ đồng)") +
theme_custom

Phân tích: Doanh thu có xu hướng tăng ổn định qua thời gian, thể hiện năng lực mở rộng thị trường. 5 Layer: geom_line + geom_point + geom_smooth + geom_text + theme_custom.

4.2: Lợi nhuận thuần theo năm

ggplot(data, aes(x = Năm, y = NetProfit)) +
geom_col(fill = "darkseagreen") +
geom_text(aes(label = round(NetProfit, 0)), vjust = -0.5, size = 3) +
geom_hline(yintercept = mean(data$NetProfit, na.rm=TRUE), color="red", linetype="dashed") +
geom_smooth(method="lm", se=FALSE, color="orange") +
labs(title="Biểu đồ 2. Lợi nhuận thuần theo năm",
y="Lợi nhuận (tỷ đồng)") + theme_custom

Phân tích: Lợi nhuận tăng đều, đường hồi quy dương chứng minh xu hướng tích cực.

4.3: ROE và ROA qua các năm

data_long <- data %>% select(Năm, ROE, ROA) %>% pivot_longer(-Năm)
ggplot(data_long, aes(x=Năm, y=value, color=name)) +
geom_line(size=1.2) +
geom_point(size=3) +
geom_text(aes(label=round(value,1)), vjust=-0.4, size=3) +
geom_hline(yintercept=mean(data$ROE, na.rm=TRUE), color="red", linetype="dotted") +
labs(title="Biểu đồ 3. ROE và ROA qua các năm", y="%", color="Chỉ tiêu") +
theme_custom

Phân tích: ROE cao hơn ROA cho thấy hiệu quả sử dụng vốn chủ tốt hơn tài sản.

4.4: Tỷ lệ Nợ / Vốn chủ sở hữu

ggplot(data, aes(x=Năm, y=TotalDebt/Equity)) +
geom_line(color="purple", size=1.2) +
geom_point(size=3, color="darkred") +
geom_smooth(method="loess", color="orange", se=FALSE) +
geom_hline(yintercept=1, color="gray", linetype="dashed") +
geom_text(aes(label=round(TotalDebt/Equity,2)), vjust=-0.5, size=3) +
labs(title="Biểu đồ 4. Hệ số Nợ/Vốn chủ sở hữu", y="Hệ số") +
theme_custom

Phân tích: Hệ số nhỏ hơn 1 hầu hết các năm, phản ánh cơ cấu vốn lành mạnh.

4.5: Tổng tài sản và vốn chủ sở hữu

data_long2 <- data %>% select(Năm, TotalAssets, Equity) %>% pivot_longer(-Năm)
ggplot(data_long2, aes(x=Năm, y=value, fill=name)) +
geom_bar(stat="identity", position="dodge") +
geom_text(aes(label=scales::comma(value)), vjust=-0.3, size=3, position=position_dodge(width=0.9)) +
geom_hline(yintercept=mean(data$TotalAssets), color="red", linetype="dashed") +
geom_smooth(aes(group=name, color=name), method="lm", se=FALSE) +
labs(title="Biểu đồ 5. So sánh Tổng tài sản và Vốn chủ sở hữu", y="Tỷ đồng", fill="Chỉ tiêu") +
theme_custom

Phân tích: Tổng tài sản tăng song song với vốn chủ, chứng minh năng lực mở rộng cân đối.

4.6: Tăng trưởng doanh thu (%)

ggplot(data, aes(x=Năm, y=RevenueGrowth)) +
geom_line(color="darkgreen", size=1.2) +
geom_point(size=3) +
geom_hline(yintercept=0, linetype="dashed", color="gray") +
geom_text(aes(label=round(RevenueGrowth,1)), vjust=-0.5, size=3) +
geom_smooth(method="loess", color="orange", se=FALSE) +
labs(title="Biểu đồ 6. Tăng trưởng doanh thu (%)", y="%") +
theme_custom

Phân tích: Tăng trưởng dương hầu hết các năm → thị phần mở rộng đều.

4.7: Tăng trưởng lợi nhuận (%)

ggplot(data, aes(x=Năm, y=NetProfitGrowth)) +
geom_line(color="darkblue", size=1.2) +
geom_point(size=3) +
geom_smooth(method="lm", color="red", se=FALSE) +
geom_text(aes(label=round(NetProfitGrowth,1)), vjust=-0.4, size=3) +
geom_hline(yintercept=0, linetype="dotted", color="gray") +
labs(title="Biểu đồ 7. Tăng trưởng lợi nhuận (%)", y="%") + theme_custom

Phân tích: Các năm tăng trưởng âm ngắn hạn không ảnh hưởng xu hướng dài hạn.

4.8: Mối quan hệ giữa Doanh thu và Lợi nhuận

ggplot(data, aes(x=Revenue, y=NetProfit)) +
geom_point(color="steelblue", size=3) +
geom_smooth(method="lm", color="red", se=FALSE) +
geom_text(aes(label=Năm), vjust=-0.5, size=3) +
geom_vline(xintercept=mean(data$Revenue), linetype="dashed") +
geom_hline(yintercept=mean(data$NetProfit), linetype="dashed") +
labs(title="Biểu đồ 8. Mối quan hệ Doanh thu - Lợi nhuận", x="Doanh thu", y="Lợi nhuận") +
theme_custom

Phân tích: Mối tương quan tuyến tính mạnh (R² cao), doanh thu tăng kéo lợi nhuận tăng.

4.9: Mối quan hệ ROE và ROA

ggplot(data, aes(x=ROA, y=ROE)) +
geom_point(size=3, color="darkgreen") +
geom_smooth(method="lm", color="orange", se=FALSE) +
geom_text(aes(label=Năm), vjust=-0.4, size=3) +
geom_vline(xintercept=mean(data$ROA), linetype="dashed") +
geom_hline(yintercept=mean(data$ROE), linetype="dashed") +
labs(title="Biểu đồ 9. Mối quan hệ giữa ROE và ROA", x="ROA (%)", y="ROE (%)") +
theme_custom

Phân tích: ROE tăng cùng ROA, chứng tỏ khả năng tận dụng tài sản tốt.

4.10: Dòng tiền hoạt động kinh doanh (CFO)

ggplot(data, aes(x=Năm, y=CFO)) +
geom_area(fill="lightblue", alpha=0.6) +
geom_line(color="darkblue", size=1.2) +
geom_point(size=3) +
geom_text(aes(label=scales::comma(CFO)), vjust=-0.5, size=3) +
geom_smooth(method="lm", color="red", se=FALSE) +
labs(title="Biểu đồ 10. Lưu chuyển tiền thuần từ hoạt động kinh doanh", y="Tỷ đồng") +
theme_custom

Phân tích: Dòng tiền dương ổn định, phù hợp với xu hướng lợi nhuận.

4.11: Phân bố Tài sản cố định

ggplot(data, aes(x=FixedAssets)) +
geom_histogram(fill="lightsteelblue", bins=10, color="white") +
geom_vline(xintercept=mean(data$FixedAssets), color="red", linetype="dashed") +
geom_density(color="darkblue") +
labs(title="Biểu đồ 11. Phân bố giá trị Tài sản cố định", x="Tài sản cố định", y="Tần suất") +
theme_custom

4.12: Phân bố Tổng tài sản

ggplot(data, aes(x=TotalAssets)) +
geom_histogram(fill="skyblue", bins=10, color="white") +
geom_vline(xintercept=mean(data$TotalAssets), color="red", linetype="dashed") +
geom_density(color="darkblue") +
labs(title="Biểu đồ 12. Phân bố Tổng tài sản", x="Tổng tài sản") + theme_custom

4.13: Phân bố Vốn chủ sở hữu

ggplot(data, aes(x=Equity)) +
geom_histogram(fill="palegreen", bins=10, color="white") +
geom_density(color="darkgreen") +
geom_vline(xintercept=mean(data$Equity), color="red", linetype="dashed") +
labs(title="Biểu đồ 13. Phân bố Vốn chủ sở hữu", x="Vốn chủ sở hữu") +
theme_custom

4.14: Mối quan hệ Tài sản - Nợ

ggplot(data, aes(x=TotalAssets, y=TotalDebt)) +
geom_point(color="darkred", size=3) +
geom_smooth(method="lm", color="steelblue", se=FALSE) +
geom_text(aes(label=Năm), vjust=-0.4, size=3) +
labs(title="Biểu đồ 14. Quan hệ giữa Tổng tài sản và Tổng nợ") +
theme_custom

Giải thích kỹ thuật (Code) Mục tiêu: Đoạn code này nhằm vẽ biểu đồ phân tán (scatter plot) để phân tích mối quan hệ giữa Tổng tài sản (TotalAssets) và Tổng nợ phải trả (TotalDebt).

Các thành phần biểu đồ:

geom_point(): Hiển thị các điểm dữ liệu, mỗi điểm là một năm.

geom_smooth(): Vẽ một đường hồi quy để làm nổi bật xu hướng chung của mối quan hệ.

geom_text(): Thử ghi nhãn cho mỗi điểm dữ liệu bằng giá trị của cột “Năm”. Nhận xét về kết quả và ý nghĩa Kết quả chạy ra: Một biểu đồ phân tán với trục X là Tổng tài sản và trục Y là Tổng nợ. Các điểm dữ liệu phân bố gần như theo một đường chéo và đường xu hướng có độ dốc dương rõ rệt. ### 4.15: Heatmap tương quan

library(reshape2)
corr <- cor(select(data, Revenue, NetProfit, TotalAssets, TotalDebt, Equity, ROE, ROA), use="complete.obs")
melted <- melt(corr)
ggplot(melted, aes(Var1, Var2, fill=value)) +
geom_tile() +
geom_text(aes(label=round(value,2))) +
scale_fill_gradient(low="white", high="steelblue") +
labs(title="Biểu đồ 15. Ma trận tương quan") + theme_custom

Giải thích kỹ thuật (Code) Mục tiêu: Đoạn code này nhằm tạo ra một biểu đồ heatmap (bản đồ nhiệt) để trực quan hóa ma trận tương quan giữa các biến tài chính chính, giúp người xem dễ dàng nhận diện các mối quan hệ mạnh/yếu.

Thư viện và Hàm sử dụng:

library(reshape2): Sử dụng thư viện reshape2 để chuyển đổi dữ liệu.

cor(…): Tính toán ma trận tương quan cho 7 biến được chọn.

melt(corr): “Làm tan chảy” ma trận tương quan từ định dạng rộng sang định dạng dài, phù hợp để vẽ biểu đồ bằng ggplot2.

ggplot(…) + geom_tile(): Sử dụng ggplot2 để vẽ heatmap, trong đó geom_tile() tạo ra các ô vuông màu.

scale_fill_gradient(…): Thiết lập thang màu cho heatmap (từ trắng đến xanh dương). Nhận xét về kết quả và ý nghĩa Kết quả chạy ra: Biểu đồ heatmap đã được tạo ra thành công, hiển thị ma trận tương quan giữa 7 biến. Các ô màu và con số cho thấy hệ số tương quan giữa từng cặp biến. Các mối quan hệ tiêu cực và cảnh báo (Tương quan nghịch):

NetProfit & TotalDebt (-0.29) và ROE/ROA & TotalDebt (~-0.4): Đây là một phát hiện QUAN TRỌNG và ĐÁNG BÁO ĐỘNG. Nó cho thấy một mối quan hệ nghịch. Nói cách khác, nợ càng cao thì lợi nhuận và hiệu quả sinh lời (ROE, ROA) càng có xu hướng THẤP. Điều này trái ngược với lý thuyết đòn bẩy tài chính tích cực và củng cố cho kết quả tỷ lệ D/E cao ở bước 3.12. Nó cho thấy doanh nghiệp đang sử dụng nợ không hiệu quả, và gánh nặng nợ có thể đang làm xói mòn lợi nhuận.

Revenue & TotalDebt (0.08): Tương quan gần như bằng 0, cho thấy quy mô doanh thu không có mối liên hệ gì với quy mô nợ.

4.16: Boxplot phân phối ROE

### 🔹 Biểu đồ 16 (đã sửa): Boxplot phân phối ROE
ggplot(data, aes(x = "ROE", y = ROE)) +
  geom_boxplot(fill = "lightblue", width = 0.3) +                           # Layer 1
  geom_jitter(aes(x = "ROE"), color = "darkblue", width = 0.15, size = 2) + # Layer 2
  geom_hline(yintercept = mean(data$ROE, na.rm = TRUE),
             linetype = "dashed", color = "red") +                          # Layer 3
  geom_text(aes(x = "ROE", y = mean(data$ROE, na.rm = TRUE),
                label = paste0("Mean: ", round(mean(data$ROE, na.rm = TRUE), 2))),
            vjust = -1, size = 3, color = "red") +                          # Layer 4
  labs(title = "Biểu đồ 16. Phân phối ROE của VHM",
       x = "", y = "ROE (%)") +                                             # Layer 5
  theme_custom

Giải thích kỹ thuật (Code) Mục tiêu: Đoạn code này nhằm tạo một biểu đồ hộp (Boxplot) kết hợp với điểm dữ liệu (jitter) để trực quan hóa phân phối của chỉ số ROE.

Các lớp (Layers) biểu đồ:

geom_boxplot(): Vẽ boxplot truyền thống, cho thấy các đặc điểm: trung vị (đường giữa hộp), tứ phân vị (phạm vi hộp), và giá trị ngoại lai (outliers).

geom_jitter(): Rải các điểm dữ liệu gốc lên trên boxplot để thể hiện mật độ và vị trí thực của từng quan sát, tránh việc các điểm chồng lấn lên nhau.

geom_hline(): Vẽ một đường nằm ngang đứt đoạn để đánh dấu vị trí của giá trị trung bình (mean).

geom_text(): Thêm nhãn văn bản hiển thị giá trị trung bình trên biểu đồ.

labs() và theme_custom: Đặt tiêu đề, nhãn trục và áp dụng một theme tùy chỉnh. Nhận xét về kết quả và ý nghĩa Kết quả chạy ra: Biểu đồ boxplot đã được tạo ra, hiển thị phân phối của ROE với đường trung bình là 16.12%. Kết luận: Biểu đồ này xác nhận lại một cách trực quan những gì các số liệu thống kê trước đó (độ lệch chuẩn, hệ số biến động, range) đã chỉ ra: Hiệu quả sinh lời (ROE) của doanh nghiệp cực kỳ bất ổn định. Nó không “ổn định” hay “tăng đều” như một số kết luận trước đó, mà thay vào đó, nó phụ thuộc vào một số ít kỳ có hiệu suất đột biến để nâng mức trung bình lên, trong khi phần lớn thời gian hiệu suất ở mức khiêm tốn hơn.

4.17: Boxplot phân phối ROA

ggplot(data, aes(x = "ROA", y = ROA)) +
  geom_boxplot(fill = "lightgreen", width = 0.3) +                          # Layer 1
  geom_jitter(aes(x = "ROA"), color = "darkgreen", width = 0.15, size = 2) +# Layer 2
  geom_hline(yintercept = mean(data$ROA, na.rm = TRUE),
             linetype = "dashed", color = "red") +                          # Layer 3
  geom_text(aes(x = "ROA", y = mean(data$ROA, na.rm = TRUE),
                label = paste0("Mean: ", round(mean(data$ROA, na.rm = TRUE), 2))),
            vjust = -1, size = 3, color = "red") +                          # Layer 4
  labs(title = "Biểu đồ 17. Phân phối ROA của VHM",
       x = "", y = "ROA (%)") +                                             # Layer 5
  theme_custom

Giải thích kỹ thuật (Code) Mục tiêu: Đoạn code này nhằm tạo một biểu đồ hộp (Boxplot) kết hợp với điểm dữ liệu (jitter) để trực quan hóa phân phối của chỉ số ROA.

Các lớp (Layers) biểu đồ (Giống với biểu đồ ROE):

geom_boxplot(): Vẽ boxplot thể hiện trung vị, tứ phân vị và phạm vi của ROA.

geom_jitter(): Hiển thị các điểm dữ liệu gốc để thấy mật độ phân bố thực tế.

geom_hline(): Vẽ đường nằm ngang đánh dấu giá trị trung bình.

geom_text(): Ghi nhãn giá trị trung bình lên biểu đồ.

labs() và theme_custom: Đặt tiêu đề và định dạng theme. Nhận xét về kết quả và ý nghĩa Kết quả chạy ra: Biểu đồ boxplot cho ROA đã được tạo ra, hiển thị đường trung bình là 5.53%.

Giải thích và ý nghĩa (Phân tích hình dạng phân phối): Kết luận: Biểu đồ ROA này, cùng với biểu đồ ROE trước đó, vẽ nên một bức tranh nhất quán: Hiệu quả sinh lời của doanh nghiệp là không ổn định và có tính chất bùng nổ (boom-and-bust). Nó phụ thuộc nhiều vào một số kỳ có hiệu suất đỉnh để nâng tổng thể trung bình lên, hơn là một sự tăng trưởng ổn định và bền vững. Điều này làm dấy lên câu hỏi về tính bền vững trong mô hình kinh doanh và chất lượng của lợi nhuận.

4.18: Quan hệ giữa Lợi nhuận và Dòng tiền

ggplot(data, aes(x=NetProfit, y=CFO)) +
geom_point(size=3, color="blue") +
geom_smooth(method="lm", se=FALSE, color="red") +
geom_text(aes(label=Năm), vjust=-0.4, size=3) +
labs(title="Biểu đồ 18. Mối quan hệ giữa Lợi nhuận và Dòng tiền") + theme_custom

Giải thích kỹ thuật (Code) Mục tiêu: Đoạn code này nhằm vẽ biểu đồ phân tán (scatter plot) để khám phá mối quan hệ trực quan giữa Lợi nhuận thuần (NetProfit) và Dòng tiền từ hoạt động kinh doanh (CFO).

Các thành phần biểu đồ:

geom_point(): Hiển thị các điểm dữ liệu, mỗi điểm là một năm.

geom_smooth(): Vẽ một đường hồi quy để làm nổi bật xu hướng chung của mối quan hệ.

geom_text(): Thử ghi nhãn cho mỗi điểm dữ liệu bằng giá trị của cột “Năm”. Nhận xét về kết quả và ý nghĩa Kết quả chạy ra: Một biểu đồ phân tán với trục X là Lợi nhuận (NetProfit) và trục Y là Dòng tiền (CFO). Các điểm dữ liệu phân tán rộng và đường xu hướng có độ dốc âm.

Giải thích và ý nghĩa:Khi Lợi nhuận (NetProfit) CAO, Dòng tiền từ hoạt động kinh doanh (CFO) lại có xu hướng THẤP.

Ngược lại, có những kỳ Lợi nhuận THẤP nhưng Dòng tiền (CFO) lại CAO.

4.19: Cơ cấu nguồn vốn

data_pie <- data %>%
summarise(Debt = sum(TotalDebt), Equity = sum(Equity)) %>%
pivot_longer(everything(), names_to="Type", values_to="Value")
ggplot(data_pie, aes(x="", y=Value, fill=Type)) +
geom_bar(stat="identity", width=1) +
coord_polar("y") +
geom_text(aes(label=paste0(round(Value/sum(Value)*100,1),"%")), position=position_stack(vjust=0.5)) +
labs(title="Biểu đồ 19. Cơ cấu nguồn vốn") + theme_custom

Giải thích kỹ thuật (Code) Mục tiêu: Đoạn code này nhằm vẽ biểu đồ tròn (pie chart) để thể hiện cơ cấu nguồn vốn, so sánh tỷ trọng giữa Tổng nợ (TotalDebt) và Vốn chủ sở hữu (Equity) trong tổng nguồn vốn.

Các bước xử lý và vẽ biểu đồ:

summarise(): Tính tổng Tổng nợ (Debt) và tổng Vốn chủ sở hữu (Equity) cho toàn bộ dataset.

pivot_longer(): Chuyển đổi dữ liệu từ dạng rộng (2 cột) sang dạng dài (1 cột loại Type và 1 cột giá trị Value) để phù hợp với ggplot2.

geom_bar(stat=“identity”): Tạo một thanh bar chart.

coord_polar(“y”): Chuyển đổi bar chart thành biểu đồ tròn.

geom_text(): Thêm nhãn phần trăm vào các phần của biểu đồ tròn. Nhận xét về kết quả và ý nghĩa Kết quả chạy ra: Biểu đồ tròn đã được tạo ra, nhưng có vẻ như thiếu các nhãn phần trăm cụ thể. Dựa vào mục tiêu của code, nó sẽ hiển thị hai phần chính: “Debt” (Nợ) và “Equity” (Vốn chủ sở hữu).

4.20: So sánh Lợi nhuận và Đầu tư dài hạn

ggplot(data, aes(x=LongTermInvest, y=NetProfit)) +
geom_point(size=3, color="darkviolet") +
geom_smooth(method="lm", color="red", se=FALSE) +
geom_text(aes(label=Năm), vjust=-0.5, size=3) +
geom_vline(xintercept=mean(data$LongTermInvest), linetype="dashed") +
labs(title="Biểu đồ 20. Quan hệ giữa Lợi nhuận và Đầu tư dài hạn") +
theme_custom

Giải thích kỹ thuật (Code) Mục tiêu: Đoạn code này nhằm vẽ biểu đồ phân tán (scatter plot) để phân tích mối quan hệ giữa Đầu tư tài chính dài hạn (LongTermInvest) và Lợi nhuận thuần (NetProfit).

Các thành phần biểu đồ:

geom_point(): Hiển thị các điểm dữ liệu.

geom_smooth(method=“lm”): Vẽ đường hồi quy tuyến tính để thể hiện xu hướng chung.

geom_text(aes(label=Năm)): Cố gắng ghi nhãn các điểm dữ liệu bằng giá trị năm (sẽ thất bại vì cột Năm toàn NA).

geom_vline(): Vẽ đường thẳng đứng đánh dấu giá trị trung bình của Đầu tư dài hạn. Nhận xét về kết quả và ý nghĩa Kết luận: Biểu đồ này tiết lộ một nghịch lý trong chiến lược đầu tư của doanh nghiệp: Đầu tư dài hạn nhiều hơn không đồng nghĩa với lợi nhuận cao hơn. Điều này đặt ra câu hỏi về hiệu quả thực sự của các quyết định đầu tư dài hạn và chất lượng của danh mục đầu tư hiện tại. Đây là một vấn đề cần được quản lý và giám sát chặt chẽ.