Trong bối cảnh nền kinh tế toàn cầu ngày càng vận hành dựa trên dữ liệu, phân tích và khai thác thông tin từ dữ liệu tài chính đóng vai trò then chốt đối với các nhà phân tích, nhà đầu tư và các tổ chức tài chính. Ngôn ngữ lập trình R, với hệ sinh thái thư viện phong phú và khả năng xử lý thống kê mạnh mẽ, đã trở thành một công cụ phân tích dữ liệu hàng đầu, cho phép thực hiện các thao tác từ làm sạch, biến đổi, trực quan hóa đến mô hình hóa phức tạp.
Nghiên cứu này tập trung vào việc áp dụng ngôn ngữ R để thực hiện:
“Phân tích dữ liệu đầu tư và chỉ số tài chính của các công ty niêm yết trên sàn NYSE cho nhóm Hành vi, Giao dịch và Hiệu suất đầu tư”
Bộ dữ liệu nền tảng cho nghiên cứu là “400K NYSE Random Investments + Financial Ratios” thu thập từ nền tảng Kaggle. Đây là một bộ dữ liệu giả lập quy mô lớn, mô phỏng 405.258 giao dịch đầu tư ngẫu nhiên vào các cổ phiếu niêm yết trên Sàn giao dịch Chứng khoán New York (NYSE) trong giai đoạn 2013-2018. Dữ liệu kết hợp thông tin về giao dịch (ngày mua/bán, giá, số tiền), hiệu suất đầu tư (lợi suất, rủi ro), và các chỉ số tài chính cơ bản của doanh nghiệp tại thời điểm đầu tư.
Mục tiêu chính của nghiên cứu bao gồm:
Ohân tích dữ liệu bằng R: Thực hiện tuần tự các bước từ làm sạch, chuẩn bị dữ liệu, thống kê mô tả, đến trực quan hóa đa chiều.
Khám phá đặc điểm dữ liệu đầu tư NYSE: Phân tích cấu trúc, phân bố của các biến số chính (lợi suất, rủi ro, định giá, hiệu quả hoạt động) trong bộ dữ liệu mẫu.
Xác định các yếu tố ảnh hưởng đến hiệu suất đầu
tư: Sử dụng các phương pháp thống kê và trực quan hóa để khám
phá mối quan hệ giữa các chỉ số tài chính, đặc điểm giao dịch (thời gian
nắm giữ, ngành nghề), rủi ro và kết quả đầu tư thực tế
(Real_Return, investment GOOD/BAD).
Đánh giá sự khác biệt giữa các nhóm: So sánh
hiệu suất, rủi ro, và đặc điểm tài chính giữa các ngành
(sector), các chiến lược nắm giữ
(Holding_Period), và các nhóm kết quả đầu tư
(investment).
Tên: 400K NYSE Random Investments + Financial Ratios Dataset.
Nguồn: Kaggle (Dữ liệu giả lập dựa trên hoạt động thực tế tại NYSE).
Kích thước: 405.258 quan sát (giao dịch) x 25 biến (cột).
Loại dữ liệu: Hỗn hợp gồm biến định tính
(Factor: company, sector,
investment, ngày tháng dạng text) và biến định lượng
(Numeric: giá, lợi suất, chỉ số tài chính).
Phạm vi thời gian: Giao dịch mua được thực hiện từ năm 2013 đến 2018.
Mô tả chi tiết các biến:
| Nhóm Biến | Tên Biến Gốc | Mô tả |
|---|---|---|
| Định danh | X | ID tự tăng của dòng dữ liệu (sẽ bị loại bỏ) |
| Định danh | company | Mã cổ phiếu |
| Định danh | sector | Ngành nghề |
| Giao dịch | horizon (days) | Số ngày nắm giữ dự kiến |
| Giao dịch | amount | Số tiền đầu tư (USD) |
| Giao dịch | date_BUY_fix | Ngày mua (YYYY-MM-DD) |
| Giao dịch | date_SELL_fix | Ngày bán (YYYY-MM-DD, sẽ bị loại bỏ) |
| Giao dịch | price_BUY | Giá mua (USD) |
| Giao dịch | price_SELL | Giá bán (USD) |
| Hiệu suất & Rủi ro | nominal_return | Lợi suất danh nghĩa (tỷ lệ) |
| Hiệu suất & Rủi ro | expected_return (yearly) | Lợi suất kỳ vọng hàng năm (tỷ lệ) |
| Hiệu suất & Rủi ro | Volatility_Buy | Độ biến động giá khi mua |
| Hiệu suất & Rủi ro | Volatility_sell | Độ biến động giá khi bán |
| Hiệu suất & Rủi ro | Sharpe Ratio | Tỷ suất Sharpe |
| Hiệu suất & Rủi ro | inflation | Tỷ lệ lạm phát tại thời điểm bán (tỷ lệ) |
| Chỉ số Tài chính | PE_ratio | Tỷ số Giá/Thu nhập (P/E) |
| Chỉ số Tài chính | EPS_ratio | Thu nhập trên mỗi cổ phiếu (EPS) |
| Chỉ số Tài chính | PS_ratio | Tỷ số Giá/Doanh thu (P/S) |
| Chỉ số Tài chính | PB_ratio | Tỷ số Giá/Giá trị sổ sách (P/B) |
| Chỉ số Tài chính | current_ratio | Tỷ lệ Thanh khoản hiện hành |
| Chỉ số Tài chính | roa_ratio | Tỷ suất Sinh lời trên Tài sản (ROA, tỷ lệ) |
| Chỉ số Tài chính | roe_ratio | Tỷ suất Sinh lời trên Vốn CSH (ROE, tỷ lệ) |
| Chỉ số Tài chính | NetProfitMargin_ratio | Biên Lợi nhuận ròng (tỷ lệ) |
| Phân loại & Xếp hạng | investment | Kết quả đầu tư (GOOD/BAD) |
| Phân loại & Xếp hạng | ESG_ranking | Xếp hạng ESG |
Nghiên cứu này được cấu trúc thành các mục chính sau:
Giới thiệu và chuẩn bị dữ liệu (Mục 1.1): Trình
bày tổng quan về bộ dữ liệu NYSE, mục tiêu nghiên cứu, thực hiện các
bước kiểm tra chất lượng, làm sạch, xử lý và tạo các biến phái sinh cần
thiết. Kết quả là bộ dữ liệu nyse_clean_core (32 biến) sẵn
sàng cho phân tích.
Phân tích nhóm biến: Giao dịch, Hành vi và Hiệu suất (Mục
1.2): Tập trung phân tích thống kê các biến liên quan đến đặc
điểm giao dịch (amount, Horizon_Days), chiến
lược (Holding_Period), rủi ro (Volatility_Buy,
Sharpe_Ratio), và kết quả lợi suất tổng thể
(Nominal_Return, Real_Return,
investment).
Trực quan hoá dữ liệu (Mục 1.3): Trực quan hoá
các biến liên quan đến đặc điểm giao dịch (amount,
Horizon_Days), chiến lược (Holding_Period),
rủi ro (Volatility_Buy, Sharpe_Ratio), và kết
quả lợi suất tổng thể (Nominal_Return,
Real_Return, investment).
Kết luận và đề xuất (Mục 1.4): Tóm tắt các kết quả nổi bật nhất của nghiên cứu, chỉ ra những hạn chế và đề xuất các hướng nghiên cứu tiềm năng trong tương lai.
1. library(dplyr); library(readr); library(skimr); library(knitr)
2. num_obs_raw <- nrow(nyse_raw)
3. num_vars_raw <- ncol(nyse_raw)
4. cat("Số lượng quan sát (giao dịch):", format(num_obs_raw, big.mark = ","), "\n") Số lượng quan sát (giao dịch): 405,258
Số lượng biến (trường thông tin): 25
1. if("date_BUY_fix" %in% names(nyse_raw)) {
2. nyse_raw <- nyse_raw %>% mutate(date_BUY_fix = as.Date(date_BUY_fix))}
3. if("date_SELL_fix" %in% names(nyse_raw)) {
4. nyse_raw <- nyse_raw %>% mutate(date_SELL_fix = as.Date(date_SELL_fix))}Giải thích code:
| Dòng code | Giải thích |
|---|---|
| 1 | Tải các gói thư viện R: dplyr (thao tác dữ
liệu), readr (đọc/ghi dữ liệu), skimr (tóm tắt
nhanh), và knitr (tạo báo cáo). |
| 2 | Tính số lượng quan sát (hàng) của
nyse_raw bằng hàm nrow() và lưu vào biến
num_obs_raw. |
| 3 | Tính số lượng biến (cột) của
nyse_raw bằng hàm ncol() và lưu vào biến
num_vars_raw. |
| 4-5 | Dùng hàm cat() để in ra màn
hình số lượng quan sát và số lượng biến. Hàm
format(..., big.mark = ",") được dùng để định dạng số lớn
có dấu phẩy hàng nghìn. |
| Dòng code | Giải thích |
|---|---|
| 6-7 | Kiểm tra điều kiện: Nếu cột
date_BUY_fix tồn tại (%in% names(nyse_raw)),
thì sử dụng mutate() để chuyển kiểu dữ
liệu của cột này thành kiểu Date (ngày tháng) bằng
hàm as.Date(). |
| 8-9 | Kiểm tra điều kiện: Tương tự, nếu cột
date_SELL_fix tồn tại, tiến hành chuyển kiểu dữ
liệu của cột này sang kiểu Date. |
Xác nhận dữ liệu đã được tải thành công bằng cách kiểm tra kích thước (405,258 quan sát, 25 biến) và chuẩn bị các biến ngày tháng bằng cách chuyển đổi chúng sang kiểu Date chính xác, đảm bảo dữ liệu sẵn sàng cho các phân tích dựa trên thời gian.
Kiểm tra cấu trúc dữ liệu (str): tibble [405,258 × 25] (S3: tbl_df/tbl/data.frame) $ …1 : num [1:405258] 0 1 2 3 4 5 7 8 9 10 … $ company : chr [1:405258] “BBY” “BAC” “AXP” “KSS” … $ sector : chr [1:405258] “RETAIL” “BANK” “BANK” “RETAIL” … $ horizon (days) : num [1:405258] 2 330 7 5 360 15 720 600 30 6 … $ amount : num [1:405258] 100 15000 3000 20000 15000 50000 1500 300 50000 400 … $ date_BUY_fix : Date[1:405258], format: “2017-05-25” “2016-11-22” … $ date_SELL_fix : Date[1:405258], format: “2017-05-26” “2017-10-18” … $ price_BUY : num [1:405258] 55.6 18.6 59.9 38.2 51.9 … $ price_SELL : num [1:405258] 53.5 24.7 59.5 36 52 … $ Volatility_Buy : num [1:405258] 0.384 0.323 0.239 0.429 0.195 … $ Volatility_sell : num [1:405258] 0.386 0.236 0.235 0.429 0.254 … $ Sharpe Ratio : num [1:405258] 0.384 0.323 0.239 0.429 0.195 … $ expected_return (yearly): num [1:405258] 0.0014373 0.1709691 0.0028236 0.0000939 0.1499787 … $ inflation : num [1:405258] 1.96 -0.2 -0.2 -0.2 -0.5 -0.2 -0.15 -0.2 -0.5 -0.2 … $ nominal_return : num [1:405258] -0.03722 0.32432 -0.00576 -0.05839 0.00344 … $ investment : chr [1:405258] “BAD” “GOOD” “BAD” “BAD” … $ ESG_ranking : num [1:405258] 12 26.3 19.8 12.9 27.9 17.6 31.6 24.8 26.3 27.9 … $ PE_ratio : num [1:405258] 12.58 11.39 10.58 11.09 9.38 … $ EPS_ratio : num [1:405258] 3.73 1.26 5.64 3.27 5.46 4.56 1.05 1.71 0.96 5.99 … $ PS_ratio : num [1:405258] 0.38 1.71 1.67 0.36 1.87 … $ PB_ratio : num [1:405258] 3.19 0.54 2.6 1.25 0.81 … $ NetProfitMargin_ratio : num [1:405258] 3.01 15.7 15.68 3.17 19.91 … $ current_ratio : num [1:405258] 1.49 0.92 1.91 1.6 0.99 … $ roa_ratio : num [1:405258] 8.69 0.67 3.39 4.41 0.81 … $ roe_ratio : num [1:405258] 26.69 5.54 25.78 11.35 8.91 …
| company | sector | date_BUY_fix | amount | price_BUY | nominal_return | PE_ratio | investment |
|---|---|---|---|---|---|---|---|
| BBY | RETAIL | 2017-05-25 | 100 | 55.6 | -0.037 | 12.58 | BAD |
| BAC | BANK | 2016-11-22 | 15000 | 18.6 | 0.324 | 11.39 | GOOD |
| AXP | BANK | 2016-09-27 | 3000 | 59.9 | -0.006 | 10.58 | BAD |
| KSS | RETAIL | 2016-10-11 | 20000 | 38.2 | -0.058 | 11.09 | BAD |
| JPM | BANK | 2015-03-12 | 15000 | 51.9 | 0.003 | 9.38 | GOOD |
| PEP | FMCG | 2016-09-09 | 50000 | 91.8 | 0.032 | 21.03 | GOOD |
Giải thích code:
| Dòng code | Giải thích |
|---|---|
| 1-2 | In tiêu đề và sử dụng hàm str() để
hiển thị cấu trúc nội tại của bộ dữ liệu
nyse_raw (kiểu dữ liệu, tên cột, và vài giá trị đầu
tiên). |
| 3-4 | Khai báo một vector tên là
selected_cols_for_head, chứa danh sách các cột tiêu
biểu (như company, sector,
date_BUY_fix,…) mà muốn xem trước. |
| 5 | Lọc danh sách cột ở trên: chỉ giữ lại những tên cột
thực sự tồn tại (%in% colnames(nyse_raw))
trong bộ dữ liệu gốc để tránh lỗi. |
| 6-8 | Hiển thị dữ liệu mẫu: * dplyr::select(all_of(...)): Chọn các cột
đã được lọc.* head(...): Lấy 6 quan
sát đầu tiên.* kable(...): Định dạng
kết quả thành bảng (format = "pipe") với chú thích
(caption) đã đặt. |
| 9 | In một dòng trống (\n) để tạo khoảng cách
cho định dạng hiển thị. |
Kết quả từ str() cung cấp thông tin chi tiết về kiểu dữ
liệu của từng biến (Numeric, Character, Date), xác nhận các bước chuẩn
hóa cần thực hiện ở Mục 1.1.3 (chuyển Character thành Factor, loại bỏ
cột index ...1). Bảng hiển thị 6 dòng đầu (Bảng 1.1.2) chỉ
bao gồm một tập hợp các cột tiêu biểu (company,
sector, date_BUY_fix, amount,
price_BUY, nominal_return,
PE_ratio, investment), cung cấp cái nhìn ban
đầu về dữ liệu thực tế.
── Data Summary ────────────────────────
Values
Name nyse_raw
Number of rows 405258
Number of columns 25
_______________________
Column type frequency:
character 3
Date 2
numeric 20
________________________
Group variables None
── Variable type: character ────────────────────────────────────────────────────
skim_variable n_missing complete_rate min max empty n_unique whitespace
1 company 0 1 1 4 0 27 0
2 sector 0 1 4 6 0 5 0
3 investment 0 1 3 4 0 2 0
── Variable type: Date ─────────────────────────────────────────────────────────
skim_variable n_missing complete_rate min max median
1 date_BUY_fix 0 1 2013-10-10 2018-10-09 2016-05-19
2 date_SELL_fix 0 1 2013-10-11 2020-09-28 2016-11-21
n_unique
1 1248
2 1729
── Variable type: numeric ──────────────────────────────────────────────────────
skim_variable n_missing complete_rate mean sd
1 ...1 0 1 249936. 144240.
2 horizon (days) 0 1 187. 211.
3 amount 0 1 8108. 12774.
4 price_BUY 0 1 105. 217.
5 price_SELL 0 1 117. 250.
6 Volatility_Buy 0 1 0.254 0.0981
7 Volatility_sell 0 1 0.260 0.0994
8 Sharpe Ratio 0 1 0.254 0.0981
9 expected_return (yearly) 0 1 0.0708 0.143
10 inflation 0 1 0.578 1.04
11 nominal_return 0 1 0.0710 0.299
12 ESG_ranking 0 1 22.6 6.51
13 PE_ratio 0 1 30.3 84.7
14 EPS_ratio 0 1 3.44 4.39
15 PS_ratio 0 1 2.72 3.60
16 PB_ratio 0 1 4.69 5.91
17 NetProfitMargin_ratio 0 1 9.21 10.4
18 current_ratio 0 1 2.17 2.56
19 roa_ratio 0 1 5.60 5.98
20 roe_ratio 0 1 15.6 17.5
p0 p25 p50 p75 p100 hist
1 0 125063. 249938. 374755. 499999 ▇▇▇▇▇
2 1 15 90 300 720 ▇▂▂▁▁
3 50 400 2000 10000 50000 ▇▁▁▁▁
4 7.14 28.3 46.2 76.3 2040. ▇▁▁▁▁
5 4.01 28.4 48.0 81.6 3451. ▇▁▁▁▁
6 0.0904 0.185 0.232 0.307 0.698 ▇▇▃▁▁
7 0.0904 0.189 0.238 0.316 0.923 ▇▅▁▁▁
8 0.0904 0.185 0.232 0.307 0.698 ▇▇▃▁▁
9 -0.278 0.00106 0.0128 0.0826 1.02 ▁▇▁▁▁
10 -0.5 -0.2 -0.15 1.68 1.96 ▇▁▁▁▅
11 -0.872 -0.0284 0.0141 0.111 8.85 ▇▁▁▁▁
12 12 16.3 25.1 27.9 31.6 ▇▅▂▇▇
13 0 9.82 13.7 23.5 1117. ▇▁▁▁▁
14 -6.56 1.46 2.96 4.56 29.9 ▂▇▁▁▁
15 0.16 0.47 1.71 3.18 24.5 ▇▁▁▁▁
16 0 1.22 3.05 5.23 47.6 ▇▁▁▁▁
17 -24.6 2.62 7.7 15.8 62 ▁▇▆▁▁
18 0.61 0.98 1.22 1.79 13.6 ▇▁▁▁▁
19 -13.0 1.74 5.71 7.99 38.1 ▁▇▃▁▁
20 -99.5 8.83 16.1 26.0 57.2 ▁▁▁▇▃
Giải thích code:
| Dòng code | Giải thích |
|---|---|
| 1 | Tải các gói thư viện cần thiết: skimr (để
tóm tắt dữ liệu nhanh) và dplyr (thao tác dữ liệu). |
| 2 | Sử dụng hàm skim() từ gói
skimr để tạo bản tóm tắt thống kê chi tiết
cho toàn bộ bộ dữ liệu nyse_raw và lưu kết quả vào biến
skim_summary. |
| 3 | In ra màn hình bản tóm tắt
skim_summary, bao gồm các thông tin như kiểu dữ liệu, số
lượng giá trị thiếu (NA), giá trị trung bình, độ lệch chuẩn, tứ phân vị,
v.v., cho từng cột. |
Bảng tóm tắt tổng quan từ skimr::skim() cung cấp cái
nhìn nhanh về toàn bộ bộ dữ liệu:
Xác nhận kích thước & kiểu: Tái xác nhận số
dòng/cột và phân loại kiểu dữ liệu (character,
Date, numeric).
Kiểm tra NA: Xác nhận lại không có giá trị
khuyết thiếu (n_missing = 0,
complete_rate = 1).
Phân bố sơ bộ (Numeric): Phần tóm tắt cho các
biến số (numeric) hiển thị các thống kê chính (min, max,
mean, sd, p0, p25, p50, p75, p100). Các dấu hiệu về độ lệch (so sánh
mean vs p50) và phạm vi rộng vẫn được thể hiện rõ qua các con số thống
kê.
Phân bố sơ bộ (Character/Date): Cung cấp thông
tin về số lượng giá trị duy nhất (n_unique), giá trị bắt
đầu/kết thúc (cho Date), giúp đánh giá tính đa dạng và phạm vi của dữ
liệu.
1. na_counts <- colSums(is.na(nyse_raw))
2. cols_with_na <- na_counts[na_counts > 0]
3. if (length(cols_with_na) == 0) {
4. cat("Xác nhận: Không có giá trị khuyết thiếu (NA) trong bất kỳ cột nào.\n")
5. } else {
6. cat("Các cột chứa giá trị NA và số lượng tương ứng:\n")
7. na_table <- data.frame( TenBien = names(cols_with_na), SoLuongNA = as.integer(cols_with_na) )
8. print(kable(na_table, caption = "Thống kê giá trị khuyết thiếu (NA) theo biến",
9. format = "pipe", align = "l"))}Xác nhận: Không có giá trị khuyết thiếu (NA) trong bất kỳ cột nào.
Giải thích code:
| Dòng code | Giải thích |
|---|---|
| 1 | Tính số lượng NA: Dùng hàm
is.na() để kiểm tra từng ô, sau đó dùng
colSums() để tính tổng số giá trị NA trên từng
cột của nyse_raw. Kết quả lưu vào
na_counts. |
| 2 | Lọc cột chứa NA: Lọc ra các cột có số
lượng NA lớn hơn 0 và lưu vào biến
cols_with_na. |
| 3-4 | Kiểm tra không có NA: Nếu độ dài
(length) của cols_with_na bằng 0 (tức là không
có cột nào chứa NA), in ra thông báo xác nhận không có giá
trị khuyết thiếu. |
| 5-9 | Xử lý cột chứa NA: Nếu có cột chứa
NA: * 6: In tiêu đề. * 7: Tạo một data.frame
(na_table) chứa tên cột (names(cols_with_na))
và số lượng NA tương ứng. * 8-9: Dùng kable() để hiển thị bảng thống kê số lượng
NA cho từng cột theo định dạng bảng Markdown
(format = "pipe"). |
Kết quả kiểm tra: không có giá trị khuyết thiếu (NA) nào trong bộ dữ
liệu nyse_raw.
1. num_duplicates <- sum(duplicated(nyse_raw))
2. cat("Số lượng bản ghi trùng lặp hoàn toàn:", num_duplicates, "\n")Số lượng bản ghi trùng lặp hoàn toàn: 0
1. if (num_duplicates == 0) {
2. cat("Xác nhận: Không có bản ghi trùng lặp hoàn toàn.\n")} else {
3. cat("Cảnh báo: Phát hiện", num_duplicates, "bản ghi trùng lặp. Cần xem xét loại bỏ.\n")}Xác nhận: Không có bản ghi trùng lặp hoàn toàn.
Giải thích code:
| Dòng code | Giải thích |
|---|---|
| 1 | Tính số lượng bản ghi trùng lặp: Hàm
duplicated(nyse_raw) trả về một vector
TRUE/FALSE cho biết hàng nào là bản sao lặp
lại của hàng trước đó. Hàm sum() tính tổng số lần lặp
(TRUE) và lưu vào biến num_duplicates. |
| 2 | In ra màn hình tổng số lượng bản ghi trùng lặp hoàn toàn được tìm thấy. |
| 3-4 | Kiểm tra không trùng lặp: Nếu
num_duplicates bằng 0, in ra thông báo xác nhận không có
bản ghi nào bị trùng lặp hoàn toàn. |
| 4-5 | Xử lý có trùng lặp: Nếu
num_duplicates lớn hơn 0, in ra cảnh báo
về số lượng bản ghi trùng lặp cần được xem xét và loại bỏ. |
Kết quả kiểm tra xác nhận không có bản ghi nào bị trùng lặp hoàn toàn.
Tổng kết mục 1.1.2: Bộ dữ liệu nyse_raw
(đã kiểm tra và chuyển đổi Date) có quy mô lớn, chất lượng dữ liệu ban
đầu tốt (không NA, không trùng lặp). Tuy nhiên, cần chuẩn hóa các kiểu
dữ liệu character thành Factor, loại bỏ cột
index ...1, và xử lý các biến numeric có độ
lệch cao (như đã thấy qua skim()).
1. nyse_processed <- nyse_raw %>%
2. dplyr::select(-any_of(c("...1", "date_SELL_fix"))) %>%
3. dplyr::rename(
4. Horizon_Days = `horizon (days)`,
5. Date_Buy = date_BUY_fix,
6. Expected_Return_Yr = `expected_return (yearly)`,
7. Nominal_Return = nominal_return,
8. Sharpe_Ratio = `Sharpe Ratio`,
9. NetProfitMargin = NetProfitMargin_ratio,
10. Current_Ratio = current_ratio,
11. ROA = roa_ratio,
12. ROE = roe_ratio,
13. EPS = EPS_ratio,
14. PS = PS_ratio,
15. PB = PB_ratio,
16. PE = PE_ratio,
17. ESG = ESG_ranking)
18. nyse_processed <- nyse_processed %>%
19. dplyr::mutate(dplyr::across(c(company, sector, investment), as.factor))
20. cat("Số lượng biến sau khi dọn dẹp và đổi tên:", ncol(nyse_processed), "\n")Số lượng biến sau khi dọn dẹp và đổi tên: 23
Kiểu dữ liệu mới 'sector': factor
Kiểu dữ liệu mới 'Date_Buy': Date
6 tên cột đầu tiên:
[1] "company" "sector" "Horizon_Days" "amount" "Date_Buy"
[6] "price_BUY"
Giải thích code:
| Dòng code | Giải thích |
|---|---|
| 1-17 | Dọn dẹp và đổi tên cột: * 2: Sử dụng dplyr::select() với
any_of() để loại bỏ các cột không cần
thiết như cột chỉ mục tự động ("...1") và cột
date_SELL_fix. * 3-17: Sử dụng dplyr::rename() để đổi tên các cột có tên
dài, chứa ký tự đặc biệt, hoặc không nhất quán thành các tên mới chuẩn
hóa (ví dụ: horizon (days) thành Horizon_Days,
PE_ratio thành PE,…). |
| 18-19 | Chuyển kiểu dữ liệu: * Sử dụng dplyr::mutate() và dplyr::across() để
chuyển đổi kiểu dữ liệu của các cột
company, sector, và investment
sang kiểu factor (yếu tố/phân loại), phù hợp cho các biến
định danh. |
| 20 | In ra số lượng biến còn lại
(ncol()) sau khi đã loại bỏ cột và đổi tên. |
| 21-22 | In ra để xác nhận kiểu dữ liệu mới của
cột phân loại (sector) và cột ngày tháng
(Date_Buy) đã được chuyển đổi ở bước trước. |
| 23-24 | In ra 6 tên cột đầu tiên của bộ dữ
liệu đã được xử lý (nyse_processed) để kiểm tra việc đổi
tên. |
Đã loại bỏ các cột thừa, chuẩn hóa tên gọi các biến quan trọng, và
chuyển đổi các biến phân loại sang kiểu Factor. Bộ dữ liệu
hiện có 23 biến với tên và kiểu dữ liệu cơ bản đã được chuẩn hóa.
Khai thác thông tin từ biến ngày mua (Date_Buy) và thời
gian nắm giữ (Horizon_Days) để tạo ra các biến mới cho phân
tích xu hướng và chiến lược.
1. nyse_processed <- nyse_processed %>% mutate(
2. Horizon_Days_Int = as.integer(Horizon_Days),
3. Date_Sell = Date_Buy + Horizon_Days_Int,
4. Holding_Days_Calc = as.numeric(difftime(Date_Sell, Date_Buy, units = "days")))
5. consistency_check <- nyse_processed %>%
6. summarise(Max_Diff_Holding_Days = max(abs(Horizon_Days - Holding_Days_Calc), na.rm = TRUE))
7. cat("Kiểm tra nhất quán thời gian nắm giữ (Chênh lệch tối đa):",
8. round(consistency_check$Max_Diff_Holding_Days, 5), "ngày\n") Kiểm tra nhất quán thời gian nắm giữ (Chênh lệch tối đa): 0 ngày
1. nyse_processed <- nyse_processed %>% mutate(
2. Buy_Year = year(Date_Buy),
3. Buy_Quarter_Num = quarter(Date_Buy),
4. Buy_Month = month(Date_Buy, label = TRUE, abbr = FALSE),
5. Buy_Quarter = paste0(Buy_Year, ".", Buy_Quarter_Num))
6. breaks_days <- c(0, 30, 180, Inf)
7. labels_period <- c("Short-term (<=30d)", "Mid-term (31-180d)", "Long-term (>180d)")
8. nyse_processed <- nyse_processed %>% mutate(
9. Holding_Period = cut(Horizon_Days,
10. breaks = breaks_days,
11. labels = labels_period,
12. right = TRUE, include.lowest = TRUE))
13. kable(head(nyse_processed %>% select(Date_Buy, Horizon_Days, Date_Sell, Holding_Period, Buy_Year, Buy_Quarter)),
14. caption = "Bảng 1.1.3: Ví dụ về các biến thời gian đã tạo", format="pipe")| Date_Buy | Horizon_Days | Date_Sell | Holding_Period | Buy_Year | Buy_Quarter |
|---|---|---|---|---|---|
| 2017-05-25 | 2 | 2017-05-27 | Short-term (<=30d) | 2017 | 2017.2 |
| 2016-11-22 | 330 | 2017-10-18 | Long-term (>180d) | 2016 | 2016.4 |
| 2016-09-27 | 7 | 2016-10-04 | Short-term (<=30d) | 2016 | 2016.3 |
| 2016-10-11 | 5 | 2016-10-16 | Short-term (<=30d) | 2016 | 2016.4 |
| 2015-03-12 | 360 | 2016-03-06 | Long-term (>180d) | 2015 | 2015.1 |
| 2016-09-09 | 15 | 2016-09-24 | Short-term (<=30d) | 2016 | 2016.3 |
Giải thích code:
| Dòng code | Giải thích |
|---|---|
| 1-4 | Tính toán thời gian nắm giữ: * 2: Tạo biến Horizon_Days_Int bằng cách
chuyển cột Horizon_Days (từ bước trước) sang kiểu số nguyên
(integer). * 3: Tính Ngày bán ( Date_Sell) bằng cách cộng
Date_Buy với số ngày nắm giữ
(Horizon_Days_Int). * 4: Tính Số ngày nắm giữ thực tế ( Holding_Days_Calc) bằng cách dùng difftime()
để tính chênh lệch ngày giữa Date_Sell và
Date_Buy. |
| 5-7 | Kiểm tra tính nhất quán: * 6: Tính Max_Diff_Holding_Days (chênh lệch
tối đa) giữa giá trị Horizon_Days gốc và giá trị tính toán
(Holding_Days_Calc). * 7-8: In ra kết quả kiểm tra để xác nhận hai giá trị này có nhất quán hay không (chênh lệch lý tưởng bằng 0). |
| 9-13 | Tạo biến thời gian chiết xuất: * Sử dụng mutate() để tạo các biến thời gian
chiết xuất từ Date_Buy: Buy_Year (năm),
Buy_Quarter_Num (số quý), Buy_Month (tháng),
và Buy_Quarter (kết hợp Năm.Quý). |
| 14-15 | Thiết lập khoảng phân loại: Định nghĩa
các điểm cắt (breaks_days) và nhãn
(labels_period) cho việc phân loại thời gian nắm giữ thành
các nhóm “ngắn hạn”, “trung hạn”, “dài hạn”. |
| 16-19 | Phân loại thời gian nắm giữ: Sử dụng
hàm cut() để phân loại cột Horizon_Days thành
biến phân loại Holding_Period dựa trên các khoảng và nhãn
đã định nghĩa. |
| 20-21 | Hiển thị mẫu: Dùng head()
và kable() để in ra 6 quan sát đầu tiên của các cột thời
gian mới tạo ra để kiểm tra. |
Các biến thời gian đã được xử lý. Ngày bán được tính toán, thời gian
nắm giữ được kiểm tra tính nhất quán. Các biến chu kỳ
Buy_Year, Buy_Quarter, Buy_Month
được trích xuất để phân tích xu hướng và tính thời vụ. Biến
Holding_Period phân loại các giao dịch theo chiến lược nắm
giữ (Ngắn, Trung, Dài hạn).
Tạo ra các biến mới có ý nghĩa kinh tế/tài chính từ các biến gốc để phục vụ cho các phân tích sau.
1. nyse_processed <- nyse_processed %>%
2. dplyr::mutate(Real_Return = ((1 + Nominal_Return) / (1 + inflation)) - 1) %>%
3. dplyr::mutate(PE_Positive = dplyr::if_else(PE > 0, PE, NA_real_),
4. Log_PE = log(PE_Positive)) %>%
5. dplyr::mutate(Leverage_Ratio = dplyr::if_else(abs(ROA) > 0.001, ROE/ROA, NA_real_))
6. vol_quantiles <- quantile(nyse_processed$Volatility_Buy, probs = c(0, 0.33, 0.66, 1), na.rm = TRUE)
7. nyse_processed <- nyse_processed %>%
8. dplyr::mutate(
9. Risk_Level = cut(Volatility_Buy,
10. breaks = vol_quantiles,
11. labels = c("Low", "Medium", "High"),
12. include.lowest = TRUE, right = TRUE))
13. amount_quantiles <- quantile(nyse_processed$amount, probs = c(0, 0.33, 0.66, 1), na.rm = TRUE)
14. nyse_processed <- nyse_processed %>%
15. dplyr::mutate(
16. Amount_Level = cut(amount,
17. breaks = amount_quantiles,
18. labels = c("Small", "Medium", "Large"),
19. include.lowest = TRUE, right = TRUE))
20. cat("Tạo các biến phái sinh: Real_Return, Log_PE, Leverage_Ratio, Risk_Level, Amount_Level.\n")Tạo các biến phái sinh: Real_Return, Log_PE, Leverage_Ratio, Risk_Level, Amount_Level.
1. summary(nyse_processed %>% dplyr::select(Real_Return, Log_PE, Leverage_Ratio, Risk_Level, Amount_Level)) Real_Return Log_PE Leverage_Ratio Risk_Level
Min. :-0.9523 Min. :1.13 Min. : 1.10 Low :133740
1st Qu.:-0.6208 1st Qu.:2.38 1st Qu.: 2.49 Medium:133747
Median : 0.1673 Median :2.69 Median : 3.44 High :137771
Mean : 0.0868 Mean :2.88 Mean : 5.21
3rd Qu.: 0.4859 3rd Qu.:3.21 3rd Qu.: 6.69
Max. :16.3533 Max. :7.02 Max. :34.55
NA's :39751 NA's :2468
Amount_Level
Small :142692
Medium:143246
Large :119320
Giải thích code
| Dòng code | Giải thích |
|---|---|
| 1-7 | Tạo các biến liên tục phái sinh: * Real_Return: Tính Lợi suất thực tế bằng
công thức điều chỉnh lạm phát (sử dụng biến inflation).
* Log_PE: Tạo biến
PE_Positive (bằng PE nếu \(> 0\), ngược lại là NA) sau
đó tính Logarit tự nhiên của nó. * Leverage_Ratio: Tính Tỷ lệ đòn
bẩy bằng ROE/ROA, gán NA nếu
ROA quá gần 0 để tránh phép chia không xác định. |
| 8 | Tính các phân vị (0%, 33%, 66%, 100%)
của biến Volatility_Buy và lưu vào
vol_quantiles để phân nhóm. |
| 9-13 | Phân nhóm rủi ro: Sử dụng hàm
cut() để phân loại Volatility_Buy thành 3 cấp
độ Risk_Level (Low, Medium,
High) dựa trên các điểm phân vị vừa tính. |
| 14 | Tính các phân vị (0%, 33%, 66%, 100%)
của biến amount và lưu vào amount_quantiles để
phân nhóm quy mô giao dịch. |
| 15-19 | Phân nhóm quy mô: Sử dụng hàm
cut() để phân loại amount thành 3 cấp độ
Amount_Level (Small, Medium,
Large) dựa trên các điểm phân vị vừa tính. |
| 20-22 | In thông báo và hiển thị tóm tắt thống
kê (summary) của các biến phái sinh mới tạo
ra. |
Năm biến phái sinh quan trọng đã được tạo ra:
Real_Return (Lợi suất thực), Log_PE (P/E chuẩn
hóa), Leverage_Ratio (Đòn bẩy), Risk_Level
(Phân loại rủi ro), và Amount_Level (Phân loại quy mô GD).
Các biến này sẽ là trọng tâm trong các phân tích thống kê và trực quan
hóa tiếp theo. Kết quả summary() sơ bộ cho thấy phân bố của
các biến này (Real_Return có Median dương,
Log_PE đối xứng hơn, Leverage_Ratio lệch phải,
Risk_Level và Amount_Level phân bổ đều).
Tạo ra hai bộ dữ liệu con và lưu chúng lại.
1. common_vars <- c("company", "sector", "Date_Buy", "Buy_Year", "Buy_Quarter", "investment", "Real_Return")
2. group1_vars <- c(common_vars,
3. "amount", "Amount_Level", "Horizon_Days", "Holding_Period",
4. "Volatility_Buy", "Risk_Level", "Sharpe_Ratio", "Nominal_Return",
5. "inflation", "price_BUY", "price_SELL", "Expected_Return_Yr")
6. group1_vars <- unique(group1_vars)
7. group2_vars <- c(common_vars,
8. "Log_PE", "PB", "PS", "ROE", "ROA", "EPS",
9. "NetProfitMargin", "Current_Ratio", "Leverage_Ratio", "ESG")
10. group2_vars <- unique(group2_vars)
11. nyse_group1_data <- nyse_processed %>% dplyr::select(intersect(group1_vars, colnames(nyse_processed)))
12. nyse_group2_data <- nyse_processed %>% dplyr::select(intersect(group2_vars, colnames(nyse_processed)))
13. cat("\nNhóm 1 (Giao dịch, Hành vi, Hiệu suất):\n")
Nhóm 1 (Giao dịch, Hành vi, Hiệu suất):
Số biến: 19
[1] "company" "sector" "Date_Buy"
[4] "Buy_Year" "Buy_Quarter" "investment"
[7] "Real_Return" "amount" "Amount_Level"
[10] "Horizon_Days" "Holding_Period" "Volatility_Buy"
[13] "Risk_Level" "Sharpe_Ratio" "Nominal_Return"
[16] "inflation" "price_BUY" "price_SELL"
[19] "Expected_Return_Yr"
Nhóm 2 (Chỉ số Công ty, Định giá, ESG):
Số biến: 17
[1] "company" "sector" "Date_Buy" "Buy_Year"
[5] "Buy_Quarter" "investment" "Real_Return" "Log_PE"
[9] "PB" "PS" "ROE" "ROA"
[13] "EPS" "NetProfitMargin" "Current_Ratio" "Leverage_Ratio"
[17] "ESG"
1. save(nyse_processed, nyse_group1_data, nyse_group2_data,
2. file = "nyse_processed_groups.RData")
3. cat("\n Lưu 3 bộ dữ liệu (nyse_processed, nyse_group1_data, nyse_group2_data) vào file: nyse_processed_groups.RData\n")
Lưu 3 bộ dữ liệu (nyse_processed, nyse_group1_data, nyse_group2_data) vào file: nyse_processed_groups.RData
Giải thích code
| Dòng code | Giải thích |
|---|---|
| 1 | Khai báo một vector common_vars chứa danh
sách các biến chung sẽ được sử dụng trong cả hai nhóm
dữ liệu. |
| 2-5 | Khai báo và chuẩn hóa vector group1_vars,
chứa các biến thuộc nhóm 1 (liên quan đến Giao dịch,
Hành vi, Hiệu suất) bằng cách kết hợp common_vars với các
biến riêng và loại bỏ trùng lặp (unique). |
| 6-9 | Khai báo và chuẩn hóa vector group2_vars,
chứa các biến thuộc nhóm 2 (liên quan đến Chỉ số Công
ty, Định giá, ESG) bằng cách kết hợp common_vars với các
biến riêng và loại bỏ trùng lặp. |
| 10 | Tạo bộ dữ liệu nhóm 1: Chọn từ
nyse_processed các cột nằm trong cả
group1_vars và tồn tại trong
nyse_processed (intersect) để tạo
nyse_group1_data. |
| 11 | Tạo bộ dữ liệu nhóm 2: Tương tự, chọn
các cột giao nhau giữa group2_vars và
nyse_processed để tạo nyse_group2_data. |
| 12-16 | In thông tin tóm tắt: In ra số lượng
biến (ncol()) và danh sách tên cột
(colnames()) của từng bộ dữ liệu nhóm (nhóm 1 và nhóm 2) để
kiểm tra. |
| 17-18 | Lưu dữ liệu: Sử dụng hàm
save() để lưu cả ba bộ dữ liệu
(nyse_processed, nyse_group1_data,
nyse_group2_data) vào một file nén định dạng R
(nyse_processed_groups.RData) để sử dụng cho các phân tích
tiếp theo. |
Bộ dữ liệu gốc (nyse_raw) đã được xử lý thành
nyse_processed (37 biến, bao gồm cả cột tính toán trung
gian). Từ đó, hai bộ dữ liệu con đã được tạo ra:
nyse_group1_data (chứa các biến chung và biến Nhóm 1 - Giao
dịch/Hành vi/Hiệu suất) và nyse_group2_data (chứa các biến
chung và biến Nhóm 2 - Chỉ số Công ty/Định giá/ESG). Cả ba bộ dữ liệu
này được lưu vào file nyse_processed_groups.RData.
Tổng kết mục 1.1.3: Quá trình tiền xử lý dữ liệu đã hoàn tất. Đã thực hiện các bước dọn dẹp, chuẩn hóa, xử lý thời gian, tạo biến phái sinh, và cuối cùng là phân chia dữ liệu thành hai nhóm biến chính. Các bộ dữ liệu sạch đã được lưu lại. Sau khi chuẩn hóa và tạo các biến phái sinh trọng yếu (Real_Return, Risk_Level, Amount_Level,…), phần 1.2 sẽ lượng hóa đặc trưng phân phối, mối liên hệ và khác biệt nhóm của các biến giao dịch – hành vi– hiệu suất trong nyse_group1_data
1. vars_g1_numeric <- c("Real_Return", "Nominal_Return", "amount", "Horizon_Days",
2. "Volatility_Buy", "Sharpe_Ratio", "Expected_Return_Yr",
3. "inflation", "price_BUY", "price_SELL")
4. summary_stats_g1_wide <- nyse_group1_finite %>%
5. select(any_of(vars_g1_numeric)) %>%
6. summarise(across(everything(),
7. list( Min = ~ min(.x, na.rm = TRUE),
8. Q1 = ~ quantile(.x, 0.25, na.rm = TRUE),
9. Median = ~ median(.x, na.rm = TRUE),
10. Mean = ~ mean(.x, na.rm = TRUE),
11. Q3 = ~ quantile(.x, 0.75, na.rm = TRUE),
12. Max = ~ max(.x, na.rm = TRUE)),
13. .names = "{.col}_{.fn}" ))
14. summary_g1_final_table <- summary_stats_g1_wide %>%
15. as.matrix() %>% t() %>% as.data.frame() %>%
16. rownames_to_column("VarStat") %>%
17. mutate( Statistic = factor(gsub(".*_", "", VarStat),
18. levels = c("Min","Q1","Median","Mean","Q3","Max")),
19. Variable = gsub("_.*", "", VarStat)) %>%
20. rename(Value = V1) %>%
21. select(Variable, Statistic, Value) %>%
22. pivot_wider(names_from = Statistic, values_from = Value) %>%
23. mutate(across(where(is.numeric), ~ round(.x, 3))) %>%
24. arrange(Variable)
25. kable(summary_g1_final_table,
26. caption = "Bảng 1.2.1: Thống kê mô tả (Giao dịch, Hành vi, Hiệu suất đầu tư)",
27. col.names = c("Tên Biến", "Min", "Q1", "Median", "Mean", "Q3", "Max"),
28. format = "pipe", align = "l")| Tên Biến | Min | Q1 | Median | Mean | Q3 | Max |
|---|---|---|---|---|---|---|
| Expected | -0.278 | 0.00106 | 0.0128 | 0.0708 | 0.0826 | 1.02 |
| Horizon | 1 | 15 | 90 | 187 | 300 | 720 |
| Nominal | -0.872 | -0.0284 | 0.0141 | 0.071 | 0.111 | 8.85 |
| Real | -0.952 | -0.621 | 0.167 | 0.0868 | 0.486 | 16.4 |
| Sharpe | 0.0904 | 0.185 | 0.232 | 0.254 | 0.307 | 0.698 |
| Volatility | 0.0904 | 0.185 | 0.232 | 0.254 | 0.307 | 0.698 |
| amount | 50 | 400 | 2000 | 8108 | 10000 | 50000 |
| inflation | -0.5 | -0.2 | -0.15 | 0.578 | 1.68 | 1.96 |
| price | 7.14, 4.01 | 28.3, 28.4 | 46.2, 48.0 | 105, 117 | 76.3, 81.6 | 2040, 3451 |
Giải thích code
| Dòng code | Giải thích |
|---|---|
| 1-3 | Khai báo vector vars_g1_numeric chứa danh
sách các biến định lượng cần thống kê trong nhóm
1. |
| 4-10 | Tính thống kê mô tả: * select(any_of(...)): Chọn các cột định
lượng đã định nghĩa. * summarise(across(...)): Áp dụng cùng một
tập hợp các hàm thống kê (min, quantile
0.25/0.75, median, mean, max) cho
tất cả các cột đã chọn. * .names: Đặt
tên cột kết quả theo định dạng TênBiến_Hàm (ví dụ:
Real_Return_Min). |
| 11-19 | Chuyển đổi định dạng bảng: * Dòng 11-14: Chuyển bảng thống kê từ định dạng wide (biến trên cột) sang định dạng long tạm
thời (transpose và rownames_to_column). * Dòng 15-16: Tách tên cột thành hai phần: Variable (Tên Biến) và Statistic (Thống kê) và
chuẩn hóa thứ tự thống kê. * 18-19: Dùng pivot_wider() để chuyển bảng về định dạng wide
sạch (Biến trên hàng, Thống kê trên cột), làm tròn giá trị đến 3 chữ số
thập phân, và sắp xếp theo tên biến. |
| 20-23 | Dùng kable() để in ra màn
hình bảng thống kê mô tả cuối cùng với tiêu đề và tên cột tiếng
Việt rõ ràng. |
Nhận xét:
Bảng 1.2.1 tóm tắt phân bố của các biến định lượng chính liên quan đến Giao dịch, Hành vi và Hiệu suất Đầu tư:
Real_Return) có Median (0.167) cao hơn
Mean (0.087), gợi ý phân bố lệch trái. Lợi suất danh nghĩa
(Nominal_Return) và kỳ vọng
(Expected_Return_Yr) đều có Mean cao hơn
Median (0.071 vs 0.014/0.013), cho thấy phân bố lệch
phải.Median) của amount là 2,000 USD và
Horizon_Days là 90 ngày.Volatility_Buy và
Sharpe_Ratio có Median và Mean
rất gần nhau (~0.23 / ~0.25).amount,
Horizon_Days, và lợi suất danh nghĩa/kỳ vọng đều
lệch phải (Mean > Median). Lợi suất thực tế
lệch trái (Mean < Median).Real_Return, có đuôi âm dài (lệch
trái). Quy mô giao dịch và thời gian nắm giữ có nhiều giá trị cao (lệch
phải). Rủi ro (Volatility) và hiệu quả điều chỉnh rủi ro
(Sharpe_Ratio) phân bố tương đối đối xứng hơn. Cần lưu ý
đến sự tồn tại của các giá trị ngoại lai (outliers) do độ phân tán
rộng.1. sd_table_g1 <- nyse_group1_finite %>%
2. summarise(across(any_of(vars_g1_numeric), ~ sd(.x, na.rm = TRUE), .names = "SD_{.col}")) %>%
3. pivot_longer(everything(), names_to = "Variable_Prefixed", values_to = "Standard_Deviation") %>%
4. mutate(Variable = sub("^SD_", "", Variable_Prefixed)) %>%
5. select(Variable, Standard_Deviation) %>%
6. mutate(Standard_Deviation = round(Standard_Deviation, 3)) %>%
7. arrange(Variable)
8. kable(sd_table_g1,
9. caption = "Bảng 1.2.2: Độ lệch chuẩn (Nhóm biến Giao dịch, Hành vi và Hiệu suất đầu tư)",
10. col.names = c("Tên Biến", "Độ lệch chuẩn (SD)"),
11. format = "pipe", align = "l")| Tên Biến | Độ lệch chuẩn (SD) |
|---|---|
| Expected_Return_Yr | 0.143 |
| Horizon_Days | 210.703 |
| Nominal_Return | 0.299 |
| Real_Return | 0.774 |
| Sharpe_Ratio | 0.098 |
| Volatility_Buy | 0.098 |
| amount | 12774.429 |
| inflation | 1.038 |
| price_BUY | 217.178 |
| price_SELL | 250.137 |
Giải thích code:
| Dòng code | Giải thích |
|---|---|
| 1-2 | Tính độ lệch chuẩn (SD): * Sử dụng summarise() và across() để tính
sd() cho tất cả các cột trong vars_g1_numeric.
* .names = "SD_{.col}" đặt tên cột kết quả có tiền tố
SD_. |
| 3-6 | Chuyển đổi định dạng bảng: * 3: Dùng pivot_longer() để chuyển bảng từ
định dạng wide sang long (một cột cho tên
biến, một cột cho giá trị SD). * 4-5: Dùng mutate() và sub() để loại bỏ tiền tố
SD_ khỏi tên biến, sau đó chọn lại cột cần thiết. |
| 6-7 | Làm tròn giá trị Standard_Deviation đến 3
chữ số thập phân và sắp xếp theo tên biến. |
| 8-12 | Dùng kable() để in ra bảng Độ lệch chuẩn
cuối cùng với tiêu đề và tên cột tiếng Việt rõ ràng. |
Nhận xét:
Bảng 1.2.2 định lượng mức độ phân tán tuyệt đối (Độ lệch chuẩn - SD) cho các biến liên quan đến giao dịch, hành vi và hiệu suất đầu tư:
Real_Return, SD = 0.774) và Lợi suất
danh nghĩa (Nominal_Return, SD = 0.299) có SD rất lớn,
tái khẳng định mức độ rủi ro cao đã được gợi ý qua biên
độ Min-Max.price_BUY/price_SELL, SD ~
200+) cũng có sự biến động mạnh về giá trị.amount, SD = 12,774 USD) có độ phân
tán cực mạnh, với SD lớn hơn cả giá trị trung bình (Mean ~ 8,108 USD),
thể hiện sự phân hóa lớn về quy mô đầu tư.Horizon_Days, SD = 210.7 ngày) cũng
biến động đáng kể.Volatility_Buy (SD = 0.098) và
Sharpe_Ratio (SD = 0.098) có SD tương đối nhỏ, cho thấy
mức độ tập trung cao hơn của hai chỉ số này xung quanh
giá trị trung tâm.inflation (SD = 1.038) có SD lớn, cho thấy sự
biến động mạnh của tỷ lệ lạm phát được sử dụng.amount), giá cả và lợi
suất, phản ánh tính chất rủi ro và đa dạng của thị trường chứng
khoán.1. library(scales); library(dplyr); library(knitr); library(rlang)
2. create_freq_table_vi <- function(data, var_name, caption_text) {
3. freq_table <- data %>%
4. dplyr::count(!!rlang::sym(var_name), name = "SoLuong") %>%
5. dplyr::mutate(TyLe_PhanTram = scales::percent(SoLuong / sum(SoLuong), accuracy = 0.1)) %>%
6. dplyr::arrange(desc(SoLuong))
7. colnames(freq_table) <- c(var_name, "Số lượng", "Tỷ lệ (%)")
8. kable(freq_table, caption = caption_text, align = "l")}
9. if ("sector" %in% names(nyse_group1_finite))
10. create_freq_table_vi(nyse_group1_finite, "sector", "Bảng 1.2.3: Phân bổ giao dịch theo Ngành")| sector | Số lượng | Tỷ lệ (%) |
|---|---|---|
| RETAIL | 96269 | 23.8% |
| TECH | 92401 | 22.8% |
| BANK | 84757 | 20.9% |
| AUTO | 79836 | 19.7% |
| FMCG | 51995 | 12.8% |
1. if ("investment" %in% names(nyse_group1_finite))
2. create_freq_table_vi(nyse_group1_finite, "investment", "Bảng 1.2.4: Phân bổ giao dịch theo Kết quả đầu tư")| investment | Số lượng | Tỷ lệ (%) |
|---|---|---|
| BAD | 264440 | 65.3% |
| GOOD | 140818 | 34.7% |
1. if ("Holding_Period" %in% names(nyse_group1_finite))
2. create_freq_table_vi(nyse_group1_finite, "Holding_Period", "Bảng 1.2.5: Phân bổ giao dịch theo Thời gian nắm giữ")| Holding_Period | Số lượng | Tỷ lệ (%) |
|---|---|---|
| Long-term (>180d) | 147526 | 36.4% |
| Short-term (<=30d) | 135001 | 33.3% |
| Mid-term (31-180d) | 122731 | 30.3% |
1. if ("Risk_Level" %in% names(nyse_group1_finite))
2. create_freq_table_vi(nyse_group1_finite, "Risk_Level", "Bảng 1.2.6: Phân bổ giao dịch theo Mức độ rủi ro")| Risk_Level | Số lượng | Tỷ lệ (%) |
|---|---|---|
| High | 137771 | 34.0% |
| Medium | 133747 | 33.0% |
| Low | 133740 | 33.0% |
1. if ("Amount_Level" %in% names(nyse_group1_finite))
2. create_freq_table_vi(nyse_group1_finite, "Amount_Level", "Bảng 1.2.7: Phân bổ giao dịch theo Quy mô đầu tư")| Amount_Level | Số lượng | Tỷ lệ (%) |
|---|---|---|
| Medium | 143246 | 35.3% |
| Small | 142692 | 35.2% |
| Large | 119320 | 29.4% |
Giải thích code:
| Dòng code | Giải thích |
|---|---|
| 1 | Tải các gói thư viện cần thiết: scales
(định dạng phần trăm), dplyr (thao tác dữ liệu),
knitr (tạo bảng), và rlang (lập trình không
tiêu chuẩn). |
| 2-9 | Định nghĩa hàm
create_freq_table_vi: * Mục đích: Tạo bảng tần suất (Số lượng và Tỷ lệ %) cho biến phân loại bất kỳ. * 4: Dùng dplyr::count()
để đếm số lượng quan sát cho mỗi giá trị của biến
(var_name). !!rlang::sym(var_name) cho phép sử
dụng tên biến truyền vào dưới dạng chuỗi. * 5: Tính Tỷ lệ phần trăm ( TyLe_PhanTram) bằng
cách chia số lượng cho tổng và dùng scales::percent() để
định dạng (làm tròn 0.1%). * 6: Sắp xếp bảng theo thứ tự giảm dần của Số lượng. * 7-8: Đặt lại tên cột và dùng kable() để in bảng với tiêu đề. |
| 10-20 | Áp dụng hàm tạo bảng: Sử dụng câu lệnh
if để kiểm tra sự tồn tại của các biến phân loại
(sector, investment,
Holding_Period, Risk_Level,
Amount_Level) trong nyse_group1_finite và gọi
hàm create_freq_table_vi để tạo và in bảng
tần suất cho từng biến. |
Nhận xét:
Các bảng tần suất (Bảng 1.2.3 - 1.2.7) cho thấy sự phân bổ của các giao dịch đầu tư theo các tiêu chí phân loại:
sector - Bảng
1.2.3): Giao dịch tập trung cao độ vào ba ngành:
RETAIL (23.8%), TECH (22.8%), và
BANK (20.9%), chiếm khoảng 67.5% tổng giao dịch.
Ngành FMCG có tần suất thấp nhất (~12.8%).investment - Bảng
1.2.4): Tồn tại sự mất cân bằng lớp rõ rệt,
với nhóm BAD (65.2%) chiếm tỷ trọng lớn hơn đáng kể so với
nhóm GOOD (34.8%).Holding_Period
- Bảng 1.2.5): Ba chiến lược nắm giữ (Long-term,
Short-term, Mid-term) có sự phân bổ
tương đối đồng đều (~30.3% đến ~36.4%).Risk_Level - Bảng
1.2.6) và quy mô đầu tư (Amount_Level - Bảng
1.2.7): Cả hai biến đều thể hiện sự phân bổ rất đồng
đều giữa các nhóm (chia theo phân vị), với mức độ khác biệt chỉ
vài phần trăm.1. vars_g1_quantile <- c("Real_Return", "Nominal_Return", "amount", "Horizon_Days",
2. "Volatility_Buy", "Sharpe_Ratio", "Expected_Return_Yr", "inflation")
3. quantile_results_g1 <- sapply(nyse_group1_finite[vars_g1_quantile],
4. quantile,
5. probs = c(0, 0.25, 0.5, 0.75, 1),
6. na.rm = TRUE)
7. rownames(quantile_results_g1) <- c("Min (0%)","Q1 (25%)","Median (50%)","Q3 (75%)","Max (100%)")
8. quantile_df_g1 <- as.data.frame(round(quantile_results_g1, 3)) %>%
9. rownames_to_column("Percentile")
10. kable(quantile_df_g1,
11. caption = "Bảng 1.2.8: Các điểm phân vị của các biến định lượng", format = "pipe", align = "l")| Percentile | Real_Return | Nominal_Return | amount | Horizon_Days | Volatility_Buy | Sharpe_Ratio | Expected_Return_Yr | inflation |
|---|---|---|---|---|---|---|---|---|
| Min (0%) | -0.952 | -0.872 | 50 | 1 | 0.090 | 0.090 | -0.278 | -0.50 |
| Q1 (25%) | -0.621 | -0.028 | 400 | 15 | 0.185 | 0.185 | 0.001 | -0.20 |
| Median (50%) | 0.167 | 0.014 | 2000 | 90 | 0.232 | 0.232 | 0.013 | -0.15 |
| Q3 (75%) | 0.486 | 0.111 | 10000 | 300 | 0.307 | 0.307 | 0.083 | 1.68 |
| Max (100%) | 16.353 | 8.847 | 50000 | 720 | 0.698 | 0.698 | 1.017 | 1.96 |
Giải thích code:
| Dòng code | Giải thích |
|---|---|
| 1-2 | Khai báo vector vars_g1_quantile chứa danh
sách các biến định lượng sẽ được tính toán phân
vị. |
| 3-6 | Tính các điểm phân vị: * Sử dụng hàm sapply() để áp dụng hàm quantile() cho mỗi
cột được chỉ định trong vars_g1_quantile. * probs xác định các điểm phân vị cần tính: 0
(Min), 0.25 (Q1), 0.5 (Median),
0.75 (Q3), 1 (Max). |
| 7 | Đặt lại tên hàng (row names) của ma trận kết quả thành các tên gọi rõ ràng hơn (Min, Q1, Median, Q3, Max). |
| 8-9 | Chuyển đổi định dạng bảng: * Chuyển ma trận kết quả thành data.frame, làm tròn giá trị
đến 3 chữ số thập phân. * Dùng rownames_to_column() để
biến tên hàng (Percentile) thành một cột dữ liệu. |
| 10-12 | Dùng kable() để in ra bảng phân vị cuối
cùng với tiêu đề rõ ràng. |
Nhận xét:
Bảng 1.2.8 trình bày các điểm phân vị chính (Min, Q1, Median, Q3, Max) cho các biến thuộc nhóm Giao dịch, Hành vi và Hiệu suất, cung cấp thông tin về phạm vi và sự tập trung của dữ liệu:
Real_Return,
Nominal_Return):
Median của lợi suất thực tế (16.7%) và lợi suất danh
nghĩa (1.4%) cho thấy đa số giao dịch đạt được lợi suất thực tế
dương.amount,
Horizon_Days):
Median) là 2,000 USD
và 90 ngày.amount \(\leq\)
10,000 USD và Horizon_Days \(\leq\) 300 ngày, nhưng giá trị Max vượt
trội rất xa.Volatility_Buy,
Sharpe_Ratio): Cả hai biến đều có phân bố
tập trung (Median ~0.23, IQR hẹp ~0.185 đến ~0.307), xác nhận
tính đối xứng đã nhận xét.Median thấp (1.3%) nhưng Q3 (8.3%) cao hơn đáng kể, xác
nhận sự lệch phải. Lạm phát có Median âm
(-0.15%), cho thấy sự phân bố bị kéo lên bởi các giá trị lạm phát dương
cao ở phần trên.Tần suất lợi suất thực tế
1. return_sign_freq_g1 <- nyse_group1_finite %>%
2. mutate(Return_Sign = case_when(Real_Return > 0 ~ "Lợi suất Dương (>0)",
3. Real_Return <= 0 ~ "Lợi suất Âm (<=0)",
4. TRUE ~ "NA")) %>%
5. count(Return_Sign, name = "SoLuong") %>%
6. mutate(TyLe_PhanTram = round((SoLuong / sum(SoLuong)) * 100, 2))
7. kable(return_sign_freq_g1,
8. caption = "Bảng 1.2.9: Tần suất giao dịch theo dấu của lợi suất thực tế",
9. col.names = c("Dấu Lợi suất thực tế", "Số lượng", "Tỷ lệ (%)"), format = "pipe", align = "l")| Dấu Lợi suất thực tế | Số lượng | Tỷ lệ (%) |
|---|---|---|
| Lợi suất Dương (>0) | 229767 | 56.7 |
| Lợi suất Âm (<=0) | 175491 | 43.3 |
Giải thích code:
| Dòng code | Giải thích |
|---|---|
| 1-5 | Phân loại dấu Lợi suất: * Sử dụng mutate() và case_when() để tạo biến phân loại
Return_Sign dựa trên Real_Return: * > 0: “Lợi suất Dương” * <= 0: “Lợi
suất Âm” * TRUE ~ "NA": Gán NA cho các
trường hợp còn lại (nếu có, ở đây là giá trị NA của
Real_Return). |
| 6 | Đếm tần suất: Dùng
count() để đếm số lượng quan sát cho mỗi loại
Return_Sign và lưu vào cột SoLuong. |
| 7 | Tính tỷ lệ phần trăm: Tính tỷ lệ phần
trăm (TyLe_PhanTram) của mỗi nhóm, làm tròn đến 2 chữ số
thập phân. |
| 8-11 | Dùng kable() để in ra bảng tần suất cuối
cùng với tiêu đề và tên cột tiếng Việt rõ ràng. |
Nhận xét:
Bảng 1.2.9 phân loại các giao dịch dựa trên dấu của Lợi suất thực tế
(Real_Return):
> 0).<= 0).investment:
GOOD (34.8% - Bảng 1.2.4).investment là GOOD trong bộ dữ liệu này
khắt khe hơn tiêu chí đơn giản là lãi thực tế dương
(Real_Return > 0). Việc đa số giao dịch có lãi thực tế
(56.7%) là một tín hiệu khả quan về khả năng sinh lời trên thị
trường.(1) Đo lường độ xiên (Skewness Measurement)
Độ xiên đo lường mức độ bất đối xứng của phân phối dữ liệu quanh giá trị trung bình. Giá trị dương thể hiện lệch phải, giá trị âm thể hiện lệch trái.
1. skewness_table_g1 <- sapply(nyse_group1_finite[vars_g1_shape],
2. skewness, na.rm = TRUE) %>%
3. round(3) %>% as.data.frame() %>%
4. rownames_to_column("Variable") %>%
5. rename(Skewness = ".") %>%
6. arrange(Variable)
7. kable(skewness_table_g1,
8. caption = "Bảng 1.2.10: Độ xiên (Skewness) của các biến định lượng",
9. col.names = c("Tên Biến", "Độ xiên (Skewness)"), format = "pipe", align = "l")| Tên Biến | Độ xiên (Skewness) |
|---|---|
| Expected_Return_Yr | 2.853 |
| Horizon_Days | 1.108 |
| Nominal_Return | 7.522 |
| Real_Return | 2.752 |
| Sharpe_Ratio | 1.104 |
| Volatility_Buy | 1.104 |
| amount | 2.140 |
| inflation | 0.341 |
| price_BUY | 4.553 |
| price_SELL | 4.547 |
Giải thích code:
| Dòng code | Giải thích |
|---|---|
| 1 | Tính độ xiên (Skewness): * Dùng hàm sapply() để áp dụng hàm skewness() (giả
định hàm này được tải từ gói e1071 hoặc tương tự) cho tất
cả các cột trong vector vars_g1_shape (giả định được định
nghĩa trước). |
| 2-5 | Chuyển đổi định dạng bảng: * Làm tròn kết quả đến 3 chữ số thập phân, chuyển thành data.frame. * Dùng rownames_to_column()
và rename() để đặt tên cột là Variable và
Skewness. * Sắp xếp bảng theo tên biến. |
| 6-9 | Dùng kable() để in ra bảng độ xiên cuối
cùng với tiêu đề và tên cột tiếng Việt rõ ràng. |
Nhận xét:
Bảng 1.2.10 cho thấy độ xiên của các biến định lượng, thể hiện mức độ đối xứng của phân phối:
Nominal_Return (7.52),
price_BUY/price_SELL (4.55),
Expected_Return_Yr (2.85), Real_Return (2.75),
và amount (2.14) đều thể hiện độ lệch phải rất cao.
Mean cao hơn Median cho hầu hết
các biến này.Horizon_Days (1.11), Sharpe_Ratio (1.10), và
Volatility_Buy (1.10) cũng lệch phải rõ rệt.inflation (0.34) có độ
xiên thấp, gần với phân phối đối xứng.(2) Đo lường độ nhọn (Kurtosis Measurement)
Độ nhọn đo lường mức độ tập trung của dữ liệu quanh giá trị trung tâm
và độ “dày” của phần đuôi phân phối so với phân phối chuẩn. Hàm
kurtosis() trong R thường tính Excess Kurtosis
(Kurtosis - 3).
1. kurtosis_table_g1 <- sapply(nyse_group1_finite[vars_g1_shape],
2. kurtosis, na.rm = TRUE) %>%
3. round(3) %>% as.data.frame() %>%
4. rownames_to_column("Variable") %>%
5. rename(Excess_Kurtosis = ".") %>%
6. arrange(Variable)
7. kable(kurtosis_table_g1,
8. caption = "Bảng 1.2.11: Độ nhọn (Excess Kurtosis = Kurtosis − 3)",
9. col.names = c("Tên Biến", "Độ nhọn (Kurtosis−3)"), format = "pipe", align = "l")| Tên Biến | Độ nhọn (Kurtosis−3) |
|---|---|
| Expected_Return_Yr | 13.89 |
| Horizon_Days | 3.04 |
| Nominal_Return | 117.30 |
| Real_Return | 31.87 |
| Sharpe_Ratio | 4.39 |
| Volatility_Buy | 4.39 |
| amount | 7.05 |
| inflation | 1.22 |
| price_BUY | 26.66 |
| price_SELL | 26.58 |
Giải thích code:
| Dòng code | Giải thích |
|---|---|
| 1-2 | Tính độ nhọn (Kurtosis): Sử dụng hàm
sapply() để áp dụng hàm kurtosis() cho mỗi cột
trong vars_g1_shape. Đối số na.rm = TRUE đảm
bảo rằng các giá trị thiếu (NA) sẽ được loại bỏ trước
khi tính toán độ nhọn. |
| 2-5 | Chuyển đổi định dạng bảng: * round(3): Làm tròn kết quả đến 3 chữ số
thập phân. * as.data.frame(): Chuyển
kết quả thành data.frame. * rownames_to_column("Variable"): Biến tên
biến thành cột Variable. * rename(Excess_Kurtosis = "."): Đổi tên cột
giá trị độ nhọn. * arrange(Variable):
Sắp xếp bảng theo tên biến. |
| 6-9 | Dùng kable() để in ra bảng độ nhọn cuối
cùng với tiêu đề và tên cột tiếng Việt rõ ràng. |
Nhận xét:
Kết quả độ nhọn (Excess Kurtosis) củng cố thêm đặc điểm phân phối không chuẩn:
Nominal_Return (117.3) và Real_Return
(31.9) có độ nhọn cực kỳ cao.price_BUY, price_SELL) cũng
rất cao (~26.6).Expected_Return_Yr (13.9),
amount (7.1),
Volatility_Buy/Sharpe_Ratio (4.4)).
(3) Kiểm định tính phân phối chuẩn
Kiểm định Shapiro-Wilk là một phương pháp thống kê để kiểm tra giả thuyết H0 rằng dữ liệu tuân theo phân phối chuẩn. Tuy nhiên, kiểm định này rất nhạy với cỡ mẫu lớn và thường không phù hợp cho bộ dữ liệu > 5000 quan sát. Ta sẽ thực hiện trên một mẫu con nhỏ để có đánh giá sơ bộ.
1. library(purrr); library(dplyr); library(knitr); library(tibble); library(stats)
2. shapiro_on_sample <- function(x, max_n = 5000) {x <- na.omit(x)
3. n_use <- min(length(x), max_n)
4. if (n_use < 3) return(tibble(N = length(x), W = NA_real_, p_value = NA_real_))
5. x_sample <- sample(x, n_use)
6. res <- shapiro.test(x_sample)
7. tibble(N = n_use, W = unname(res$statistic), p_value = unname(res$p.value))}
8. vars_shapiro <- c("Real_Return","amount","Volatility_Buy")
9. shapiro_df <- map_dfr(vars_shapiro, ~ {
10. tmp <- nyse_group1_finite[[.x]]
11. out <- shapiro_on_sample(tmp)
12. out$Variable <- .x
13. out}) %>% relocate(Variable)
14. shapiro_df <- shapiro_df %>%
15. mutate(p_value_fmt = format.pval(p_value, digits = 3, eps = 0.001),
16. Kết_luận = ifelse(!is.na(p_value) & p_value < 0.05,
17. "Bác bỏ phân phối chuẩn (p<0.05)", "Không bác bỏ (p≥0.05)"))
18. kable(shapiro_df %>% select(Variable, N, W, p_value_fmt, Kết_luận),
19. caption = "Bảng 1.2.12: Kiểm định Shapiro–Wilk trên mẫu con (N≤5000)",
20. col.names = c("Biến","Cỡ mẫu","W","P-value","Kết luận"), format = "pipe", align = "l")| Biến | Cỡ mẫu | W | P-value | Kết luận |
|---|---|---|---|---|
| Real_Return | 5000 | 0.721 | <0.001 | Bác bỏ phân phối chuẩn (p<0.05) |
| amount | 5000 | 0.647 | <0.001 | Bác bỏ phân phối chuẩn (p<0.05) |
| Volatility_Buy | 5000 | 0.923 | <0.001 | Bác bỏ phân phối chuẩn (p<0.05) |
Giải thích code:
| Dòng code | Giải thích |
|---|---|
| 1 | Tải các gói thư viện cần thiết: purrr (hàm
chức năng), dplyr, knitr, tibble
(tạo/thao tác khung dữ liệu), và stats (kiểm định thống kê,
bao gồm shapiro.test). |
| 2-7 | Định nghĩa hàm
shapiro_on_sample: * Mục đích: Thực hiện kiểm định Shapiro-Wilk trên một mẫu con tối đa là max_n (5000) để xử lý giới hạn cỡ
mẫu của kiểm định. * 2: Loại bỏ giá trị NA và xác định cỡ mẫu sử dụng (n_use). * Dòng 3: Trả về NA nếu cỡ mẫu không đủ
(dưới 3). * 4: Lấy mẫu ngẫu nhiên ( sample) cỡ n_use. * 5: Thực hiện kiểm định Shapiro-Wilk ( shapiro.test). * 6: Trả về kết quả dưới dạng tibble chứa cỡ
mẫu (N), giá trị kiểm định (W), và giá
trị p-value. |
| 8 | Khai báo vector vars_shapiro chứa các biến
cần kiểm tra. |
| 9-13 | Thực hiện kiểm định hàng loạt: * Sử dụng map_dfr() (từ purrr) để lặp qua từng
biến trong vars_shapiro. * Đối với mỗi biến, gọi hàm shapiro_on_sample() để chạy kiểm định. * Kết hợp kết quả ( out) với tên biến (Variable) và gom lại
thành data.frame (shapiro_df). |
| 14-17 | Định dạng kết quả: * Dòng 15: Định dạng p-value bằng format.pval() để hiển
thị các giá trị nhỏ dưới ngưỡng 0.001 dưới dạng < 0.001.
* Dòng 16-17: Tạo cột Kết_luận dựa
trên p-value: Bác bỏ H0 nếu p-value \(<
0.05\). |
| 18-20 | Dùng kable() để in ra bảng kết quả kiểm
định cuối cùng với tiêu đề và tên cột. |
Nhận xét:
Real_Return, amount,
Volatility_Buy) đều cho p-value cực kỳ nhỏ
(< 0.001). Với p-value \(<
0.05\), có bằng chứng thống kê mạnh mẽ để bác bỏ giả
thuyết H0 rằng dữ liệu của các biến này tuân theo phân phối
chuẩn.(1) Xác định ngưỡng ngoại lai bằng phương pháp IQR
Phương pháp IQR xác định outlier là những giá trị nằm ngoài khoảng [Q1 - 1.5*IQR, Q3 + 1.5*IQR].
1. vars_g1_outlier <- c("Real_Return", "Nominal_Return", "amount", "Horizon_Days")
2. outlier_results_list_g1 <- lapply(vars_g1_outlier, function(var) {
3. identify_outliers_iqr_vi(nyse_group1_finite, var)})
4. outlier_threshold_df_g1 <- do.call(rbind, lapply(outlier_results_list_g1, function(res) {
5. data.frame(
6. Variable = res$var_name,
7. Q1 = round(res$Q1, 3), Q3 = round(res$Q3, 3), IQR = round(res$IQR, 3),
8. Lower_Bound = round(res$Lower_Bound, 3), Upper_Bound = round(res$Upper_Bound, 3))}))
9. kable(outlier_threshold_df_g1,
10. caption = "Bảng 1.2.13: Ngưỡng xác định giá trị ngoại lai bằng phương pháp IQR",
11. col.names = c("Tên Biến", "Q1", "Q3", "IQR", "Ngưỡng Dưới", "Ngưỡng Trên"),
12. format = "pipe", align = "l")| Tên Biến | Q1 | Q3 | IQR | Ngưỡng Dưới | Ngưỡng Trên | |
|---|---|---|---|---|---|---|
| 25% | Real_Return | -0.621 | 0.486 | 1.11 | -2.281 | 2.146 |
| 25%1 | Nominal_Return | -0.028 | 0.111 | 0.14 | -0.238 | 0.321 |
| 25%2 | amount | 400.000 | 10000.000 | 9600.00 | -14000.000 | 24400.000 |
| 25%3 | Horizon_Days | 15.000 | 300.000 | 285.00 | -412.500 | 727.500 |
Giải thích code
| Dòng code | Giải thích |
|---|---|
| 1 | Khai báo vector vars_g1_outlier chứa danh
sách các biến định lượng cần xác định ngưỡng ngoại
lai. |
| 2-3 | Tính toán ngưỡng ngoại lai: Sử dụng
hàm lapply() để lặp qua từng biến. Gọi hàm
identify_outliers_iqr_vi() (giả định đã được định nghĩa) để
tính Tứ phân vị (Q1, Q3), Khoảng Tứ phân vị (IQR), và hai ngưỡng
ngưỡng Dưới (\(Q1 - 1.5 \times
IQR\)) và ngưỡng Trên (\(Q3 + 1.5 \times IQR\)). Kết quả
được lưu dưới dạng danh sách. |
| 4-7 | Tạo bảng kết quả: * do.call(rbind, lapply(...)): Chuyển đổi
danh sách kết quả thành một khung dữ liệu (data.frame) duy
nhất. * Chọn các cột cần thiết và làm tròn giá trị đến 3 chữ số thập phân. |
| 8-11 | Dùng kable() để in ra bảng ngưỡng ngoại
lai cuối cùng với tiêu đề và tên cột. |
Nhận xét:
Real_Return: Ngoại lai nếu \(< -2.281\) (-228.1%) hoặc \(> 2.146\) (214.6%).Nominal_Return: Ngoại lai nếu nằm
ngoài khoảng \([-0.238, 0.321]\).amount: Ngoại lai nếu lớn hơn
24,400 USD (không có outlier dưới vì Q1/Q3 dương và IQR
không đủ lớn để kéo ngưỡng dưới về âm).Horizon_Days: Đáng chú ý, ngưỡng trên
là 727.5 ngày, cao hơn giá trị tối đa thực tế (Max =
720). Điều này có nghĩa là, theo phương pháp IQR, không có
outlier ở phía trên cho biến Horizon_Days.(2) Định lượng tỷ lệ phần trăm ngoại lai
Đếm số lượng và tính tỷ lệ phần trăm các giá trị nằm ngoài ngưỡng IQR đã xác định.
1. outlier_count_df_g1 <- do.call(rbind, lapply(outlier_results_list_g1, function(res) {
2. data.frame(Variable = res$var_name,
3. Num_Outliers_Low = res$Num_Outliers_Low,
4. Num_Outliers_High = res$Num_Outliers_High,
5. Total_Outliers = res$Total_Outliers,
6. Total_NonNA = res$Total_NonNA)}))
7. outlier_percentage_df_g1 <- outlier_count_df_g1 %>%
8. mutate(Percentage_Outliers = round((Total_Outliers / Total_NonNA) * 100, 2) ) %>%
9. arrange(desc(Percentage_Outliers))
10. kable(outlier_percentage_df_g1 %>% select(Variable, Total_Outliers, Total_NonNA, Percentage_Outliers),
11. caption = "Bảng 1.2.14: Số lượng và tỷ lệ phần trăm giá trị ngoại lai",
12. col.names = c("Tên Biến", "Số lượng Outlier", "Tổng QS (Không NA)", "Tỷ lệ Outlier (%)"),
13. format = "pipe", align = "l")| Tên Biến | Số lượng Outlier | Tổng QS (Không NA) | Tỷ lệ Outlier (%) |
|---|---|---|---|
| Nominal_Return | 54140 | 405258 | 13.36 |
| amount | 47556 | 405258 | 11.73 |
| Real_Return | 3140 | 405258 | 0.77 |
| Horizon_Days | 0 | 405258 | 0.00 |
Giải thích code
| Dòng code | Giải thích |
|---|---|
| 1-5 | Tổng hợp số lượng Outlier: * Sử dụng do.call(rbind, lapply(...)) để tổng hợp kết quả từ
danh sách outlier_results_list_g1 (đã tính ở bước trước).
* Tạo một data.frame mới
(outlier_count_df_g1) chứa tên biến, số lượng Outlier
Dưới/Trên/Tổng, và tổng số quan sát không NA
(Total_NonNA). |
| 6-8 | Tính tỷ lệ Outlier: * Dùng mutate() để tính Tỷ lệ Outlier (%) bằng
cách chia Total_Outliers cho Total_NonNA và
làm tròn đến 2 chữ số thập phân. * Sắp xếp bảng theo tỷ lệ Outlier giảm dần. |
| 9-12 | Dùng kable() để in ra màn hình bảng thống
kê Outlier cuối cùng. |
Nhận xét:
Nominal_Return có tỷ lệ
outlier cao nhất (13.36%), tiếp theo là
amount (11.73%). Điều này phù hợp với độ
lệch và độ nhọn rất cao của chúng, cho thấy các giá trị cực đoan (lãi/lỗ
danh nghĩa lớn, số tiền đầu tư rất lớn) là khá phổ biến.Real_Return có tỷ lệ
outlier thấp nhất trong nhóm này (0.77%). Sự khác biệt
lớn này so với Nominal_Return có thể là do Khoảng Tứ phân
vị (IQR) của Real_Return rộng hơn (do ảnh hưởng của biến
inflation), làm cho ngưỡng outlier của nó xa hơn, khiến ít
giá trị bị coi là cực đoan hơn theo định nghĩa IQR.Horizon_Days xác
nhận không có outlier (0.00%), củng cố nhận xét từ bước
ngưỡng.(3) Phân tích đặc điểm ngoại lai của lợi suất thực tế
(Real_Return)
Xem xét các outlier của biến lợi suất chính
(Real_Return) phân bổ như thế nào theo Ngành và Năm
mua.
1. rr_outlier_info_g1 <- outlier_results_list_g1[[1]]
2. rr_lower_bound_g1 <- rr_outlier_info_g1$Lower_Bound
3. rr_upper_bound_g1 <- rr_outlier_info_g1$Upper_Bound
4. cat(paste("Ngưỡng Outlier cho Real_Return: Dưới", round(rr_lower_bound_g1, 3),
5. "và Trên", round(rr_upper_bound_g1, 3), "\n"))Ngưỡng Outlier cho Real_Return: Dưới -2.281 và Trên 2.146
1. real_return_outliers_g1 <- nyse_group1_finite %>%
2. dplyr::filter(Real_Return < rr_lower_bound_g1 | Real_Return > rr_upper_bound_g1)
3. outlier_by_sector_g1 <- real_return_outliers_g1 %>%
4. dplyr::count(sector, name = "SoLuongOutlier") %>%
5. dplyr::mutate(TyLeTrongOutlier = round((SoLuongOutlier / sum(SoLuongOutlier)) * 100, 2)) %>%
6. dplyr::arrange(desc(SoLuongOutlier))
7. knitr::kable(outlier_by_sector_g1,
8. caption = "Bảng 1.2.15: Phân bổ Outlier lợi suất thực tế theo ngành",
9. col.names = c("Ngành", "Số lượng Outlier", "Tỷ lệ trong tổng Outlier (%)"), align = "l")| Ngành | Số lượng Outlier | Tỷ lệ trong tổng Outlier (%) |
|---|---|---|
| TECH | 2785 | 88.69 |
| RETAIL | 241 | 7.68 |
| BANK | 80 | 2.55 |
| AUTO | 34 | 1.08 |
1. outlier_by_year_g1 <- real_return_outliers_g1 %>%
2. dplyr::count(Buy_Year, name = "SoLuongOutlier") %>%
3. dplyr::mutate(TyLeTrongOutlier = round((SoLuongOutlier / sum(SoLuongOutlier)) * 100, 2)) %>%
4. dplyr::arrange(Buy_Year)
5. knitr::kable(outlier_by_year_g1,
6. caption = "Bảng 1.2.16: Phân bổ Outlier lợi suất thực tế theo năm mua",
7. col.names = c("Năm Mua", "Số lượng Outlier", "Tỷ lệ trong tổng Outlier (%)"), align = "l")| Năm Mua | Số lượng Outlier | Tỷ lệ trong tổng Outlier (%) |
|---|---|---|
| 2014 | 82 | 2.61 |
| 2015 | 2176 | 69.30 |
| 2016 | 882 | 28.09 |
Giải thích code
real_return| Dòng code | Giải thích |
|---|---|
| 1-3 | Truy xuất ngưỡng Outlier: Lấy thông
tin kết quả outlier của biến Real_Return từ danh sách
outlier_results_list_g1 và lưu các ngưỡng dưới/trên vào các
biến riêng. |
| 4-5 | In ra ngưỡng Outlier cho
Real_Return đã được xác định. |
| 6-7 | Lọc Outlier: Lọc từ bộ dữ liệu
nyse_group1_finite tất cả các quan sát mà
Real_Return nhỏ hơn ngưỡng dưới hoặc lớn hơn ngưỡng
trên. Kết quả lưu vào
real_return_outliers_g1. |
| 8-12 | Phân bổ Outlier theo ngành: * Đếm số lượng Outlier theo sector. * Tính Tỷ lệ phần trăm của mỗi ngành trong tổng số Outlier. * Sắp xếp giảm dần theo số lượng. |
| 13-16 | Dùng knitr::kable() để in ra bảng phân bổ
Outlier theo ngành. |
| 17-21 | Phân bổ Outlier theo năm mua: * Đếm số lượng Outlier theo Buy_Year. * Tính Tỷ lệ phần trăm của mỗi năm trong tổng số Outlier. * Sắp xếp tăng dần theo năm. |
| 22-25 | Dùng knitr::kable() để in ra bảng phân bổ
Outlier theo năm mua. |
Nhận xét:
Real_Return (Bảng 1.2.15
& 1.2.16): Phân tích các outlier của
Real_Return cho thấy sự tập trung rõ rệt:
TECH chiếm tỷ trọng áp đảo
(88.69%) số lượng outlier, cho thấy đây là ngành có
biến động lợi suất cực đoan nhất (lãi hoặc lỗ rất lớn). Ngành
RETAIL đứng thứ hai với 7.68%, các ngành còn lại rất
thấp.Real_Return chủ yếu là do các giao dịch
TECH được thực hiện trong giai đoạn
2015-2016. Việc này gợi ý rằng bất kỳ mô hình nào cũng
cần xem xét xử lý hoặc kiểm soát các biến cố thời gian/ngành cụ thể
này.(4) Đánh giá ảnh hưởng của ngoại lai lên thống kê lợi suất thực tế
So sánh giá trị Trung bình (Mean) và Độ lệch chuẩn (SD) của
Real_Return trước và sau khi loại bỏ các outlier đã xác
định.
1. mean_rr_orig_g1 <- mean(nyse_group1_finite$Real_Return, na.rm = TRUE)
2. sd_rr_orig_g1 <- sd(nyse_group1_finite$Real_Return, na.rm = TRUE)
3. nyse_no_rr_outliers_g1 <- nyse_group1_finite %>%
4. filter(!is.na(Real_Return) &
5. Real_Return >= rr_lower_bound_g1 &
6. Real_Return <= rr_upper_bound_g1)
7. mean_rr_no_out_g1 <- mean(nyse_no_rr_outliers_g1$Real_Return, na.rm = TRUE)
8. sd_rr_no_out_g1 <- sd(nyse_no_rr_outliers_g1$Real_Return, na.rm = TRUE)
9. impact_df_g1 <- data.frame(
10. Metric = c("Mean (Trung bình)", "SD (Độ lệch chuẩn)"),
11. Original = round(c(mean_rr_orig_g1, sd_rr_orig_g1), 3),
12. Without_Outliers_IQR = round(c(mean_rr_no_out_g1, sd_rr_no_out_g1), 3))
13. kable(impact_df_g1,
14. caption = "Bảng 1.2.17: Ảnh hưởng Outlier lên Mean/SD lợi suất thực tế",
15. col.names = c("Chỉ số thống kê", "Giá trị gốc", "Giá trị sau khi loại bỏ Outlier (IQR)"),
16. format = "pipe", align = "l")| Chỉ số thống kê | Giá trị gốc | Giá trị sau khi loại bỏ Outlier (IQR) |
|---|---|---|
| Mean (Trung bình) | 0.087 | 0.057 |
| SD (Độ lệch chuẩn) | 0.774 | 0.666 |
Giải thích code
| Dòng code | Giải thích |
|---|---|
| 1-2 | Tính Mean/SD gốc: * Tính giá trị trung bình ( mean) và độ lệch chuẩn (sd) gốc
của Real_Return. * Đối số na.rm = TRUE
đảm bảo các giá trị thiếu (NA) sẽ được loại bỏ trước
khi tính toán. |
| 3-6 | Loại bỏ Outlier và tạo bộ dữ liệu mới:
* Lọc ( filter) từ bộ dữ liệu gốc: giữ lại các quan sát
không phải là NA và có Real_Return nằm
trong khoảng ngưỡng Dưới và ngưỡng Trên
(rr_lower_bound_g1, rr_upper_bound_g1) đã xác
định bằng phương pháp IQR. Kết quả lưu vào
nyse_no_rr_outliers_g1. |
| 7-8 | Tính Mean/SD sau khi loại bỏ Outlier:
Tính lại Mean và SD trên bộ dữ liệu đã được lọc, vẫn sử dụng
na.rm = TRUE. |
| 9-12 | Tạo bảng so sánh: Tạo một
data.frame (impact_df_g1) so sánh Mean và SD
trước (Original) và sau
(Without_Outliers_IQR) khi loại bỏ outlier, làm tròn đến 3
chữ số thập phân. |
| 13-16 | Dùng kable() để in ra bảng so sánh tác
động: * caption: Đặt tiêu đề cho bảng
(Bảng 1.2.17). * col.names: Đặt lại
tên cột hiển thị bằng tiếng Việt. * format = "pipe": Định dạng bảng theo chuẩn
Markdown. * align = "l": Căn lề trái cho các cột. |
Nhận xét:
Real_Return (Bảng 1.2.17): Việc loại bỏ
0.77% outlier của Real_Return (theo phương
pháp IQR) có tác động đáng kể lên các chỉ số thống kê trung tâm và phân
tán:
Mean giảm từ
0.087 (8.7%) xuống 0.057 (5.7%) (\(\approx\) giảm 35%). Sự giảm này cho thấy
các outlier bị loại bỏ có xu hướng nghiêng về phía lãi rất
cao, kéo giá trị trung bình gốc lên.SD giảm từ
0.774 xuống 0.666 (\(\approx\) giảm 14%). Mức giảm này khẳng
định outliers góp phần đáng kể vào độ biến động tổng thể.Real_Return vẫn là một biến có tính biến động cao, ngay cả
sau khi đã loại bỏ các giá trị cực đoan nhất theo IQR.Tổng kết mục 1.2.1: Phân tích thống kê mô tả đã phác
thảo những đặc điểm cốt lõi của dữ liệu: thị trường đa dạng nhưng lợi
nhuận thường nghiêng về nhóm “lỗ” (BAD)
theo tiêu chí của bộ dữ liệu. Các biến quan trọng như Lợi suất
(Real_Return, Nominal_Return) và Quy mô Giao
dịch (amount) có sự biến động cực kỳ lớn,
với phân bố lệch phải và đuôi dày,
chứa nhiều outliers. Những outliers này tập trung chủ yếu ở ngành
TECH và giai đoạn 2015-2016. Sự phân
phối không chuẩn này là yếu tố then chốt, cảnh báo rằng các mô hình giả
định phân phối chuẩn cần được áp dụng thận trọng.
(1) Tính toán ma trận tương quan
1. cor_data_input_g1 <- nyse_group1_finite %>%
2. dplyr::select(dplyr::all_of(vars_g1_cor)) %>% na.omit()
3. n_obs_cor_g1 <- nrow(cor_data_input_g1)
4. cat("Số lượng quan sát hợp lệ (không NA/Inf) để tính tương quan:", format(n_obs_cor_g1, big.mark = ","), "\n")Số lượng quan sát hợp lệ (không NA/Inf) để tính tương quan: 405,258
1. cor_matrix_g1 <- cor(cor_data_input_g1, method = "pearson", use = "complete.obs")
2. knitr::kable(round(cor_matrix_g1, 2),
3. caption = "Bảng 1.2.18: Ma trận tương quan Pearson",align = "l")| Real_Return | Nominal_Return | amount | Horizon_Days | Volatility_Buy | Sharpe_Ratio | Expected_Return_Yr | inflation | price_BUY | price_SELL | |
|---|---|---|---|---|---|---|---|---|---|---|
| Real_Return | 1.00 | 0.48 | 0 | 0.13 | 0.04 | 0.04 | 0.27 | -0.80 | -0.06 | -0.02 |
| Nominal_Return | 0.48 | 1.00 | 0 | 0.29 | 0.10 | 0.10 | 0.54 | -0.02 | 0.06 | 0.16 |
| amount | 0.00 | 0.00 | 1 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 |
| Horizon_Days | 0.13 | 0.29 | 0 | 1.00 | 0.00 | 0.00 | 0.56 | 0.00 | 0.00 | 0.06 |
| Volatility_Buy | 0.04 | 0.10 | 0 | 0.00 | 1.00 | 1.00 | 0.15 | -0.03 | -0.06 | -0.05 |
| Sharpe_Ratio | 0.04 | 0.10 | 0 | 0.00 | 1.00 | 1.00 | 0.15 | -0.03 | -0.06 | -0.05 |
| Expected_Return_Yr | 0.27 | 0.54 | 0 | 0.56 | 0.15 | 0.15 | 1.00 | -0.02 | 0.15 | 0.22 |
| inflation | -0.80 | -0.02 | 0 | 0.00 | -0.03 | -0.03 | -0.02 | 1.00 | 0.12 | 0.11 |
| price_BUY | -0.06 | 0.06 | 0 | 0.00 | -0.06 | -0.06 | 0.15 | 0.12 | 1.00 | 0.97 |
| price_SELL | -0.02 | 0.16 | 0 | 0.06 | -0.05 | -0.05 | 0.22 | 0.11 | 0.97 | 1.00 |
Giải thích code
| Dòng code | Giải thích |
|---|---|
| 1-2 | Chuẩn bị dữ liệu đầu vào: * dplyr::select(dplyr::all_of(vars_g1_cor)):
Chọn các cột cần thiết để tính tương quan (giả định
vars_g1_cor đã được định nghĩa). * na.omit(): Loại bỏ các hàng có bất
kỳ giá trị thiếu (NA) nào trong các cột đã chọn, đảm bảo chỉ sử
dụng các quan sát hoàn chỉnh. |
| 3 | Đếm số lượng quan sát hợp lệ: Đếm số
hàng (nrow()) còn lại sau khi đã loại bỏ các hàng có NA để
làm rõ cỡ mẫu thực tế được sử dụng. |
| 4-5 | In ra số lượng quan sát hợp lệ. |
| 6 | Tính ma trận tương quan: * Sử dụng hàm cor() để tính ma trận tương quan. * method = "pearson": Chỉ định tính
Hệ số tương quan Pearson (đo lường mối quan hệ tuyến
tính). * use = "complete.obs": Yêu
cầu hàm chỉ sử dụng các quan sát hoàn chỉnh (dù dữ liệu đã được lọc ở
bước 1, đây là một lớp bảo vệ). |
| 7-8 | Dùng knitr::kable() để in ra ma trận tương
quan: * round(..., 2): Làm tròn giá
trị tương quan đến 2 chữ số thập phân. * caption: Đặt tiêu đề cho bảng (Bảng
1.2.18). * align = "l": Căn lề trái
cho tên hàng. |
(2) Xác định các cặp tương quan mạnh nhất
1. melted_cor_g1 <- reshape2::melt(cor_matrix_g1, na.rm = TRUE)
2. melted_cor_unique_g1 <- melted_cor_g1 %>%
3. dplyr::filter(as.character(Var1) < as.character(Var2))
4. strongest_cor_g1 <- melted_cor_unique_g1 %>%
5. dplyr::arrange(desc(abs(value)))
6. top_5_cor_g1 <- head(strongest_cor_g1, 5) %>%
7. dplyr::mutate(value = round(value, 3))
8. knitr::kable(top_5_cor_g1,
9. caption = "Bảng 1.2.19: Top 5 cặp tương quan tuyến tính mạnh nhất",
10. col.names = c("Biến 1", "Biến 2", "Hệ số tương quan (r)"), align = "l")| Biến 1 | Biến 2 | Hệ số tương quan (r) |
|---|---|---|
| Sharpe_Ratio | Volatility_Buy | 1.000 |
| price_BUY | price_SELL | 0.974 |
| inflation | Real_Return | -0.800 |
| Expected_Return_Yr | Horizon_Days | 0.557 |
| Expected_Return_Yr | Nominal_Return | 0.545 |
1. top_3_pos_cor_g1 <- strongest_cor_g1 %>%
2. dplyr::filter(value > 0 & value < 1) %>%
3. head(3) %>%
4. dplyr::mutate(value = round(value, 3))
5. knitr::kable(top_3_pos_cor_g1,
6. caption = "Bảng 1.2.20: Top 3 cặp tương quan dương mạnh nhất",
7. col.names = c("Biến 1", "Biến 2", "Hệ số tương quan (r)"), align = "l")| Biến 1 | Biến 2 | Hệ số tương quan (r) |
|---|---|---|
| price_BUY | price_SELL | 0.974 |
| Expected_Return_Yr | Horizon_Days | 0.557 |
| Expected_Return_Yr | Nominal_Return | 0.545 |
1. top_3_neg_cor_g1 <- strongest_cor_g1 %>%
2. dplyr::filter(value < 0) %>%
3. dplyr::arrange(value) %>%
4. head(3) %>%
5. dplyr::mutate(value = round(value, 3))
6. if(nrow(top_3_neg_cor_g1) > 0) {
7. knitr::kable(top_3_neg_cor_g1,
8. caption = "Bảng 1.2.21: Top 3 cặp tương quan âm Mạnh nhất",
9. col.names = c("Biến 1", "Biến 2", "Hệ số tương quan (r)"), align = "l")}| Biến 1 | Biến 2 | Hệ số tương quan (r) |
|---|---|---|
| inflation | Real_Return | -0.800 |
| price_BUY | Real_Return | -0.063 |
| price_BUY | Volatility_Buy | -0.062 |
Giải thích code:
| Dòng code | Giải thích |
|---|---|
| 1-3 | Chuyển đổi ma trận sang dạng dài: * 1: Sử dụng reshape2::melt() để chuyển ma
trận tương quan (cor_matrix_g1) từ định dạng vuông sang
định dạng dài (3 cột: Var1, Var2,
value). * 3: Lọc ( filter) chỉ giữ lại các cặp tương quan duy
nhất (Var1 < Var2) để loại bỏ các
giá trị trùng lặp và tương quan của biến với chính nó. |
| 4-6 | Top 5 tương quan mạnh nhất: * 4: Sắp xếp ( arrange) các cặp theo trị
tuyệt đối của hệ số tương quan (abs(value)) theo thứ tự
giảm dần. * 5-6: Lấy 5 cặp đầu tiên ( head(5)), làm tròn hệ số tương quan đến 3 chữ số thập
phân. |
| 7-9 | Dùng knitr::kable() để in ra Bảng 1.2.19
(Top 5 mạnh nhất). |
| 10-14 | Top 3 tương quan dương mạnh nhất: Lọc
(filter) các cặp tương quan dương
(value > 0 & value < 1), lấy 3 cặp đầu tiên, làm
tròn giá trị. |
| 15-17 | Dùng knitr::kable() để in ra Bảng 1.2.20
(Top 3 dương). |
| 18-24 | Top 3 tương quan âm mạnh nhất: * Lọc ( filter) các cặp tương quan âm
(value < 0). * Sắp xếp ( arrange) theo
giá trị tăng dần (tức là âm nhất lên đầu). * Lấy 3 cặp đầu tiên, làm tròn giá trị. * 22: Kiểm tra nếu có kết quả âm mới in bảng. * 23-24: Dùng knitr::kable() để in ra Bảng 1.2.21 (Top 3 âm). |
(3) Kiểm định ý nghĩa thống kê của các tương quan chính
1. library(purrr); library(dplyr); library(knitr); library(tibble); library(stats); library(broom)
2. pairs_to_test_g1 <- list(c("Real_Return", "Volatility_Buy"),
3. c("Real_Return", "Horizon_Days"),
4. c("Volatility_Buy", "Sharpe_Ratio"),
5. c("Nominal_Return", "Real_Return"),
6. c("amount", "Real_Return"))
7. cor_test_results_df <- map_dfr(pairs_to_test_g1, function(pair) {
8. var1 <- pair[1]
9. var2 <- pair[2]
10. pair_label <- paste(var1, "vs", var2)
11. pair_data_g1 <- cor_data_input_g1[, c(var1, var2)] %>% na.omit()
12. if (nrow(pair_data_g1) >= 3) {test_result <- tryCatch({
13. cor.test(pair_data_g1[[var1]], pair_data_g1[[var2]], method = "pearson") %>%
14. tidy() %>%
15. select(Correlation = estimate, P_Value = p.value, DF = parameter) %>%
16. mutate(across(where(is.numeric), ~ round(.x, 3)))}, error = function(e){
17. tibble(Correlation = NA_real_, P_Value = NA_real_, DF = NA_integer_)})} else {
18. test_result <- tibble(Correlation = NA_real_, P_Value = NA_real_, DF = NA_integer_)}
19. test_result %>% add_column(Pair = pair_label, .before = 1)})
20. cor_test_final_df <- cor_test_results_df %>%
21. mutate(P_Value_Formatted = format.pval(P_Value, digits = 3, eps = 0.001),
22. Significance = case_when(is.na(P_Value) ~ "Lỗi/NA",
23. P_Value < 0.05 ~ "Có ý nghĩa (p < 0.05)",
24. TRUE ~ "Không ý nghĩa (p >= 0.05)"))
25. kable(cor_test_final_df %>% select(Pair, Correlation, P_Value_Formatted, Significance, DF),
26. caption = "Bảng 1.2.22: Kết quả kiểm định ý nghĩa thống kê cho các tương quan chính",
27. col.names = c("Cặp biến", "Hệ số r", "P-value", "Kết luận", "Bậc tự do (df)"),
28. format = "pipe", align = "l")| Cặp biến | Hệ số r | P-value | Kết luận | Bậc tự do (df) |
|---|---|---|---|---|
| Real_Return vs Volatility_Buy | 0.040 | <0.001 | Có ý nghĩa (p < 0.05) | 405256 |
| Real_Return vs Horizon_Days | 0.125 | <0.001 | Có ý nghĩa (p < 0.05) | 405256 |
| Volatility_Buy vs Sharpe_Ratio | 1.000 | <0.001 | Có ý nghĩa (p < 0.05) | 405256 |
| Nominal_Return vs Real_Return | 0.485 | <0.001 | Có ý nghĩa (p < 0.05) | 405256 |
| amount vs Real_Return | -0.001 | 0.377 | Không ý nghĩa (p >= 0.05) | 405256 |
Giải thích code
| Dòng code | Giải thích |
|---|---|
| 1 | Tải các gói thư viện cần thiết: purrr,
dplyr, knitr, tibble,
stats, và broom (để làm sạch kết quả kiểm định
thống kê). |
| 2-4 | Khai báo pairs_to_test_g1, một
danh sách chứa các cặp biến mà ta muốn kiểm định ý
nghĩa thống kê của mối tương quan. |
| 5-20 | Thực hiện kiểm định tương quan hàng
loạt: * 5: Sử dụng map_dfr()
để lặp qua từng cặp biến. * 10: Lấy dữ liệu của cặp biến và loại bỏ NA ( na.omit()). * 11-18: Kiểm định: * cor.test(...): Thực hiện kiểm định tương
quan Pearson. * tidy() (từ
broom): Chuyển kết quả kiểm định sang định dạng bảng sạch.
* select(...): Lọc và đổi tên các cột
cần thiết (Correlation, P_Value,
DF). * mutate(across(...)): Làm tròn các cột số
đến 3 chữ số thập phân. * Dòng 19: Thêm cột tên cặp biến ( Pair) vào kết quả. |
| 21-25 | Định dạng kết quả cuối cùng: * 22: Định dạng p-value bằng format.pval()
để hiển thị các giá trị rất nhỏ dưới dạng < 0.001. * 23-25: Tạo cột Significance (Ý nghĩa
thống kê) với kết luận dựa trên ngưỡng p-value \(< 0.05\). |
| 26-29 | Dùng kable() để in ra bảng kết quả kiểm
định cuối cùng: * caption: Đặt tiêu
đề cho bảng (Bảng 1.2.22). * col.names: Đặt tên cột hiển thị bằng tiếng
Việt. * format = "pipe" /
align = "l": Định dạng bảng. |
Nhận xét phân tích tổng hợp mục 1.2.2:
Volatility_Buy và
Sharpe_Ratio. Điều này chỉ ra rằng hai biến này
thừa thãi (đa cộng tuyến hoàn hảo). Hành động
cần thiết: Loại bỏ một trong hai biến khỏi mô hình đa
biến.Real_Return):
inflation
(Bảng 1.2.19). Đây là mối quan hệ tuyến tính mạnh nhất, hợp lý về mặt
kinh tế (lạm phát ăn mòn lợi suất thực tế).Volatility_Buy (Rủi ro): r = 0.040
(Dương rất yếu, Bảng 1.2.22).Horizon_Days (Thời gian): r = 0.125
(Dương yếu, Bảng 1.2.22).amount (Quy mô): r = -0.001 (Gần bằng
0, Không có ý nghĩa thống kê với p=0.377, Bảng
1.2.22).Real_Return.price_BUY và
price_SELL tương quan dương rất mạnh (r = 0.974).
Expected_Return_Yr tương quan dương trung bình với
Horizon_Days và Nominal_Return.Sau khi mô tả phân bố và xem xét tương quan, mục này sử dụng thống kê suy luận để kiểm tra xem liệu có sự khác biệt có ý nghĩa thống kê về giá trị trung bình của các biến định lượng nhóm 1 giữa các nhóm được xác định bởi các biến phân loại hay không (ví dụ: lợi suất có khác nhau giữa các ngành, các chiến lược nắm giữ?). Giả thuyết gốc (H0) thường là “không có sự khác biệt trung bình giữa các nhóm”. Nếu p-value < 0.05, chúng ta bác bỏ H0.
Real_Return theo các
nhóm1. display_tukey_hsd <- function(anova_result, term_name) {
2. if (!is.null(anova_result) && summary(anova_result)[[1]][["Pr(>F)"]][1] < 0.05) {
3. tuk_output <- capture.output(TukeyHSD(anova_result))
4. cat(paste0("\nTukeyHSD: Real_Return ~ ", term_name, "\n"))
5. cat(paste(tuk_output, collapse = "\n"))
6. cat("\n")}}
7. if (all(c("Real_Return","investment") %in% names(nyse_group1_finite))) {
8. t_test_inv_rr <- run_t_test_calc(nyse_group1_finite, Real_Return ~ investment)
9. display_t_test_result(t_test_inv_rr, "Lợi suất thực tế", "Kết quả đầu tư (investment)")
10. cat("\n")}So sánh Lợi suất thực tế theo Kết quả đầu tư (investment) (Welch t-test)
| T-statistic | P-value | Mean Grp1 | Mean Grp2 | Chênh lệch | 95% CI Low | 95% CI High |
|---|---|---|---|---|---|---|
| -426 | 0 | -0.248 | 0.715 | -0.963 | -0.968 | -0.959 |
1. if (all(c("Real_Return","sector") %in% names(nyse_group1_finite))) {
2. anova_sec_rr <- run_anova_calc(nyse_group1_finite, Real_Return ~ sector)
3. display_anova_result(anova_sec_rr, "Lợi suất thực tế", "Ngành (sector)", term_key = "sector")
4. display_tukey_hsd(anova_sec_rr, "sector")
5. cat("\n")}So sánh Lợi suất thực tế theo Ngành (sector) (ANOVA)
| Nguồn biến đổi | df | SS | MS | F-statistic | P-value |
|---|---|---|---|---|---|
| sector | 4 | 4132 | 1032.92 | 1752 | 0 |
| Residuals | 405253 | 238942 | 0.59 | NA | NA |
Kết luận ANOVA: Có sự khác biệt trung bình ý nghĩa thống kê giữa ít nhất hai nhóm (p < 0.05). (Xem chi tiết cặp khác biệt bằng TukeyHSD() trên kết quả ANOVA.)
TukeyHSD: Real_Return ~ sector Tukey multiple comparisons of means 95% family-wise confidence level
Fit: aov(formula = formula, data = data)
$sector diff lwr upr p adj BANK-AUTO -0.02703 -0.03736 -0.01670 0.00 FMCG-AUTO -0.01938 -0.03118 -0.00758 0.00 RETAIL-AUTO 0.02885 0.01882 0.03888 0.00 TECH-AUTO 0.23427 0.22415 0.24440 0.00 FMCG-BANK 0.00765 -0.00402 0.01932 0.38 RETAIL-BANK 0.05588 0.04601 0.06574 0.00 TECH-BANK 0.26130 0.25134 0.27127 0.00 RETAIL-FMCG 0.04823 0.03683 0.05963 0.00 TECH-FMCG 0.25366 0.24217 0.26514 0.00 TECH-RETAIL 0.20543 0.19578 0.21507 0.00
1. if (all(c("Real_Return","Holding_Period") %in% names(nyse_group1_finite))) {
2. anova_hp_rr <- run_anova_calc(nyse_group1_finite, Real_Return ~ Holding_Period)
3. display_anova_result(anova_hp_rr, "Lợi suất thực tế", "Thời gian nắm giữ (Holding_Period)", term_key = "Holding_Period")
4. display_tukey_hsd(anova_hp_rr, "Holding_Period")
5. cat("\n")}So sánh Lợi suất thực tế theo Thời gian nắm giữ (Holding_Period) (ANOVA)
| Nguồn biến đổi | df | SS | MS | F-statistic | P-value |
|---|---|---|---|---|---|
| Holding_Period | 2 | 2442 | 1220.843 | 2056 | 0 |
| Residuals | 405255 | 240632 | 0.594 | NA | NA |
Kết luận ANOVA: Có sự khác biệt trung bình ý nghĩa thống kê giữa ít nhất hai nhóm (p < 0.05). (Xem chi tiết cặp khác biệt bằng TukeyHSD() trên kết quả ANOVA.)
TukeyHSD: Real_Return ~ Holding_Period Tukey multiple comparisons of means 95% family-wise confidence level
Fit: aov(formula = formula, data = data)
$Holding_Period diff lwr upr p adj Mid-term (31-180d)-Short-term (<=30d) 0.022 0.0148 0.0291 0 Long-term (>180d)-Short-term (<=30d) 0.171 0.1640 0.1776 0 Long-term (>180d)-Mid-term (31-180d) 0.149 0.1418 0.1558 0
1. if (all(c("Real_Return","Risk_Level") %in% names(nyse_group1_finite))) {
2. anova_rl_rr <- run_anova_calc(nyse_group1_finite, Real_Return ~ Risk_Level)
3. display_anova_result(anova_rl_rr, "Lợi suất thực tế", "Mức độ rủi ro (Risk_Level)", term_key = "Risk_Level")
4. display_tukey_hsd(anova_rl_rr, "Risk_Level")
5. cat("\n")}So sánh Lợi suất thực tế theo Mức độ rủi ro (Risk_Level) (ANOVA)
| Nguồn biến đổi | df | SS | MS | F-statistic | P-value |
|---|---|---|---|---|---|
| Risk_Level | 2 | 3526 | 1762.805 | 2982 | 0 |
| Residuals | 405255 | 239548 | 0.591 | NA | NA |
Kết luận ANOVA: Có sự khác biệt trung bình ý nghĩa thống kê giữa ít nhất hai nhóm (p < 0.05). (Xem chi tiết cặp khác biệt bằng TukeyHSD() trên kết quả ANOVA.)
TukeyHSD: Real_Return ~ Risk_Level Tukey multiple comparisons of means 95% family-wise confidence level
Fit: aov(formula = formula, data = data)
$Risk_Level diff lwr upr p adj Medium-Low 0.2088 0.2018 0.2157 0 High-Low 0.1864 0.1795 0.1933 0 High-Medium -0.0224 -0.0293 -0.0155 0
1. if (all(c("Real_Return","Amount_Level") %in% names(nyse_group1_finite))) {
2. anova_al_rr <- run_anova_calc(nyse_group1_finite, Real_Return ~ Amount_Level)
3. display_anova_result(anova_al_rr, "Lợi suất thực tế", "Quy mô giao dịch (Amount_Level)", term_key = "Amount_Level")
4. display_tukey_hsd(anova_al_rr, "Amount_Level")
5. cat("\n")}So sánh Lợi suất thực tế theo Quy mô giao dịch (Amount_Level) (ANOVA)
| Nguồn biến đổi | df | SS | MS | F-statistic | P-value |
|---|---|---|---|---|---|
| Amount_Level | 2 | 0.465 | 0.233 | 0.388 | 0.678 |
| Residuals | 405255 | 243073.327 | 0.600 | NA | NA |
Kết luận ANOVA: Không có đủ bằng chứng về sự khác biệt trung bình ý nghĩa (p ≥ 0.05).
Giải thích code:
| Dòng code | Giải thích |
|---|---|
| 1 | Tải các gói cần thiết: purrr,
dplyr, knitr, tibble,
stats, và broom. |
| 2-8 | Định nghĩa hàm
display_tukey_hsd: Hàm này dùng để hiển thị kết
quả kiểm định so sánh cặp (Post-hoc test) TukeyHSD.
* Hàm chỉ chạy nếu kết quả ANOVA ban đầu ( anova_result) cho thấy có ý nghĩa thống kê (p-value \(< 0.05\)). * TukeyHSD(...) thực hiện kiểm định so sánh tất cả các cặp
nhóm. |
| 9-12 | Kiểm định t-test (Real_Return ~
investment): * Kiểm tra điều kiện tồn tại của cả hai biến. * Gọi hàm run_t_test_calc (giả định đã
được định nghĩa) để thực hiện t-test so sánh trung bình giữa hai nhóm
GOOD và BAD. * Gọi hàm display_t_test_result để hiển thị kết quả t-test. |
| 13-17 | Kiểm định ANOVA và TukeyHSD
(Real_Return ~ sector): * Gọi hàm run_anova_calc (đã được định nghĩa) để thực hiện
ANOVA (Phân tích phương sai) vì biến
sector có nhiều hơn 2 nhóm. * Hiển thị kết quả ANOVA. * Gọi hàm display_tukey_hsd để chạy và hiển thị kiểm
định TukeyHSD (so sánh tất cả các cặp ngành) nếu kết
quả ANOVA có ý nghĩa. |
| 18-32 | Các kiểm định ANOVA và TukeyHSD khác:
Thực hiện các bước tương tự như trên cho các biến phân loại khác: * Holding_Period (Thời gian nắm giữ). * Risk_Level (Mức độ rủi ro). * Amount_Level (Quy mô giao dịch). |
Nhận xét:
investment (GOOD/BAD): t-test cho thấy
sự khác biệt rất lớn và có ý nghĩa thống kê
(p=0). Nhóm GOOD có lợi suất trung bình cao hơn
BAD tới 96.3%, khẳng định sự phân loại hiệu quả của biến
investment.sector (Ngành): ANOVA xác nhận
có khác biệt ý nghĩa (p=0). TukeyHSD chỉ ra tất
cả các cặp ngành đều khác biệt (p<0.05), ngoại trừ
FMCG-BANK. Thứ tự lợi suất trung bình là: TECH >
RETAIL > FMCG \(\approx\) BANK >
AUTO.Holding_Period (Thời gian nắm giữ):
ANOVA xác nhận có khác biệt ý nghĩa (p=0). TukeyHSD chỉ
ra cả ba cặp đều khác biệt (p<0.05), ủng hộ chiến
lược dài hạn: Long-term > Mid-term >
Short-term.Risk_Level (Mức độ rủi ro): ANOVA xác
nhận có khác biệt ý nghĩa (p=0). TukeyHSD chỉ ra
cả ba cặp đều khác biệt (p<0.05). Tuy nhiên, thứ tự
lợi suất trung bình là Medium > High > Low,
ngược với lý thuyết rủi ro-lợi suất thông thường
(Risk-Return Trade-off), phù hợp với “bất thường rủi ro thấp”.Amount_Level (Quy mô giao dịch): ANOVA
cho thấy Không có khác biệt ý nghĩa (p=0.678). Quy mô
giao dịch không ảnh hưởng đến lợi suất thực tế trung bình.Kết luận: Kết quả kiểm định cho thấy lợi suất thực tế trung bình bị ảnh hưởng đáng kể bởi kết quả phân loại (GOOD/BAD), ngành nghề (TECH > RETAIL > FMCG ≈ BANK > AUTO), thời gian nắm giữ (Long > Mid > Short), và mức độ rủi ro (Medium > High > Low). Ngược lại, quy mô giao dịch dường như không có ảnh hưởng ý nghĩa đến lợi suất trung bình. Phát hiện về mối quan hệ giữa rủi ro và lợi suất (nhóm Medium tốt nhất) là điểm bất thường đáng chú ý.
Volatility_Buy theo các
nhóm1. if (all(c("Volatility_Buy","sector") %in% names(nyse_group1_finite))) {
2. anova_sec_vol <- run_anova_calc(nyse_group1_finite, Volatility_Buy ~ sector)
3. display_anova_result(anova_sec_vol, "Độ biến động (Volatility_Buy)", "Ngành (sector)", term_key = "sector")
4. display_tukey_hsd(anova_sec_vol, "sector")
5. cat("\n")}So sánh Độ biến động (Volatility_Buy) theo Ngành (sector) (ANOVA)
| Nguồn biến đổi | df | SS | MS | F-statistic | P-value |
|---|---|---|---|---|---|
| sector | 4 | 661 | 165.136 | 20634 | 0 |
| Residuals | 405253 | 3243 | 0.008 | NA | NA |
Kết luận ANOVA: Có sự khác biệt trung bình ý nghĩa thống kê giữa ít nhất hai nhóm (p < 0.05). (Xem chi tiết cặp khác biệt bằng TukeyHSD() trên kết quả ANOVA.)
TukeyHSD: Real_Return ~ sector Tukey multiple comparisons of means 95% family-wise confidence level
Fit: aov(formula = formula, data = data)
$sector diff lwr upr p adj BANK-AUTO -0.01152 -0.012723 -0.0103157 0.000 FMCG-AUTO -0.12779 -0.129162 -0.1264119 0.000 RETAIL-AUTO -0.00861 -0.009781 -0.0074447 0.000 TECH-AUTO -0.00972 -0.010901 -0.0085428 0.000 FMCG-BANK -0.11627 -0.117627 -0.1149085 0.000 RETAIL-BANK 0.00291 0.001757 0.0040559 0.000 TECH-BANK 0.00180 0.000637 0.0029580 0.000 RETAIL-FMCG 0.11917 0.117846 0.1205024 0.000 TECH-FMCG 0.11807 0.116727 0.1194030 0.000 TECH-RETAIL -0.00111 -0.002233 0.0000148 0.055
1. if (all(c("Volatility_Buy","Holding_Period") %in% names(nyse_group1_finite))) {
2. anova_hp_vol <- run_anova_calc(nyse_group1_finite, Volatility_Buy ~ Holding_Period)
3. display_anova_result(anova_hp_vol, "Độ biến động (Volatility_Buy)", "Thời gian nắm giữ (Holding_Period)", term_key = "Holding_Period")
4. display_tukey_hsd(anova_hp_vol, "Holding_Period")
5. cat("\n")}So sánh Độ biến động (Volatility_Buy) theo Thời gian nắm giữ (Holding_Period) (ANOVA)
| Nguồn biến đổi | df | SS | MS | F-statistic | P-value |
|---|---|---|---|---|---|
| Holding_Period | 2 | 0.001 | 0.00 | 0.034 | 0.967 |
| Residuals | 405255 | 3903.818 | 0.01 | NA | NA |
Kết luận ANOVA: Không có đủ bằng chứng về sự khác biệt trung bình ý nghĩa (p ≥ 0.05).
1. if (all(c("Volatility_Buy","Risk_Level") %in% names(nyse_group1_finite))) {
2. anova_rl_vol <- run_anova_calc(nyse_group1_finite, Volatility_Buy ~ Risk_Level)
3. display_anova_result(anova_rl_vol, "Độ biến động (Volatility_Buy)", "Mức độ rủi ro (Risk_Level)", term_key = "Risk_Level")
4. display_tukey_hsd(anova_rl_vol, "Risk_Level")
5. cat("\n")}So sánh Độ biến động (Volatility_Buy) theo Mức độ rủi ro (Risk_Level) (ANOVA)
| Nguồn biến đổi | df | SS | MS | F-statistic | P-value |
|---|---|---|---|---|---|
| Risk_Level | 2 | 2899 | 1449.464 | 584543 | 0 |
| Residuals | 405255 | 1005 | 0.002 | NA | NA |
Kết luận ANOVA: Có sự khác biệt trung bình ý nghĩa thống kê giữa ít nhất hai nhóm (p < 0.05). (Xem chi tiết cặp khác biệt bằng TukeyHSD() trên kết quả ANOVA.)
TukeyHSD: Real_Return ~ Risk_Level Tukey multiple comparisons of means 95% family-wise confidence level
Fit: aov(formula = formula, data = data)
$Risk_Level diff lwr upr p adj Medium-Low 0.0726 0.0721 0.073 0 High-Low 0.2036 0.2032 0.204 0 High-Medium 0.1311 0.1306 0.132 0
Giải thích code:
| Dòng code | Giải thích |
|---|---|
| 1-5 | Kiểm định ANOVA và TukeyHSD
(Volatility_Buy ~ sector): Thực hiện
ANOVA để kiểm tra sự khác biệt trung bình của Độ biến
động (Volatility_Buy) giữa các nhóm
Ngành (sector). Hiển thị kết quả ANOVA và
so sánh cặp TukeyHSD nếu ANOVA có ý nghĩa thống kê. |
| 6-10 | Kiểm định ANOVA và TukeyHSD
(Volatility_Buy ~ Holding_Period):
Thực hiện ANOVA để kiểm tra sự khác biệt trung bình của
Volatility_Buy giữa các nhóm Thời gian nắm
giữ (Holding_Period). Hiển thị kết quả ANOVA và so
sánh cặp TukeyHSD. |
| 11-15 | Kiểm định ANOVA và TukeyHSD
(Volatility_Buy ~ Risk_Level): Thực
hiện ANOVA để kiểm tra sự khác biệt trung bình của
Volatility_Buy giữa các nhóm Mức độ rủi ro
(Risk_Level). Hiển thị kết quả ANOVA và so sánh cặp
TukeyHSD. |
Nhận xét:
sector): ANOVA cho thấy
có khác biệt ý nghĩa thống kê (p=0) về
Volatility_Buy giữa các ngành. TukeyHSD xác nhận hầu hết
các cặp đều khác biệt. Thứ tự rủi ro trung bình là: AUTO (cao
nhất) > TECH \(\approx\) RETAIL >
BANK > FMCG (thấp nhất). Sự khác biệt này phù hợp với tính
chất của từng ngành (ví dụ: FMCG có rủi ro thấp nhất).Holding_Period): ANOVA cho thấy không có
khác biệt ý nghĩa thống kê (p=0.967) về
Volatility_Buy trung bình. Điều này gợi ý rằng quyết định
về chiến lược thời gian nắm giữ độc lập với mức độ rủi
ro (biến động) trung bình của cổ phiếu được chọn mua.Risk_Level): ANOVA
và TukeyHSD xác nhận có khác biệt ý nghĩa thống kê
(p=0) giữa tất cả các nhóm Low,
Medium, High, với thứ tự trung bình là
High > Medium > Low. Kết quả này là hiển nhiên và
xác nhận tính đúng đắn của việc tạo biến phân loại
Risk_Level theo phân vị.Kết luận: Mức độ rủi ro (Độ biến động) bị ảnh hưởng đáng kể bởi Ngành nghề của cổ phiếu nhưng không bị ảnh hưởng bởi chiến lược thời gian nắm giữ của nhà đầu tư.
Sharpe_Ratio theo các
nhóm1. if (all(c("Sharpe_Ratio","investment") %in% names(nyse_group1_finite))) {
2. t_test_inv_sharpe <- run_t_test_calc(nyse_group1_finite, Sharpe_Ratio ~ investment)
3. display_t_test_result(t_test_inv_sharpe, "Tỷ suất Sharpe", "Kết quả đầu tư (investment)")
4. cat("\n")}So sánh Tỷ suất Sharpe theo Kết quả đầu tư (investment) (Welch t-test)
| T-statistic | P-value | Mean Grp1 | Mean Grp2 | Chênh lệch | 95% CI Low | 95% CI High |
|---|---|---|---|---|---|---|
| -30 | 0 | 0.251 | 0.261 | -0.01 | -0.01 | -0.009 |
1. if (all(c("Sharpe_Ratio","sector") %in% names(nyse_group1_finite))) {
2. anova_sec_sharpe <- run_anova_calc(nyse_group1_finite, Sharpe_Ratio ~ sector)
3. display_anova_result(anova_sec_sharpe, "Tỷ suất Sharpe", "Ngành (sector)", term_key = "sector")
4. display_tukey_hsd(anova_sec_sharpe, "sector")
5. cat("\n")}So sánh Tỷ suất Sharpe theo Ngành (sector) (ANOVA)
| Nguồn biến đổi | df | SS | MS | F-statistic | P-value |
|---|---|---|---|---|---|
| sector | 4 | 661 | 165.136 | 20634 | 0 |
| Residuals | 405253 | 3243 | 0.008 | NA | NA |
Kết luận ANOVA: Có sự khác biệt trung bình ý nghĩa thống kê giữa ít nhất hai nhóm (p < 0.05). (Xem chi tiết cặp khác biệt bằng TukeyHSD() trên kết quả ANOVA.)
TukeyHSD: Real_Return ~ sector Tukey multiple comparisons of means 95% family-wise confidence level
Fit: aov(formula = formula, data = data)
$sector diff lwr upr p adj BANK-AUTO -0.01152 -0.012723 -0.0103157 0.000 FMCG-AUTO -0.12779 -0.129162 -0.1264119 0.000 RETAIL-AUTO -0.00861 -0.009781 -0.0074447 0.000 TECH-AUTO -0.00972 -0.010901 -0.0085428 0.000 FMCG-BANK -0.11627 -0.117627 -0.1149085 0.000 RETAIL-BANK 0.00291 0.001757 0.0040559 0.000 TECH-BANK 0.00180 0.000637 0.0029580 0.000 RETAIL-FMCG 0.11917 0.117846 0.1205024 0.000 TECH-FMCG 0.11807 0.116727 0.1194030 0.000 TECH-RETAIL -0.00111 -0.002233 0.0000148 0.055
1. if (all(c("Sharpe_Ratio","Risk_Level") %in% names(nyse_group1_finite))) {
2. anova_rl_sharpe <- run_anova_calc(nyse_group1_finite, Sharpe_Ratio ~ Risk_Level)
3. display_anova_result(anova_rl_sharpe, "Tỷ suất Sharpe", "Mức độ rủi ro (Risk_Level)", term_key = "Risk_Level")
4. display_tukey_hsd(anova_rl_sharpe, "Risk_Level")
5. cat("\n")}So sánh Tỷ suất Sharpe theo Mức độ rủi ro (Risk_Level) (ANOVA)
| Nguồn biến đổi | df | SS | MS | F-statistic | P-value |
|---|---|---|---|---|---|
| Risk_Level | 2 | 2899 | 1449.464 | 584543 | 0 |
| Residuals | 405255 | 1005 | 0.002 | NA | NA |
Kết luận ANOVA: Có sự khác biệt trung bình ý nghĩa thống kê giữa ít nhất hai nhóm (p < 0.05). (Xem chi tiết cặp khác biệt bằng TukeyHSD() trên kết quả ANOVA.)
TukeyHSD: Real_Return ~ Risk_Level Tukey multiple comparisons of means 95% family-wise confidence level
Fit: aov(formula = formula, data = data)
$Risk_Level diff lwr upr p adj Medium-Low 0.0726 0.0721 0.073 0 High-Low 0.2036 0.2032 0.204 0 High-Medium 0.1311 0.1306 0.132 0
Giải thích code:
| Dòng code | Giải thích |
|---|---|
| 1-4 | Kiểm định t-test (Sharpe_Ratio ~
investment): Kiểm tra điều kiện tồn tại của biến.
Gọi hàm run_t_test_calc để thực hiện t-test so sánh
Tỷ suất Sharpe (Sharpe_Ratio) trung bình
giữa hai nhóm GOOD và BAD. Hiển thị kết quả
t-test. |
| 5-9 | Kiểm định ANOVA và TukeyHSD
(Sharpe_Ratio ~ sector): Thực hiện
ANOVA để kiểm tra sự khác biệt trung bình của Sharpe_Ratio
giữa các nhóm Ngành (sector). Hiển thị kết
quả ANOVA và so sánh cặp TukeyHSD nếu có ý nghĩa. |
| 10-14 | Kiểm định ANOVA và TukeyHSD
(Sharpe_Ratio ~ Risk_Level): Thực
hiện ANOVA để kiểm tra sự khác biệt trung bình của
Sharpe_Ratio giữa các nhóm Mức độ rủi ro
(Risk_Level). Hiển thị kết quả ANOVA và so sánh cặp
TukeyHSD. |
Nhận xét:
investment):
t-test cho thấy có khác biệt ý nghĩa thống kê (p=0).
Tuy nhiên, chênh lệch thực tế rất nhỏ
(GOOD chỉ cao hơn BAD khoảng 0.01).
Sharpe_Ratio không phải là yếu tố phân biệt mạnh mẽ giữa
hai nhóm này.sector) & Mức độ rủi ro
(Risk_Level): ANOVA và TukeyHSD đều xác nhận
có khác biệt ý nghĩa thống kê (p=0). Quan trọng
nhất: Thứ tự và mức độ khác biệt của Sharpe_Ratio
giữa các nhóm giống hệt với Volatility_Buy
(đã phân tích ở Mục 1.2.3.2).
AUTO (cao nhất) >
TECH \(\approx\)
RETAIL > BANK > FMCG (thấp
nhất).High (cao nhất) >
Medium > Low (thấp nhất).Sharpe_Ratio và Volatility_Buy (kể cả
sự khác biệt chi tiết của TukeyHSD) khẳng định mối tương quan
tuyến tính gần như hoàn hảo (r \(\approx\) 1.00) giữa hai biến này.
Do đó, Sharpe_Ratio không cung cấp thông tin
mới và cần được loại bỏ khỏi các mô hình đa biến để tránh đa
cộng tuyến hoàn hảo.amount theo các
nhóm1. if (all(c("amount","sector") %in% names(nyse_group1_finite))) {
2. anova_sec_amt <- run_anova_calc(nyse_group1_finite, amount ~ sector)
3. display_anova_result(anova_sec_amt, "Quy mô giao dịch (amount)", "Ngành (sector)", term_key = "sector")
4. display_tukey_hsd(anova_sec_amt, "sector")
5. cat("\n")}So sánh Quy mô giao dịch (amount) theo Ngành (sector) (ANOVA)
| Nguồn biến đổi | df | SS | MS | F-statistic | P-value |
|---|---|---|---|---|---|
| sector | 4 | 361714814 | 90428703 | 0.554 | 0.696 |
| Residuals | 405253 | 66131926423339 | 163186766 | NA | NA |
Kết luận ANOVA: Không có đủ bằng chứng về sự khác biệt trung bình ý nghĩa (p ≥ 0.05).
1. if (all(c("amount","investment") %in% names(nyse_group1_finite))) {
2. t_test_inv_amt <- run_t_test_calc(nyse_group1_finite, amount ~ investment)
3. display_t_test_result(t_test_inv_amt, "Quy mô giao dịch (amount)", "Kết quả đầu tư (investment)")
4. cat("\n")}So sánh Quy mô giao dịch (amount) theo Kết quả đầu tư (investment) (Welch t-test)
| T-statistic | P-value | Mean Grp1 | Mean Grp2 | Chênh lệch | 95% CI Low | 95% CI High |
|---|---|---|---|---|---|---|
| -0.215 | 0.83 | 8105 | 8114 | -9.05 | -91.7 | 73.6 |
1. if (all(c("amount","Holding_Period") %in% names(nyse_group1_finite))) {
2. anova_hp_amt <- run_anova_calc(nyse_group1_finite, amount ~ Holding_Period)
3. display_anova_result(anova_hp_amt, "Quy mô giao dịch (amount)", "Thời gian nắm giữ (Holding_Period)", term_key = "Holding_Period")
4. display_tukey_hsd(anova_hp_amt, "Holding_Period")
5. cat("\n")}So sánh Quy mô giao dịch (amount) theo Thời gian nắm giữ (Holding_Period) (ANOVA)
| Nguồn biến đổi | df | SS | MS | F-statistic | P-value |
|---|---|---|---|---|---|
| Holding_Period | 2 | 465428644 | 232714322 | 1.43 | 0.24 |
| Residuals | 405255 | 66131822709526 | 163185705 | NA | NA |
Kết luận ANOVA: Không có đủ bằng chứng về sự khác biệt trung bình ý nghĩa (p ≥ 0.05).
Giải thích code:
| Dòng code | Giải thích |
|---|---|
| 1-5 | Kiểm định ANOVA và TukeyHSD
(amount ~ sector): Thực hiện ANOVA để
kiểm tra sự khác biệt trung bình của Quy mô giao dịch
(amount) giữa các nhóm Ngành
(sector). Hiển thị kết quả ANOVA và so sánh cặp
TukeyHSD. |
| 6-9 | Kiểm định t-test (amount ~
investment): Thực hiện t-test để so sánh trung
bình của amount giữa hai nhóm Kết quả đầu
tư (investment) là GOOD và
BAD. Hiển thị kết quả t-test. |
| 10-14 | Kiểm định ANOVA và TukeyHSD
(amount ~ Holding_Period): Thực hiện
ANOVA để kiểm tra sự khác biệt trung bình của amount giữa
các nhóm Thời gian nắm giữ
(Holding_Period). Hiển thị kết quả ANOVA và so sánh cặp
TukeyHSD. |
Nhận xét:
sector): ANOVA cho thấy
Không có khác biệt ý nghĩa thống kê (p=0.696) về
amount trung bình giữa các ngành.investment):
t-test cho thấy Không có khác biệt ý nghĩa thống kê
(p=0.83). Chênh lệch Mean giữa nhóm GOOD và
BAD là cực kỳ nhỏ (\(\approx\) 9 USD).Holding_Period): ANOVA cho thấy Không có
khác biệt ý nghĩa thống kê (p=0.24).Kết luận: Quy mô giao dịch trung bình
(amount) không có sự khác biệt ý nghĩa thống
kê khi phân nhóm theo Ngành, Kết quả Đầu tư, hoặc Thời gian Nắm
giữ. Điều này khẳng định amount là một biến độc
lập trong bộ dữ liệu này, không liên quan đến các yếu tố phân
loại cốt lõi.
Horizon_Days theo các
nhóm1. if (all(c("Horizon_Days","sector") %in% names(nyse_group1_finite))) {
2. anova_sec_hor <- run_anova_calc(nyse_group1_finite, Horizon_Days ~ sector)
3. display_anova_result(anova_sec_hor, "Thời gian nắm giữ (Horizon_Days)", "Ngành (sector)", term_key = "sector")
4. display_tukey_hsd(anova_sec_hor, "sector")
5. cat("\n")}So sánh Thời gian nắm giữ (Horizon_Days) theo Ngành (sector) (ANOVA)
| Nguồn biến đổi | df | SS | MS | F-statistic | P-value |
|---|---|---|---|---|---|
| sector | 4 | 59718 | 14929 | 0.336 | 0.854 |
| Residuals | 405253 | 17991548813 | 44396 | NA | NA |
Kết luận ANOVA: Không có đủ bằng chứng về sự khác biệt trung bình ý nghĩa (p ≥ 0.05).
1. if (all(c("Horizon_Days","investment") %in% names(nyse_group1_finite))) {
2. t_test_inv_hor <- run_t_test_calc(nyse_group1_finite, Horizon_Days ~ investment)
3. display_t_test_result(t_test_inv_hor, "Thời gian nắm giữ (Horizon_Days)", "Kết quả đầu tư (investment)")
4. cat("\n")}So sánh Thời gian nắm giữ (Horizon_Days) theo Kết quả đầu tư (investment) (Welch t-test)
| T-statistic | P-value | Mean Grp1 | Mean Grp2 | Chênh lệch | 95% CI Low | 95% CI High |
|---|---|---|---|---|---|---|
| -46.9 | 0 | 176 | 209 | -33 | -34.4 | -31.7 |
Giải thích code:
| Dòng code | Giải thích |
|---|---|
| 1-5 | Kiểm định ANOVA và TukeyHSD
(Horizon_Days ~ sector): Thực hiện
ANOVA để kiểm tra sự khác biệt trung bình của Thời gian nắm
giữ (Horizon_Days) giữa các nhóm
Ngành (sector). Hiển thị kết quả ANOVA và
so sánh cặp TukeyHSD. |
| 6-9 | Kiểm định t-test (Horizon_Days ~
investment): Thực hiện t-test để so sánh trung
bình của Horizon_Days giữa hai nhóm Kết quả đầu
tư (investment) là GOOD và
BAD. Hiển thị kết quả t-test. |
Nhận xét:
sector): ANOVA cho thấy
Không có khác biệt ý nghĩa thống kê (p=0.854) về
Horizon_Days trung bình giữa các ngành. Thời gian nắm giữ
trung bình không phụ thuộc vào ngành của cổ phiếu.investment):
t-test cho thấy Có khác biệt ý nghĩa thống kê (p=0).
Nhóm GOOD có thời gian nắm giữ trung bình dài hơn
đáng kể (\(\approx\) 209 ngày)
so với nhóm BAD (\(\approx\) 176 ngày), chênh lệch khoảng
33 ngày.
GOOD) có xu hướng được nắm giữ lâu hơn, có thể
là do nhà đầu tư kiên nhẫn hơn, hoặc do lợi suất dương cần thời gian lâu
hơn để được ghi nhận.Kết luận tổng hợp mục 1.2.3 (Kiểm định khác biệt trung bình):
Phân tích kiểm định sự khác biệt trung bình đã cung cấp các kết luận sau:
Lợi suất & Nắm giữ: Lợi suất Thực tế
(Real_Return) trung bình khác biệt ý nghĩa theo
investment (GOOD/BAD), sector,
Holding_Period (Long > Mid > Short) và
Risk_Level (Medium > High > Low),
nhưng không khác biệt theo Amount_Level.
Các khoản đầu tư thành công (GOOD) có thời gian nắm giữ
(Horizon_Days) dài hơn đáng kể.
Rủi ro: Volatility_Buy trung bình
khác biệt ý nghĩa theo sector
(FMCG thấp nhất, AUTO cao nhất) nhưng
không khác biệt theo
Holding_Period.
Tương quan hoàn hảo: Sharpe_Ratio
và Volatility_Buy cho kết quả kiểm định gần như
giống hệt nhau, củng cố quyết định loại bỏ
Sharpe_Ratio khỏi các mô hình đa biến.
Quy mô giao dịch: amount trung bình
không khác biệt ý nghĩa thống kê theo bất kỳ yếu tố
phân loại chính nào (sector, investment,
Holding_Period).
Bức tranh tổng thể cho thấy các yếu tố phân loại quan trọng nhất ảnh
hưởng đến hiệu suất là investment (hiển
nhiên), Ngành nghề, Thời gian nắm giữ,
và Rủi ro, trong khi Quy mô giao dịch dường như là độc
lập. Mối quan hệ Rủi ro-Lợi suất theo thứ tự Medium > High
> Low là một “bất thường” đáng lưu ý.
Real_Return) vs. Danh nghĩa
(Nominal_Return)1. library(ggplot2); library(scales); library(dplyr); library(tidyr); library(patchwork)
2. return_comparison_long_viz <- nyse_group1_finite %>%
3. dplyr::select(Nominal_Return, Real_Return) %>%
4. dplyr::filter(!is.na(Nominal_Return), !is.na(Real_Return),
5. Nominal_Return > -1 & Nominal_Return < 2,
6. Real_Return > -1 & Real_Return < 2) %>%
7. tidyr::pivot_longer(cols = everything(),
8. names_to = "Return_Type",
9. values_to = "Return_Value")
10. plot_density <- ggplot(return_comparison_long_viz, aes(x = Return_Value, fill = Return_Type)) +
11. geom_density(alpha = 0.6) +
12. geom_vline(xintercept = 0, linetype = "dashed", color = "red") +
13. scale_x_continuous(labels = scales::percent_format(accuracy = 1)) +
14. labs(title = "Biểu đồ 1.3.1: So sánh phân bố lợi suất",
15. subtitle = "Phân bố mật độ Real_Return vs Nominal_Return (Lọc giá trị ngoài [-100%, +200%])",
16. x = "Lợi suất", y = "Mật độ ước lượng", fill = "Loại lợi suất:") +
17. theme_minimal(base_size = 11) +
18. theme(legend.position = "bottom")
19. core_limits <- quantile(nyse_group1_finite$Real_Return, c(0.005, 0.995), na.rm = TRUE)
20. set.seed(1)
21. sample_rr <- nyse_group1_finite %>%
22. dplyr::filter(!is.na(Real_Return)) %>%
23. dplyr::sample_n(min(2000, dplyr::n()))
24. plot_boxplot <- ggplot(nyse_group1_finite %>% dplyr::filter(!is.na(Real_Return)), aes(y = Real_Return, x = 1)) +
25. geom_boxplot(fill = "#D62728", color = "black", alpha = 0.7, outlier.shape = 1, outlier.size = 1) +
26. stat_summary(fun = median, geom = "point", size = 2, color = "black") +
27. geom_jitter(data = sample_rr, aes(x = 1, y = Real_Return), width = .05, alpha = .05, size = .6) +
28. scale_y_continuous(labels = scales::percent, limits = core_limits) +
29. labs(title = "Biểu đồ 1.3.2: Phân bố Box Plot lợi suất thực tế (Real_Return)",
30. subtitle = "Tập trung lõi (0.5%–99.5%) + chấm jitter minh hoạ phân tán",
31. y = "Lợi suất thực tế", x = "") +
32. coord_flip() +
33. theme_minimal(base_size = 11) +
34. theme(panel.grid.minor = element_blank(),
35. axis.text.y = element_blank())
36. plot_density + plot_boxplot| Dòng code | Giải thích |
|---|---|
| Dòng 1 | Tải các gói thư viện cần thiết: ggplot2
(tạo biểu đồ), scales (định dạng trục), dplyr,
tidyr (thao tác dữ liệu), và patchwork (kết
hợp biểu đồ). |
| Dòng 2-7 | Chuẩn bị dữ liệu cho Biểu đồ Mật độ (Density
Plot): * Dòng 3: Chọn hai cột Nominal_Return và Real_Return. * Dòng 4-5: Lọc ( filter) để loại
bỏ giá trị NA và các giá trị cực đoan ngoài khoảng [-1,
2] (tức là [-100%, +200%]) để tập trung vào phần lõi phân bố.
* Dòng 6-7: Sử dụng tidyr::pivot_longer() để chuyển dữ liệu từ định dạng ngang
(wide) sang định dạng dài (long), tạo hai cột
Return_Type và Return_Value, cần thiết cho
việc vẽ hai đường mật độ. |
| Dòng 8-15 | Tạo Biểu đồ Mật độ (Plot 1 -
plot_density): * Dòng 8: Tạo biểu đồ mật độ ( geom_density), phân biệt bằng màu
(fill = Return_Type). * Dòng 9: geom_density(alpha = 0.6): Vẽ đường mật độ với độ trong
suốt 60%. * Dòng 10: geom_vline: Vẽ
đường thẳng đứng đứt nét tại mốc 0 để đánh dấu điểm hòa
vốn. * Dòng 11: scale_x_continuous:
Định dạng trục X thành phần trăm (accuracy = 1%). * Dòng 12-14: Đặt tiêu đề, phụ đề, nhãn trục và định dạng chủ đề ( theme_minimal). |
| Dòng 16 | Xác định giới hạn Trục Y cho Box Plot:
Tính các điểm phân vị thứ 0.5% và 99.5% của Real_Return để
giới hạn trục Y cho Box Plot, giúp tập trung vào phần lớn dữ liệu. |
| Dòng 17-20 | Lấy mẫu cho Jitter Plot: Lấy mẫu ngẫu
nhiên (tối đa 2000 điểm) từ Real_Return để tạo các chấm
điểm minh họa sự phân tán trong Box Plot. |
| Dòng 21-29 | Tạo Biểu đồ Hộp (Plot 2 -
plot_boxplot): * Dòng 22: geom_boxplot: Vẽ Box Plot, tô màu và đặt kiểu cho outlier.
* Dòng 23: stat_summary(fun = median, ...): Thêm một chấm
đen lớn để đánh dấu vị trí Trung vị. * Dòng 24: geom_jitter: Thêm các chấm điểm minh họa (từ
mẫu) để thể hiện mật độ dữ liệu thực tế (alpha rất thấp để không làm rối
biểu đồ). * Dòng 25: scale_y_continuous: Định dạng trục Y thành phần trăm, giới
hạn theo core_limits đã tính. * Dòng 26-27: Đặt tiêu đề và phụ đề. * Dòng 28: coord_flip(): Xoay biểu đồ để trục Y (Lợi
suất) nằm ngang, giúp dễ đọc hơn. |
| Dòng 30 | Kết hợp biểu đồ: Sử dụng toán tử
+ của patchwork để hiển thị hai biểu đồ cạnh
nhau. |
A. Biểu đồ 1.3.1: So sánh phân bố lợi suất (Density Plot)
inflation):
Real_Return (Lợi suất Thực tế) dịch chuyển
hơi sang trái so với
Nominal_Return (Lợi suất Danh nghĩa).Real_Return nằm ở vùng lợi suất âm nhẹ, cho thấy sự
ăn mòn sức mua.B. Biểu đồ 1.3.2: Phân bố Box Plot lợi suất thực tế (Real_Return)
C. Kết luận tổng hợp từ trực quan hóa
Các biểu đồ trực quan hóa này là lời cảnh báo rõ ràng về tính biến động và sự phi chuẩn của lợi suất trong bộ dữ liệu này:
Lợi suất không chuẩn: Phân bố của Lợi suất Thực tế không tuân theo phân phối chuẩn; nó có đỉnh nhọn (cao) và đuôi dày chứa đầy các giá trị cực đoan.
Rủi ro cực đoan cao: Đặc tính đuôi dày cho thấy các sự kiện lãi/lỗ cực đoan xảy ra thường xuyên hơn so với kỳ vọng của các mô hình dựa trên phân phối chuẩn.
Hành động mô hình hóa: Bất kỳ mô hình dự báo nào sau này đều cần phải sử dụng các phương pháp thống kê phi tham số hoặc các kỹ thuật hồi quy robust (ít nhạy cảm với outlier) để tránh bị các giá trị cực đoan chi phối, hoặc phải áp dụng các bước biến đổi dữ liệu mạnh mẽ (ví dụ: Logarit) để cố gắng làm dịu tính lệch và đuôi dày.
1. library(ggplot2); library(scales); library(dplyr); library(patchwork); library(rlang)
2. sector_freq_viz <- nyse_group1_finite %>%
3. dplyr::filter(!is.na(sector)) %>%
4. dplyr::count(sector, name = "Count") %>%
5. dplyr::arrange(desc(Count))
6. top_lab <- sector_freq_viz$sector[which.max(sector_freq_viz$Count)]
7. plot_sector <- ggplot(sector_freq_viz, aes(x = reorder(sector, Count), y = Count, fill = sector)) +
8. geom_col(show.legend = FALSE) +
9. geom_text(aes(label = scales::comma(Count)), hjust = -0.1, size = 3) +
10. geom_hline(yintercept = median(sector_freq_viz$Count), linetype = "dotted") +
11. annotate("label", x = top_lab, y = max(sector_freq_viz$Count)*1.02, label = "Top sector", size = 3) +
12. coord_flip() +
13. scale_y_continuous(labels = scales::comma, expand = expansion(mult = c(0, .12))) +
14. scale_fill_brewer(palette = "Blues") +
15. labs(title = "Biểu đồ 1.3.3: Phân bổ giao dịch theo ngành",
16. subtitle = "Cột lật ngang + đường median + nhãn 'Top sector'",
17. x = NULL, y = "Số lượng giao dịch") +
18. theme_minimal(base_size = 11)
19. investment_freq_viz <- nyse_group1_finite %>%
20. dplyr::filter(!is.na(investment)) %>%
21. dplyr::count(investment, name = "Count") %>%
22. dplyr::mutate(Percent = Count / sum(Count))
23. plot_investment <- ggplot(investment_freq_viz, aes(x = investment, y = Count, fill = investment)) +
24. geom_col(show.legend = FALSE) +
25. geom_text(aes(label = scales::comma(Count)), vjust = -0.5, size = 3.5) +
26. geom_text(aes(label = scales::percent(Percent, accuracy = 0.1)),
27. vjust = 1.8, size = 3, color = "gray20") +
28. geom_hline(yintercept = median(investment_freq_viz$Count), linetype = "dashed") +
29. scale_x_discrete(drop = FALSE) +
30. scale_y_continuous(labels = scales::comma, expand = expansion(mult = c(0, .05))) +
31. scale_fill_brewer(palette = "Set3") +
32. labs(title = "Biểu đồ 1.3.4: Phân bổ giao dịch theo kết quả đầu tư",
33. subtitle = "Hiển thị đồng thời số lượng & tỷ lệ (%) + đường median nhóm",
34. x = "Kết quả đầu tư", y = "Số lượng giao dịch") +
35. theme_minimal(base_size = 11)
36. holding_freq_viz <- nyse_group1_finite %>%
37. dplyr::filter(!is.na(Holding_Period)) %>%
38. dplyr::count(Holding_Period, name = "Count")
39. plot_holding <- ggplot(holding_freq_viz,
40. aes(x = Holding_Period, y = Count, fill = Holding_Period)) +
41. geom_col(show.legend = FALSE) +
42. geom_text(aes(label = scales::comma(Count)), vjust = -0.5, size = 3.5) +
43. scale_y_continuous(labels = scales::comma, expand = expansion(mult = c(0, 0.05))) +
44. labs(title = "Biểu đồ 1.3.5: Phân bổ giao dịch theo thời gian nắm giữ",
45. subtitle = "Số lượng giao dịch theo chiến lược Short, Mid, Long-term",
46. x = "Thời gian nắm giữ", y = "Số lượng giao dịch") +
47. theme_minimal(base_size = 11)
48. (plot_sector + plot_investment) / plot_holding| Dòng code | Giải thích |
|---|---|
| Dòng 1 | Tải các gói cần thiết: ggplot2 (tạo biểu
đồ), scales (định dạng trục), dplyr,
patchwork (kết hợp biểu đồ), và rlang. |
| Dòng 2-9 | Chuẩn bị và tạo biểu đồ phân bổ ngành (Plot 1 -
plot_sector): * Dòng 2-4: Lọc NA, đếm số lượng ( Count) giao dịch theo
sector, và sắp xếp giảm dần. * Dòng 5: Xác định tên ngành có số lượng giao dịch cao nhất ( Top sector). * Dòng 6: Tạo biểu đồ cột ( geom_col). * Dòng 6: reorder(sector, Count): Sắp xếp các cột
theo giá trị Count tăng dần. * Dòng 7-8: Thêm nhãn số lượng ( geom_text) và đường trung
vị (geom_hline) cho số lượng giao dịch. * Dòng 9: Thêm nhãn “Top sector” minh họa. * Dòng 10: coord_flip(): Xoay biểu đồ
(cột ngang) để dễ đọc tên ngành. * Dòng 13-15: Đặt tiêu đề, nhãn trục và định dạng. |
| Dòng 16-29 | Chuẩn bị và tạo biểu đồ phân bổ kết quả đầu tư
(Plot 2 - plot_investment): * Dòng 16-19: Lọc NA, đếm số lượng, và tính tỷ lệ phần trăm ( Percent). * Dòng 20: Tạo biểu đồ cột. * Dòng 21-23: Thêm nhãn số lượng ( scales::comma) và nhãn tỷ lệ phần trăm
(scales::percent). * Dòng 24: Thêm đường trung vị. * Dòng 25: scale_x_discrete(drop = FALSE): Đảm bảo
hiển thị tất cả các cấp độ. * Dòng 28-29: Đặt tiêu đề và định dạng. |
| Dòng 30-38 | Chuẩn bị và tạo biểu đồ phân bổ thời gian nắm
giữ (Plot 3 - plot_holding): * Dòng 30-32: Lọc NA, đếm số lượng giao dịch theo Holding_Period. * Dòng 33-34: Tạo biểu đồ cột và thêm nhãn số lượng. |
| Dòng 39 | Kết hợp biểu đồ: Sử dụng
patchwork để hiển thị plot_sector và
plot_investment ở hàng trên, và plot_holding ở
hàng dưới. |
Nhận xét biểu đồ (Biểu đồ 1.3.3, 1.3.4, 1.3.5)
A. Biểu đồ 1.3.3: Phân bổ giao dịch theo ngành
B. Biểu đồ 1.3.4: Phân bổ giao dịch theo kết quả đầu tư
** C. Biểu đồ 1.3.5: Phân bổ giao dịch theo thời gian nắm giữ**
Tổng kết
Các biểu đồ tần suất xác nhận dữ liệu có tính đa dạng cao về ngành nghề và chiến lược nắm giữ, nhưng nổi cộm là vấn đề mất cân bằng lớp (lợi nhuận BAD chiếm đa số) và sự tập trung của hoạt động đầu tư vào một số ngành chính.
1. library(dplyr); library(ggplot2); library(scales); library(patchwork)
2. if (!exists("nyse_processed")) {
3. load("nyse_processed_groups.RData")
4. nyse_processed <- nyse_processed %>%
5. dplyr::mutate(dplyr::across(where(is.numeric), ~ ifelse(is.finite(.x), .x, NA_real_)))
6. nyse_group1_finite <- nyse_group1_data %>% dplyr::mutate(dplyr::across(where(is.numeric), ~ ifelse(is.finite(.x), .x, NA_real_)))
7. nyse_group2_finite <- nyse_group2_data %>% dplyr::mutate(dplyr::across(where(is.numeric), ~ ifelse(is.finite(.x), .x, NA_real_))) %>%
8. dplyr::filter(!is.na(Log_PE))}
9. if("Log_PE" %in% colnames(nyse_processed)) {
10. p1316 <- ggplot(nyse_processed %>% dplyr::filter(!is.na(Log_PE)), aes(x = Log_PE)) +
11. geom_histogram(aes(y = after_stat(density)), bins = 50,
12. fill = "#1F77B4", color = "white", alpha = 0.7) +
13. geom_density(color = "red", linewidth = 1) +
14. labs(title = "Biểu đồ 1.3.6: Phân bố Log(PE)",
15. subtitle = "Histogram và đường cong mật độ ứớc lượng",
16. x = "Giá trị Log(PE)", y = "Mật độ") +
17. theme_minimal(base_size = 11)} else {
18. p1316 <- ggplot() + labs(title = "Biến Log_PE không có")}
19. d_amt <- nyse_group1_finite %>% dplyr::filter(is.finite(amount) & amount > 0)
20. p1317 <- ggplot(d_amt, aes(x = amount)) +
21. geom_histogram(bins = 40, fill = "#FF7F0E", color = "white", alpha = 0.9) +
22. geom_vline(xintercept = 1000, linetype = "dashed", color = "blue", linewidth = 1) +
23. geom_rug(alpha = 0.05) +
24. scale_x_log10(labels = scales::label_comma()) +
25. labs(title = "Biểu đồ 1.3.7: Phân bố quy mô giao dịch (amount)",
26. subtitle = "Histogram trên thang log10, vạch tham chiếu $1,000",
27. x = "Quy mô đầu tư (USD, thang Log10)", y = "Số lượng giao dịch") +
28. theme_minimal(base_size = 11)
29. lr_viz_data <- nyse_group2_finite %>%
30. dplyr::filter(!is.na(Leverage_Ratio), Leverage_Ratio > -100, Leverage_Ratio < 100)
31. p1318 <- ggplot(lr_viz_data, aes(x = Leverage_Ratio)) +
32. geom_histogram(aes(y = after_stat(density)), bins = 50,
33. fill = "#9467BD", color = "white", alpha = 0.7) +
34. geom_density(color = "red", linewidth = 1) +
35. geom_vline(xintercept = 1, linetype = "dashed", color = "black", linewidth = 1) +
36. scale_x_continuous(limits = c(-10, 10)) +
37. labs(title = "Biểu đồ 1.3.8: Phân bố tỷ lệ đòn bẩy (Leverage_Ratio)",
38. subtitle = "Phân bố tỷ lệ đòn bẩy (ROE/ROA) - Tập trung quanh ngưỡng [-10, 10]",
39. x = "Tỷ lệ đòn bẩy tài chính (ROE/ROA)", y = "Mật độ") +
40. theme_minimal(base_size = 11)
41. d_hz <- nyse_group1_finite %>% dplyr::filter(is.finite(Horizon_Days))
42. hz_med <- median(d_hz$Horizon_Days, na.rm = TRUE)
43. hz_q <- quantile(d_hz$Horizon_Days, c(.25,.75), na.rm = TRUE)
44. hz_max <- quantile(d_hz$Horizon_Days, .995, na.rm = TRUE)
45. p1319 <- ggplot(d_hz, aes(x = Horizon_Days)) +
46. geom_histogram(aes(y = after_stat(density)), bins = 50, fill = "#E377C2", color = "white", alpha = 0.7) +
47. geom_density(linewidth = 1) +
48. geom_vline(xintercept = 180, linetype = "dotted") +
49. annotate("label", x = 180, y = Inf, label = ">180d (Long-term)", vjust = 1.2, size = 3) +
50. geom_vline(xintercept = hz_med, linetype = "dashed", color = "black") +
51. geom_vline(xintercept = hz_q[1], linetype = "dotted", color = "gray50") +
52. geom_vline(xintercept = hz_q[2], linetype = "dotted", color = "gray50") +
53. coord_cartesian(xlim = c(0, hz_max)) +
54. scale_x_continuous(breaks = scales::pretty_breaks()) +
55. labs(title = "Biểu đồ 1.3.9: Phân bố thời gian nắm giữ (Horizon_Days)",
56. subtitle = "Histogram + Density | Annotate mốc 180 ngày & Q1/Median/Q3 | Cắt biên 99.5%",
57. x = "Số ngày nắm giữ", y = "Mật độ") +
58. theme_minimal(base_size = 11)
59. (p1316 + p1317) / (p1318 + p1319)| Dòng code | Giải thích |
|---|---|
| Dòng 2-7 | Tải và chuẩn bị dữ liệu: * Dòng 2-5: Kiểm tra sự tồn tại của dữ liệu. Nếu không có, tải file RData và chuẩn hóa tất cả các
giá trị vô cực (Inf) và không xác định thành NA. * Dòng 6-7: Lọc các hàng có Log_PE là NA trong bộ dữ liệu
nhóm 2. |
| Dòng 8-15 | Biểu đồ 1.3.6: Phân bố
Log_PE: * Tạo biểu đồ histogram ( geom_histogram) với 50 bins và đường mật độ
(geom_density). * aes(y = after_stat(density)) đảm bảo trục Y biểu diễn mật
độ, cho phép hiển thị đường mật độ. |
| Dòng 16-24 | Biểu đồ 1.3.7: Phân bố
amount: * Lọc các giá trị amount
hợp lệ. * Tạo histogram. * geom_rug(alpha = 0.05): Vẽ các vạch nhỏ
sát trục X để hiển thị mật độ dữ liệu thô. * scale_x_log10(...): Chuyển trục X
sang thang logarit cơ số 10 để chuẩn hóa và làm giảm tính lệch
phải của biến amount gốc. |
| Dòng 25-36 | Biểu đồ 1.3.8: Phân bố
Leverage_Ratio: * Lọc các giá trị Leverage_Ratio hợp lệ và nằm trong khoảng \([-100, 100]\) để tránh các outlier
cực đoan. * Tạo histogram và đường mật độ. * scale_x_continuous(limits = c(-10, 10)):
Tập trung trực quan vào khoảng \([-10,
10]\) để xem rõ hơn phần lõi phân bố. * Thêm đường tham chiếu ( geom_vline) tại \(\text{Leverage} = 1\). |
| Dòng 37-49 | Biểu đồ 1.3.9: Phân bố
Horizon_Days: * Lọc các giá trị hợp lệ. * Tính Median và Tứ phân vị (Q1, Q3). * Tạo histogram và đường mật độ. * Thêm các đường tham chiếu ( geom_vline) tại mốc
180 ngày (ngưỡng Long-term) và các điểm Q1, Median, Q3.
* coord_cartesian(xlim = c(0, hz_max)): Cắt
biên trục X tại phân vị 99.5% (hz_max) để tập trung. |
| Dòng 50 | Kết hợp bốn biểu đồ (2 hàng x 2 cột) bằng
patchwork. |
Nhận xét biểu đồ (1.3.6, 1.3.7, 1.3.8, 1.3.9)
A. Phân bố định giá và tài chính (Log_PE, Leverage_Ratio)
Log_PE phù hợp cho các
mô hình dựa trên giả định chuẩn. Tuy nhiên, sự tồn tại của các đỉnh phụ
và đuôi kéo dài vẫn cho thấy các nhóm công ty được định giá rất cao (P/E
\(\gg 33\)).B. Phân bố hành vi đầu tư (amount, Horizon_Days)
C. Kết luận
Các biểu đồ này cung cấp sự trực quan hóa mạnh mẽ về tính đa dạng và
phi chuẩn của dữ liệu: Log_PE là một biến được chuẩn hóa
tốt, trong khi amount và Leverage_Ratio thể
hiện tính đa đỉnh và lệch mạnh. Sự mâu thuẫn Hành vi-Hiệu
suất (giao dịch ngắn hạn chiếm ưu thế nhưng lợi suất tốt nhất
lại đến từ dài hạn) là phát hiện đáng chú ý nhất, cần được khai thác
trong các phân tích về động lực thị trường.
Volatility_Buy và tỷ
suất Sharpe Sharpe_Ratio1. library(ggplot2); library(scales); library(dplyr); library(patchwork)
2. d_vol <- nyse_group1_finite %>% dplyr::filter(is.finite(Volatility_Buy))
3. v_med <- median(d_vol$Volatility_Buy, na.rm = TRUE)
4. v_q <- quantile(d_vol$Volatility_Buy, c(.25,.75), na.rm = TRUE)
5. plot_vol <- ggplot(d_vol, aes(x = Volatility_Buy)) +
6. geom_histogram(aes(y = after_stat(density)), bins = 50, fill = "#8C564B", color = "white", alpha = 0.7) +
7. geom_density(color = "darkred", linewidth = 1) +
8. geom_vline(xintercept = v_med, linetype = "dashed", color = "black") +
9. geom_vline(xintercept = v_q[1], linetype = "dotted", color = "gray50") +
10. geom_vline(xintercept = v_q[2], linetype = "dotted", color = "gray50") +
11. scale_x_continuous(breaks = scales::pretty_breaks(n = 5)) +
12. labs(title = "Biểu đồ 1.3.10: Phân bố độ biến động (Volatility_Buy)",
13. subtitle = "Histogram + Density | Annotate Q1/Median/Q3",
14. x = "Độ biến động khi mua", y = "Mật độ") +
15. theme_minimal(base_size = 11)
16. d_sh <- nyse_group1_finite %>% dplyr::filter(is.finite(Sharpe_Ratio))
17. s_med <- median(d_sh$Sharpe_Ratio, na.rm = TRUE)
18. s_q <- quantile(d_sh$Sharpe_Ratio, c(.25,.75), na.rm = TRUE)
19. plot_sharpe <- ggplot(d_sh, aes(x = Sharpe_Ratio)) +
20. geom_histogram(aes(y = after_stat(density)), bins = 50, fill = "#E377C2", color = "white", alpha = 0.7) +
21. geom_density(linewidth = 1) +
22. geom_vline(xintercept = s_med, linetype = "dashed", color = "black") +
23. geom_vline(xintercept = s_q[1], linetype = "dotted", color = "gray50") +
24. geom_vline(xintercept = s_q[2], linetype = "dotted", color = "gray50") +
25. labs(title = "Biểu đồ 1.3.11: Phân bố tỷ suất Sharpe (Sharpe_Ratio)",
26. subtitle = "Hình dạng gần trùng Volatility_Buy",
27. x = "Sharpe Ratio", y = "Mật độ") +
28. theme_minimal(base_size = 11)
29. plot_vol | plot_sharpe| Dòng code | Giải thích |
|---|---|
| Dòng 1 | Tải các gói cần thiết: ggplot2,
scales, dplyr, và patchwork. |
| Dòng 2-15 | Biểu đồ 1.3.10: Phân bố
Volatility_Buy (Rủi ro): * Dòng 2-4: Lọc giá trị NA/Inf, tính Median, và Tứ phân vị (Q1, Q3). * Dòng 5: Tạo biểu đồ histogram và đường mật độ. * Dòng 7-9: Thêm các đường tham chiếu ( geom_vline) tại các điểm Median và
Q1/Q3 để đánh dấu sự tập trung của dữ liệu. * Dòng 10-14: Định dạng trục X và đặt tiêu đề. |
| Dòng 16-29 | Biểu đồ 1.3.11: Phân bố
Sharpe_Ratio (Hiệu suất điều chỉnh rủi ro): * Dòng 16-18: Lọc giá trị NA/Inf, tính Median, và Tứ phân vị (Q1, Q3). * Dòng 19: Tạo biểu đồ histogram và đường mật độ. * Dòng 21-23: Thêm các đường tham chiếu tại các điểm Median và Q1/Q3. * Dòng 24-27: Đặt tiêu đề và định dạng. |
| Dòng 30 | Kết hợp hai biểu đồ (plot_vol và
plot_sharpe) cạnh nhau bằng patchwork. |
Nhận xét biểu đồ độ biến động và tỷ suất Sharpe (1.3.10 & 1.3.11)
A. Phân bố độ biến động (Volatility_Buy - Biểu
đồ 1.3.10)
B. Phân bố tỷ suất Sharpe (Sharpe_Ratio - Biểu
đồ 1.3.11)
Sharpe_Ratio có hình dạng gần như giống
hệt với phân bố của Volatility_Buy (đỉnh và các
điểm Q1, Median, Q3 trùng khớp), trực quan hóa mối tương quan
tuyến tính hoàn hảo (r = 1.000) đã được phát hiện trước
đó.Sharpe_Ratio trong bộ dữ liệu này là thừa
thãi (gần như là bản sao của Volatility_Buy).Sharpe_Ratio
cần được loại bỏ khỏi các mô hình đa biến để tránh vấn
đề đa cộng tuyến hoàn hảo và giữ lại
Volatility_Buy để đại diện cho yếu tố rủi ro.1. if (!exists("nyse_sample_viz")) {set.seed(42)
2. nyse_sample_viz <- nyse_group1_finite %>%
3. filter(!is.na(Real_Return), !is.na(Volatility_Buy), !is.na(Risk_Level)) %>%
4. sample_n(20000) }
5. if (!exists("cor_matrix_g1")) {
6. vars_g1_cor <- c("Real_Return", "Nominal_Return", "amount", "Horizon_Days",
7. "Volatility_Buy", "Sharpe_Ratio", "Expected_Return_Yr",
8. "inflation", "price_BUY", "price_SELL")
9. cor_data_input_g1 <- nyse_group1_finite %>% select(all_of(vars_g1_cor)) %>% na.omit()
10. cor_matrix_g1 <- cor(cor_data_input_g1, method = "pearson", use = "complete.obs")}
11. p1321 <- ggplot(nyse_sample_viz,
12. aes(x = Volatility_Buy, y = Real_Return, color = Risk_Level)) +
13. geom_point(alpha = 0.4, size = 1.5) +
14. geom_smooth(method = "lm", se = FALSE, color = "black", linetype = "dashed") +
15. scale_y_continuous(labels = scales::percent, limits = quantile(nyse_sample_viz$Real_Return, c(0.01, 0.99), na.rm = TRUE)) +
16. scale_color_brewer(palette = "Set1") +
17. labs(title = "1. Rủi ro (Volatility) vs Lợi suất (Real_Return)",
18. subtitle = "Đường hồi quy gần như đi ngang (r = 0.040)",
19. x = "Độ biến động giá", y = "Lợi suất Thực tế") +
20. theme_minimal(base_size = 10) + theme(legend.position = "none")
21. p1322 <- ggplot(nyse_sample_viz,
22. aes(x = Volatility_Buy, y = Sharpe_Ratio)) +
23. geom_point(alpha = 0.4, size = 1.5, color = "darkblue") +
24. geom_smooth(method = "lm", se = FALSE, color = "red", linetype = "solid", linewidth = 1.5) +
25. labs(title = "2. Volatility_Buy vs Sharpe_Ratio",
26. subtitle = "Tương quan tuyến tính hoàn hảo (r = 1.000)",
27. x = "Độ biến động giá", y = "Tỷ suất Sharpe") +
28. theme_minimal(base_size = 10)
29. gridExtra::grid.arrange(p1321, p1322, ncol = 2)| Dòng code | Giải thích |
|---|---|
| Dòng 1 | Tải các gói cần thiết: gridExtra (để sắp
xếp biểu đồ) và các gói thường dùng. |
| Dòng 2-5 | Chuẩn bị mẫu dữ liệu: * Kiểm tra sự tồn tại của mẫu ( nyse_sample_viz). Nếu chưa có, lấy
mẫu ngẫu nhiên 20,000 quan sát từ dữ liệu gốc sau khi
lọc NA/Inf, để tăng tốc độ vẽ và dễ nhìn hơn trên biểu đồ điểm. |
| Dòng 6-10 | Tải/Tính ma trận tương quan: Nếu
cor_matrix_g1 chưa tồn tại, thực hiện tính toán ma trận
tương quan (như đã giải thích ở Mục 1.2.2) để đảm bảo có thể tham chiếu
hệ số tương quan trong nhận xét. |
| Dòng 11-19 | Biểu đồ 1.3.2.1: Volatility_Buy vs
Real_Return: * geom_point(alpha = 0.4): Vẽ biểu đồ phân
tán (scatterplot), tô màu theo nhóm Risk_Level (biến phân
loại rủi ro). * geom_smooth(method = "lm", se = FALSE):
Thêm đường hồi quy tuyến tính (lm) mà
không có dải lỗi (se = FALSE) để trực quan hóa mối quan hệ
tuyến tính (Pearson r). * scale_y_continuous(...): Giới hạn trục Y
(Lợi suất) từ phân vị 1% đến 99% để loại bỏ các điểm cực đoan nhất, giúp
đường hồi quy dễ nhìn hơn. |
| Dòng 20-26 | Biểu đồ 1.3.2.2: Volatility_Buy vs
Sharpe_Ratio: * Vẽ biểu đồ phân tán giữa hai biến. * geom_smooth(method = "lm", ...): Thêm
đường hồi quy tuyến tính để trực quan hóa mối tương quan hoàn
hảo (r = 1.000). |
| Dòng 27 | gridExtra::grid.arrange(p1321, p1322, ncol = 2):
Sắp xếp hai biểu đồ đã tạo thành 2 cột. |
Nhận xét về rủi ro và lợi suất (Biểu đồ 1.3.2.1 & 1.3.2.2)
Volatility_Buy và Sharpe_Ratio.Sharpe_Ratio cần được loại bỏ khỏi mọi
phân tích mô hình hóa đa biến sau này.1. library(dplyr); library(ggplot2); library(scales); library(patchwork); library(tidyr)
2. if (!exists("nyse_group1_finite")) {
3. load("nyse_processed_groups.RData")
4. nyse_group1_finite <- nyse_group1_data %>%
5. dplyr::mutate(dplyr::across(where(is.numeric), ~ ifelse(is.finite(.x), .x, NA)),
6. Buy_Year = factor(format(Date_Buy, "%Y")),
7. Buy_Quarter = factor(quarters(Date_Buy))) }
8. nyse_group1_finite <- nyse_group1_finite %>%
9. dplyr::mutate(Holding_Period = dplyr::case_when(Horizon_Days <= 30 ~ "Short-term",
10. Horizon_Days <= 180 ~ "Mid-term",
11. Horizon_Days > 180 ~ "Long-term",
12. TRUE ~ NA_character_),
13. Holding_Period = factor(Holding_Period, levels = c("Short-term","Mid-term","Long-term")),
14. Amount_Level = factor(Amount_Level, levels = c("Small","Medium","Large")))
15. median_overall <- median(nyse_group1_finite$Real_Return, na.rm = TRUE)
16. sum_amt <- nyse_group1_finite %>%
17. dplyr::group_by(Amount_Level) %>%
18. dplyr::summarise(n = dplyr::n(),
19. mean = mean(Real_Return, na.rm = TRUE),
20. sd = sd(Real_Return, na.rm = TRUE),
21. se = sd / sqrt(n),
22. ci95 = 1.96 * se,
23. .groups = "drop")
24. p_amt <- ggplot(sum_amt, aes(Amount_Level, mean, fill = Amount_Level)) +
25. geom_col(width = 0.65, alpha = 0.9) +
26. geom_errorbar(aes(ymin = mean - ci95, ymax = mean + ci95), width = 0.18, size = 0.5) +
27. geom_text(aes(label = scales::percent(mean, accuracy = 0.1)), vjust = -0.6, size = 4.2) +
28. geom_hline(yintercept = median_overall, linetype = "dashed", color = "red") +
29. annotate("text", x = Inf, y = median_overall, hjust = 1.02, vjust = -0.6,
30. label = paste0("Median toàn mẫu: ", scales::percent(median_overall, accuracy = 0.1)),
31. color = "red", size = 3.4) +
32. scale_y_continuous(labels = scales::percent, expand = expansion(mult = c(0, 0.12))) +
33. labs(title = "3) Lợi suất thực tế TB theo quy mô giao dịch (kèm 95% CI)",
34. subtitle = "Ba nhóm quy mô có Mean gần như tương đương",
35. x = "Quy mô đầu tư", y = "Lợi suất thực tế trung bình") +
36. theme_minimal(base_size = 11) + theme(legend.position = "none", plot.title.position = "plot")
37. sum_hold <- nyse_group1_finite %>%
38. dplyr::filter(!is.na(Holding_Period)) %>%
39. dplyr::group_by(Holding_Period) %>%
40. dplyr::summarise(n = dplyr::n(),
41. mean = mean(Real_Return, na.rm = TRUE),
42. sd = sd(Real_Return, na.rm = TRUE),
43. se = sd / sqrt(n),
44. ci95 = 1.96 * se,
45. .groups = "drop")
46. p_hold <- ggplot(sum_hold, aes(Holding_Period, mean, fill = Holding_Period)) +
47. geom_col(width = 0.65, alpha = 0.9) +
48. geom_errorbar(aes(ymin = mean - ci95, ymax = mean + ci95), width = 0.18, size = 0.5) +
49. geom_text(aes(label = scales::percent(mean, accuracy = 0.1)), vjust = -0.6, size = 4.2) +
50. geom_hline(yintercept = median_overall, linetype = "dashed", color = "red") +
51. annotate("text", x = Inf, y = median_overall, hjust = 1.02, vjust = -0.6,
52. label = paste0("Median toàn mẫu: ", scales::percent(median_overall, accuracy = 0.1)),
53. color = "red", size = 3.4) +
54. scale_y_continuous(labels = scales::percent, expand = expansion(mult = c(0, 0.12))) +
55. labs(title = "4) Lợi suất thực tế TB theo thời gian nắm giữ (kèm 95% CI)",
56. subtitle = "Long-term có Mean cao nhất - ủng hộ chiến lược nắm giữ dài hạn",
57. x = "Thời gian nắm giữ", y = "Lợi suất thực tế trung bình") +
58. theme_minimal(base_size = 11) +
59. theme(legend.position = "none", plot.title.position = "plot")
60. set.seed(42)
61. dat_scatter <- nyse_group1_finite %>%
62. dplyr::filter(is.finite(Nominal_Return), is.finite(Real_Return)) %>%
63. dplyr::sample_n(min(80000, dplyr::n()))
64. r_val <- cor(dat_scatter$Nominal_Return, dat_scatter$Real_Return, use = "complete.obs")
65. lims_x <- quantile(dat_scatter$Nominal_Return, c(0.01, 0.99))
66. lims_y <- quantile(dat_scatter$Real_Return, c(0.01, 0.99))
67. p_scatter <- ggplot(dat_scatter, aes(Nominal_Return, Real_Return)) +
68. geom_bin2d(bins = 70) +
69. scale_fill_gradient(low = "#dbe9f6", high = "#08306b", guide = "none") +
70. geom_smooth(method = "lm", se = FALSE, color = "#ff7f0e", linewidth = 1.5, linetype = "solid") +
71. geom_abline(slope = 1, intercept = 0, color = "#d62728", linetype = "dashed", linewidth = 1.3) +
72. scale_x_continuous(labels = scales::percent, limits = lims_x) +
73. scale_y_continuous(labels = scales::percent, limits = lims_y) +
74. labs(title = "5) Lợi suất danh nghĩa vs lợi suất thực tế (tác động vĩ mô)",
75. subtitle = paste0(
76. "Đường cam: OLS; Đường đỏ đứt: y = x (không lạm phát)\n",
77. "Pearson r = ", round(r_val, 3),
78. " → Lạm phát làm giảm hiệu quả lợi suất thực tế"),
79. x = "Lợi suất danh nghĩa (Nominal_Return)", y = "Lợi suất thực tế (Real_Return)") +
80. theme_minimal(base_size = 11) +
81. theme(plot.title.position = "plot", panel.grid.minor = element_blank())
82. p_amt + p_hold + p_scatter| Dòng code | Giải thích |
|---|---|
| Dòng 1-9 | Tải và chuẩn bị dữ liệu: Đảm bảo dữ
liệu được tải, các giá trị vô cực được xử lý, và các biến phân loại
(Holding_Period, Amount_Level) được chuẩn hóa
và sắp xếp lại cấp độ (levels). |
| Dòng 10-14 | Tính thống kê tóm tắt theo
Amount_Level: * Nhóm dữ liệu theo Amount_Level. * Tính Mean (Trung bình), SD (Độ lệch chuẩn), SE (Sai số chuẩn = SD/\(\sqrt{n}\)), và CI 95% (Khoảng tin cậy = \(1.96 \times SE\)) của Real_Return cho từng nhóm. |
| Dòng 15-28 | Biểu đồ 3: So sánh Mean
Real_Return theo Amount_Level: * median_overall: Tính Trung vị toàn mẫu làm
đường tham chiếu (red dashed line). * geom_col: Vẽ cột biểu diễn Mean. * geom_errorbar: Thêm thanh lỗi biểu diễn
Khoảng tin cậy 95% (mean $\pm$ ci95). * Thêm nhãn giá trị Mean và nhãn Trung vị toàn mẫu. |
| Dòng 29-41 | Biểu đồ 4: So sánh Mean
Real_Return theo Holding_Period: * Thực hiện các bước tính toán và trực quan hóa tương tự như trên cho biến Holding_Period, hiển thị Mean và CI
95% của Real_Return theo 3 nhóm thời gian. |
| Dòng 42-57 | Biểu đồ 5: Tương quan
Nominal_Return vs Real_Return: * Dòng 42-43: Lấy mẫu nhỏ hơn ( sample_n) để
vẽ biểu đồ phân tán. * Dòng 44: Tính hệ số tương quan Pearson ( r_val). * Dòng 48: geom_bin2d(bins = 70): Vẽ biểu đồ phân tán
dưới dạng các ô vuông (bin) 2D, tô màu theo mật độ điểm, giúp xử lý tập
dữ liệu lớn. * Dòng 50: geom_smooth(method = "lm", ...): Thêm
đường hồi quy tuyến tính (OLS). * Dòng 51: geom_abline(slope = 1, intercept = 0, ...):
Thêm đường tham chiếu y = x (tình huống không có lạm
phát). * Dòng 52-53: Giới hạn trục X/Y từ phân vị 1% đến 99% để tập trung vào dữ liệu lõi. |
| Dòng 58 | Kết hợp ba biểu đồ
(p_amt + p_hold + p_scatter) trên cùng một hàng bằng
patchwork. |
Nhận xét & Phân tích kết quả
Biểu đồ 3 (Amount_Level): Mean của cả ba nhóm Small, Medium, Large là gần như tương đương (\(\approx 8.5\% - 8.8\%\)). Các khoảng tin cậy 95% chồng lấn gần hoàn toàn. Kết luận: Quy mô giao dịch không ảnh hưởng đáng kể đến lợi suất trung bình, xác nhận kết quả kiểm định ANOVA (\(p \ge 0.05\)).
Biểu đồ 4 (Holding_Period): Mean khác biệt rõ rệt và có thứ tự: Long-term (18.9%) > Mid-term (4.0%) > Short-term (1.8%). Khoảng tin cậy 95% không chồng lấn giữa các nhóm. Kết luận: Chiến lược nắm giữ dài hạn mang lại hiệu quả đầu tư cao hơn đáng kể, ủng hộ mạnh mẽ cho chiến lược “Buy & Hold” trong bộ dữ liệu này.
Biểu đồ 5 (Nominal vs Real Return):
inflation).Tổng kết: Hiệu quả đầu tư được quyết định bởi
Chiến lược thời gian nắm giữ (dài hạn thắng lợi) và
Bối cảnh vĩ mô (lạm phát làm giảm lợi suất thực tế),
trong khi Quy mô vốn cá nhân (amount)
không phải là yếu tố then chốt.
1. get_lower_tri <- function(cormat){
2. cormat[upper.tri(cormat)] <- NA
3. return(cormat)}
4. lower_tri <- get_lower_tri(cor_matrix_g1)
5. melted_cor_g1_lower <- reshape2::melt(lower_tri, na.rm = TRUE) %>%
6. mutate(value = round(value, 2))
7. # Biểu đồ 6: Heatmap Nửa dưới
8. p1326 <- ggplot(melted_cor_g1_lower, aes(Var2, Var1, fill = value)) +
9. geom_tile(color = "white") +
10. scale_fill_gradient2(low = "blue", high = "red", mid = "white",
11. midpoint = 0, limit = c(-1,1), name="Tương quan\nPearson (r)") +
12. geom_text(aes(label = value), color = "black", size = 3) +
13. coord_fixed() +
14. labs(title = "6. Heatmap Ma trận tương quan",
15. subtitle = "Mối liên hệ mạnh nhất: Real_Return/inflation (r=-0.80) & Volatility/Sharpe (r=1.00)",
16. x = "Biến số", y = "Biến số") +
17. theme_minimal(base_size = 11) +
18. theme(axis.text.x = element_text(angle = 45, vjust = 1, size = 9, hjust = 1),
19. axis.text.y = element_text(size = 9))
20. print(p1326)| Dòng code | Giải thích |
|---|---|
| Dòng 1-3 | Định nghĩa hàm
get_lower_tri: Hàm này dùng để loại
bỏ (gán NA) các giá trị ở nửa trên của ma trận tương quan
(upper.tri(cormat)) để chỉ giữ lại nửa dưới và đường chéo,
tránh sự lặp lại. |
| Dòng 4 | Áp dụng hàm get_lower_tri cho
cor_matrix_g1 (ma trận tương quan đã tính trước). |
| Dòng 5-6 | Chuyển đổi dạng dài và làm tròn: * Sử dụng reshape2::melt() để chuyển ma trận nửa dưới thành
định dạng dài. * Làm tròn giá trị tương quan ( value)
đến 2 chữ số thập phân. |
| Dòng 7-16 | Tạo Biểu đồ 6 (Heatmap Nửa dưới -
p1326): * geom_tile(color = "white"): Vẽ các ô
vuông, tô màu dựa trên giá trị tương quan. * scale_fill_gradient2(...): Thiết lập thang
màu chuyển tiếp xanh-trắng-đỏ (low-mid-high) với điểm
giữa (midpoint) tại 0, cho phép trực quan hóa mối tương
quan dương (đỏ) và âm (xanh) một cách hiệu quả. * geom_text: Thêm giá trị tương quan (\(\text{r}\)) vào giữa mỗi ô vuông. * coord_fixed(): Đảm bảo các ô vuông có hình
dạng hoàn hảo (tỷ lệ 1:1). * Dòng 13-16: Đặt tiêu đề và định dạng trục X (xoay 45 độ) để tránh chồng lấn. |
| Dòng 17 | Hiển thị biểu đồ. |
Nhận xét ma trận tương quan (Biểu đồ 1.3.2.3)
Biểu đồ Heatmap nửa dưới trực quan hóa cường độ và chiều hướng của mối quan hệ tuyến tính giữa các biến định lượng:
inflation là yếu tố tuyến tính mạnh nhất quyết định Lợi
suất Thực tế.Real_Return và các biến hành
vi/rủi ro khác (amount, Horizon_Days,
Volatility_Buy) đều có màu trắng hoặc rất
nhạt (gần 0).Real_Return)
theo yếu tố cấu trúc & hành vi1. create_box_plot <- function(data, x_var, y_var, title, subtitle, limits_y) {
2. p <- ggplot(data, aes(x = .data[[x_var]], y = .data[[y_var]], fill = .data[[x_var]])) +
3. geom_boxplot(width = 0.5, color = "black", alpha = 0.7, outlier.alpha = 0.1) +
4. stat_summary(fun = "mean", geom = "point", shape = 23, size = 3, fill = "white") +
5. scale_y_continuous(labels = scales::percent, limits = limits_y) +
6. labs(title = title, subtitle = subtitle, x = "", y = "Lợi suất thực tế (Real_Return)") +
7. theme_minimal(base_size = 10) + theme(legend.position = "none")
8. return(p)}
9. rr_limits <- quantile(nyse_group1_finite$Real_Return, c(0.01, 0.99), na.rm = TRUE)
10. p1331_box <- create_box_plot(nyse_group1_finite, "sector", "Real_Return",
11. "1. Lợi suất thực tế theo ngành (Sector)", "TECH có mean cao nhất", rr_limits)
12. p1332_box <- create_box_plot(nyse_group1_finite, "Holding_Period", "Real_Return",
13. "2. Lợi suất thực tế theo thời gian nắm giữ", "Long-term có Mean/Median cao nhất", rr_limits)
14. p1333_box <- create_box_plot(nyse_group1_finite, "Risk_Level", "Real_Return",
15. "3. Lợi suất thực tế theo mức độ rủi ro", "Nhóm Medium có Mean cao nhất (Bất thường)", rr_limits)
16. p1334_box <- create_box_plot(nyse_group1_finite, "Amount_Level", "Real_Return",
17. "4. Lợi suất thực tế theo quy mô giao dịch", "Không có sự khác biệt về Mean/Median (p=0.678)", rr_limits)
18. gridExtra::grid.arrange(p1331_box, p1332_box, p1333_box, p1334_box, ncol = 2)| Dòng code | Giải thích |
|---|---|
| Dòng 1 | Tải các gói cần thiết: gridExtra và các
gói thường dùng. |
| Dòng 2-10 | Định nghĩa hàm
create_box_plot: Hàm tùy chỉnh để tạo nhanh biểu
đồ hộp so sánh. * geom_boxplot: Vẽ
Box Plot, làm trong suốt nhẹ (alpha = 0.7) và làm mờ các
outlier (outlier.alpha = 0.1) để tập trung vào phần lõi
phân phối. * stat_summary(fun = "mean", ...): Thêm
hình thoi (shape=23) để đánh dấu vị trí Giá trị
Trung bình (Mean), giúp so sánh với Trung vị (đường kẻ trong
hộp). * scale_y_continuous(...): Định
dạng trục Y thành phần trăm và giới hạn trục theo ngưỡng \(1\% - 99\%\) (limits_y) để
loại bỏ các outlier cực đoan. |
| Dòng 11 | Xác định giới hạn trục Y (rr_limits) từ
phân vị \(1\%\) đến \(99\%\) của Real_Return. |
| Dòng 12-16 | Tạo 4 Biểu đồ Hộp: Gọi hàm
create_box_plot 4 lần để trực quan hóa
Real_Return so với các biến phân loại: sector,
Holding_Period, Risk_Level, và
Amount_Level. |
| Dòng 17 | Sử dụng gridExtra::grid.arrange để sắp xếp
4 biểu đồ đã tạo thành 2 hàng x 2 cột. |
Nhận xét: So sánh lợi suất thực tế theo yếu tố cấu trúc & hành vi (Box Plots)
sector - Biểu đồ 1):
Holding_Period - Biểu
đồ 2):
Long-term cũng hẹp hơn, gợi ý lợi suất ổn định hơn.Risk_Level - Biểu đồ
3):
Amount_Level - Biểu đồ
4):
Tổng kết chung: Các kết quả trực quan củng cố các kiểm định thống kê: Hành vi (Thời gian Nắm giữ) và Yếu tố cấu trúc (Ngành) là các biến phân biệt hiệu suất đầu tư quan trọng, trong khi Quy mô giao dịch không có tác động đáng kể.
1. if (!exists("nyse_group1_finite")) {
2. load("nyse_processed_groups.RData")
3. nyse_group1_finite <- nyse_group1_data %>%
4. dplyr::mutate(dplyr::across(where(is.numeric), ~ ifelse(is.finite(.x), .x, NA_real_)))}
5. if (!("Holding_Period" %in% names(nyse_group1_finite)) ||
6. all(is.na(nyse_group1_finite$Holding_Period))) {
7. nyse_group1_finite <- nyse_group1_finite %>%
8. dplyr::mutate(
9. Holding_Period = dplyr::case_when(
10. !is.na(Horizon_Days) & Horizon_Days <= 30 ~ "Short-term",
11. !is.na(Horizon_Days) & Horizon_Days <= 180 ~ "Mid-term",
12. !is.na(Horizon_Days) & Horizon_Days > 180 ~ "Long-term",
13. TRUE ~ NA_character_))}
14. nyse_group1_finite <- nyse_group1_finite %>%
15. dplyr::mutate(
16. Holding_Period = factor(Holding_Period, levels = c("Short-term","Mid-term","Long-term")),
17. Risk_Level = factor(Risk_Level, levels = c("Low","Medium","High")),
18. investment = factor(investment, levels = c("GOOD","BAD")))
19. risk_inv_prop <- nyse_group1_finite %>%
20. dplyr::filter(!is.na(Risk_Level), !is.na(investment)) %>%
21. dplyr::count(Risk_Level, investment, name = "n") %>%
22. dplyr::group_by(Risk_Level) %>%
23. dplyr::mutate(Proportion = n / sum(n)) %>%
24. dplyr::ungroup()
25. hp_inv_prop <- nyse_group1_finite %>%
26. dplyr::filter(!is.na(Holding_Period), !is.na(investment)) %>%
27. dplyr::count(Holding_Period, investment, name = "n") %>%
28. dplyr::group_by(Holding_Period) %>%
29. dplyr::mutate(Proportion = n / sum(n)) %>%
30. dplyr::ungroup()
31. cat("Kiểm tra phân bổ Holding_Period (sử dụng factor level mới):\n")Kiểm tra phân bổ Holding_Period (sử dụng factor level mới):
Short-term Mid-term Long-term
135001 122731 147526
Bảng tần suất tương đối (Holding_Period vs investment):
# A tibble: 6 × 4
Holding_Period investment n Proportion
<fct> <fct> <int> <dbl>
1 Short-term GOOD 40640 0.301
2 Short-term BAD 94361 0.699
3 Mid-term GOOD 42577 0.347
4 Mid-term BAD 80154 0.653
5 Long-term GOOD 57601 0.390
6 Long-term BAD 89925 0.610
1. library(dplyr); library(ggplot2); library(forcats); library(gridExtra)
2. nyse_group1_finite <- nyse_group1_finite %>%
3. mutate(
4. Risk_Level = fct_relevel(factor(Risk_Level), c("Low","Medium","High")),
5. Holding_Period = case_when(
6. grepl("^Short-term", as.character(Holding_Period), ignore.case = TRUE) ~ "Short-term",
7. grepl("^Mid-term", as.character(Holding_Period), ignore.case = TRUE) ~ "Mid-term",
8. grepl("^Long-term", as.character(Holding_Period), ignore.case = TRUE) ~ "Long-term",
9. TRUE ~ as.character(Holding_Period)),
10. Holding_Period = fct_relevel(factor(Holding_Period), c("Short-term","Mid-term","Long-term")),
11. investment = fct_relevel(factor(investment), c("GOOD","BAD")))
12. risk_inv_prop <- nyse_group1_finite %>%
13. filter(!is.na(Risk_Level), !is.na(investment)) %>%
14. count(Risk_Level, investment, name = "n") %>%
15. group_by(Risk_Level) %>%
16. mutate(Proportion = n / sum(n)) %>%
17. ungroup()
18. hp_inv_prop <- nyse_group1_finite %>%
19. filter(!is.na(Holding_Period), !is.na(investment)) %>%
20. count(Holding_Period, investment, name = "n") %>%
21. group_by(Holding_Period) %>%
22. mutate(Proportion = n / sum(n)) %>%
23. ungroup()
24. p1335 <- ggplot(nyse_group1_finite,
25. aes(x = sector, y = Volatility_Buy, fill = sector)) +
26. geom_boxplot(width = 0.5, color = "black", alpha = 0.7, outlier.alpha = 0.2) +
27. stat_summary(fun = "mean", geom = "point", shape = 23, size = 3, fill = "white") +
28. labs(title = "5. Độ biến động (Volatility) theo ngành",
29. subtitle = "AUTO cao nhất – FMCG thấp nhất",
30. x = "Ngành (Sector)", y = "Độ biến động (Volatility_Buy)") +
31. theme_minimal(base_size = 10) + theme(legend.position = "none")
32. p1339 <- ggplot(risk_inv_prop,
33. aes(x = Risk_Level, y = Proportion, fill = investment)) +
34. geom_col(position = "stack", width = 0.75) +
35. geom_text(aes(label = scales::percent(Proportion, accuracy = 0.1)),
36. position = position_stack(vjust = 0.5), size = 3) +
37. scale_y_continuous(labels = scales::percent) +
38. scale_fill_manual(values = c("GOOD" = "#2CA02C", "BAD" = "#D62728")) +
39. labs(title = "6. Tỷ lệ GOOD/BAD theo mức độ rủi ro",
40. subtitle = "Tỷ lệ GOOD gần như không đổi giữa Low–Medium–High",
41. x = "Mức độ rủi ro", y = "Tỷ lệ (%)") +
42. theme_minimal(base_size = 10) + theme(legend.position = "bottom")
43. p13310 <- ggplot(hp_inv_prop,
44. aes(x = Holding_Period, y = Proportion, fill = investment)) +
45. geom_col(position = "stack", width = 0.75) +
46. geom_text(aes(label = scales::percent(Proportion, accuracy = 0.1)),
47. position = position_stack(vjust = 0.5), size = 3) +
48. scale_y_continuous(labels = scales::percent) +
49. scale_fill_manual(values = c("GOOD" = "#2CA02C", "BAD" = "#D62728")) +
50. labs(title = "7. Tỷ lệ GOOD/BAD theo thời gian nắm giữ",
51. subtitle = "Long-term thường có tỷ lệ GOOD cao nhất",
52. x = "Thời gian nắm giữ", y = "Tỷ lệ (%)") +
53. theme_minimal(base_size = 10) + theme(legend.position = "bottom")
54. gridExtra::grid.arrange(p1335, p1339, p13310, ncol = 3)| Dòng code | Giải thích |
|---|---|
| Dòng 1-13 | Chuẩn hóa và sắp xếp cấp độ biến phân
loại: * Dòng 2-10: Khối lệnh đảm bảo dữ liệu nyse_group1_finite được tải và tạo lại biến
Holding_Period nếu nó bị thiếu hoặc không hợp lệ. * Dòng 11-13: Chuyển đổi các biến phân loại ( Holding_Period, Risk_Level,
investment) thành kiểu factor và sắp
xếp lại thứ tự các cấp độ (ví dụ: Short-term trước
Mid-term trước Long-term) để đảm bảo thứ tự
hiển thị chính xác trên biểu đồ. |
| Dòng 14-19 | Tính tỷ lệ Risk_Level vs
investment: * Lọc bỏ NA và đếm số lượng giao dịch ( n) cho từng tổ hợp (Risk_Level,
investment). * Nhóm theo cấp độ rủi ro ( group_by(Risk_Level)). * mutate(Proportion = n / sum(n)): Tính tỷ
lệ phần trăm (Proportion) của
GOOD/BAD trong mỗi nhóm
Risk_Level (tổng tỷ lệ mỗi nhóm Risk_Level bằng 1). |
| Dòng 20-25 | Tính Tỷ lệ Holding_Period vs
investment: Thực hiện tính toán tương tự để tìm tỷ
lệ GOOD/BAD trong mỗi nhóm thời gian
nắm giữ (Holding_Period). |
| Dòng 26-28 | In ra bảng tần suất của Holding_Period và
bảng kết quả tỷ lệ đã tính để kiểm tra. |
| Dòng code | Giải thích |
|---|---|
| Dòng 1-14 | Chuẩn bị dữ liệu và sắp xếp lại cấp
độ: * Tải các gói thư viện cần thiết. * Mã này thực hiện lại việc sắp xếp cấp độ (re-leveling) của các biến Risk_Level, Holding_Period, và
investment (đảm bảo hiển thị đúng thứ tự trong biểu đồ),
tương tự như khối code trước. |
| Dòng 15-28 | Tính toán tỷ lệ phân bổ: Khối lệnh này
tính toán lại dữ liệu tỷ lệ risk_inv_prop
và hp_inv_prop, tương tự như khối code trước, để đảm bảo
biến đã được xử lý ở bước trên được sử dụng. |
| Dòng 29-37 | Biểu đồ 5: Volatility_Buy theo
Ngành (sector): * Vẽ biểu đồ hộp ( geom_boxplot) so sánh phân bố Độ biến động giữa các ngành.
* stat_summary(fun = "mean", ...):
Đánh dấu vị trí Mean (hình thoi trắng). |
| Dòng 38-49 | Biểu đồ 6: Tỷ lệ GOOD/BAD theo
Risk_Level: * Sử dụng dữ liệu tỷ lệ đã tính ( risk_inv_prop). * geom_col(position = "stack"): Vẽ biểu đồ
cột xếp chồng. * position_stack(vjust = 0.5): Đặt nhãn phần
trăm vào giữa mỗi phần của cột. * Sử dụng scale_fill_manual để chỉ định màu xanh/đỏ cho
GOOD/BAD. |
| Dòng 50-61 | Biểu đồ 7: Tỷ lệ GOOD/BAD theo
Holding_Period: Thực hiện các bước trực quan hóa
tương tự Biểu đồ 6 để hiển thị tỷ lệ GOOD/BAD theo nhóm
thời gian nắm giữ. |
| Dòng 62 | Sắp xếp 3 biểu đồ (Box Plot và 2 Biểu đồ Tỷ lệ) thành 3
cột bằng gridExtra::grid.arrange. |
Nhận xét & Phân tích kết quả
Volatility_Buy) trung bình cao nhất (hình thoi
trắng cao nhất), trong khi FMCG thấp nhất.Long-term \(\to\) Mean Real_Return cao
nhất.Tổng hợp: Phân tích trực quan sâu hơn xác nhận các yếu tố cấu trúc và hành vi ảnh hưởng đến hiệu suất đầu tư: Ngành quyết định rủi ro; Thời gian nắm giữ (Long-term) là chiến lược tối ưu để tăng cả Mean Lợi suất và Tỷ lệ thành công; trong khi mối quan hệ Rủi ro-Lợi suất là yếu.
1. rr_year_trend <- nyse_group1_finite %>%
2. group_by(Buy_Year) %>%
3. summarise(Mean_Return = mean(Real_Return, na.rm = TRUE), .groups = 'drop') %>%
4. filter(!is.na(Buy_Year))
5. rr_quarter_trend <- nyse_group1_finite %>%
6. filter(!is.na(Buy_Quarter)) %>%
7. group_by(Buy_Quarter) %>%
8. summarise(Mean_Return = mean(Real_Return, na.rm = TRUE), .groups = 'drop') %>%
9. mutate(Quarter_Order = factor(Buy_Quarter, levels = unique(Buy_Quarter[order(Buy_Quarter)])))
10. # Biểu đồ 1: Mean(Real_Return) theo Năm
11. p1341 <- ggplot(rr_year_trend, aes(x = factor(Buy_Year), y = Mean_Return, group = 1)) +
12. geom_line(color = "#1F77B4", linewidth = 1.2) +
13. geom_point(color = "#1F77B4", size = 3) +
14. geom_text(aes(label = scales::percent(Mean_Return, accuracy = 0.1)), vjust = -1, size = 3) +
15. scale_y_continuous(labels = scales::percent) +
16. labs(title = "1. Xu hướng lợi suất thực tế trung bình theo năm mua",
17. subtitle = "Minh họa tác động của chu kỳ kinh tế vĩ mô",
18. x = "Năm mua", y = "Lợi suất TB") + theme_minimal(base_size = 10)
19. # Biểu đồ 2: Mean(Real_Return) theo Quý
20. p1342 <- ggplot(rr_quarter_trend, aes(x = Quarter_Order, y = Mean_Return, group = 1)) +
21. geom_line(color = "#D62728", linewidth = 1.2) +
22. geom_point(color = "#D62728", size = 3) +
23. scale_y_continuous(labels = scales::percent) +
24. labs(title = "2. Xu hướng lợi suất thực tế trung bình theo quý mua",
25. subtitle = "Phân tích chi tiết sự biến động theo thời gian",
26. x = "Quý mua", y = "Lợi suất TB") +
27. theme_minimal(base_size = 10) +
28. theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust = 1, size = 7))
29. gridExtra::grid.arrange(p1341, p1342, ncol = 2)| Dòng code | Giải thích |
|---|---|
| Dòng 1-5 | Tính xu hướng lợi suất theo năm: * group_by(Buy_Year): Nhóm dữ liệu theo
Năm Mua. * summarise(Mean_Return = mean(Real_Return, ...)):
Tính Lợi suất Thực tế Trung bình cho từng năm. * filter(!is.na(Buy_Year)): Loại bỏ các năm
không xác định. |
| Dòng 6-10 | Tính xu hướng lợi suất theo quý: * Nhóm theo Quý Mua ( Buy_Quarter). * Tính Lợi suất Thực tế Trung bình cho từng quý. * mutate(Quarter_Order = factor(Buy_Quarter, ...)):
Tạo biến Quarter_Order (Factor) với các cấp độ được sắp xếp
đúng theo thứ tự thời gian (ví dụ: 2013.Q1, 2013.Q2,…)
để đảm bảo đường biểu diễn không bị sai lệch. |
| Dòng 11-19 | Biểu đồ 1: Xu hướng theo năm mua
(p1341): * geom_line và
geom_point: Vẽ biểu đồ đường và điểm. * geom_text: Thêm nhãn giá trị lợi suất
trung bình bằng phần trăm lên trên điểm. |
| Dòng 20-30 | Biểu đồ 2: Xu hướng theo quý mua
(p1342): * Vẽ biểu đồ đường và điểm dựa trên thứ tự quý đã được sắp xếp ( Quarter_Order). * theme(...): Xoay nhãn trục X 90 độ để
tránh chồng lấn do số lượng quý lớn. |
| Dòng 31 | Sắp xếp hai biểu đồ (Năm và Quý) cạnh nhau bằng
gridExtra::grid.arrange. |
Nhận xét – Xu hướng lợi suất thực tế theo thời gian
Tổng kết: Các kết quả trực quan này xác nhận vai trò mạnh mẽ của yếu tố thời gian (Market Timing) trong việc quyết định hiệu suất, cho thấy lợi suất của nhà đầu tư bị chi phối bởi bối cảnh kinh tế vĩ mô hơn là chỉ bởi các yếu tố cá nhân.
1. rr_quarter_sector_trend <- nyse_group1_finite %>%
2. dplyr::filter(!is.na(Buy_Quarter), !is.na(sector)) %>%
3. dplyr::group_by(sector, Buy_Quarter) %>%
4. dplyr::summarise(Mean_Return = mean(Real_Return, na.rm = TRUE), .groups = 'drop') %>%
5. dplyr::mutate(Quarter_Order = factor(Buy_Quarter, levels = unique(Buy_Quarter[order(Buy_Quarter)])))
6. sector_order <- rr_quarter_sector_trend %>%
7. dplyr::group_by(sector) %>%
8. dplyr::summarise(Mean_All = mean(Mean_Return, na.rm = TRUE), .groups = "drop") %>%
9. dplyr::arrange(dplyr::desc(Mean_All)) %>% dplyr::pull(sector)
10. rr_quarter_sector_trend$sector <- factor(rr_quarter_sector_trend$sector, levels = sector_order)
11. p1343 <- ggplot2::ggplot(rr_quarter_sector_trend,
12. ggplot2::aes(x = Quarter_Order, y = Mean_Return, group = sector, color = sector)) +
13. ggplot2::geom_line(linewidth = 0.8) +
14. ggplot2::geom_point(size = 1.8) +
15. ggplot2::scale_y_continuous(labels = scales::percent) +
16. ggplot2::facet_wrap(~ sector, scales = "free_y", ncol = 2) +
17. ggplot2::labs(title = "3. Xu hướng lợi suất thực tế TB theo quý — phân tách theo ngành",
18. subtitle = "Facet: trục Y tự do; thứ tự facet từ Mean toàn kỳ cao → thấp",
19. x = "Quý mua", y = "Lợi suất thực tế TB") +
20. ggplot2::theme_minimal(base_size = 11) +
21. ggplot2::theme(axis.text.x = ggplot2::element_text(angle = 90, vjust = 0.5, hjust = 1, size = 7),
22. legend.position = "none")
23. print(p1343)| Dòng code | Giải thích |
|---|---|
| Dòng 1-6 | Tính xu hướng theo ngành và quý: * Dòng 2-4: Lọc NA, nhóm dữ liệu theo sector và Buy_Quarter. * summarise(Mean_Return = mean(Real_Return, ...)):
Tính Lợi suất thực tế trung bình cho từng tổ hợp
Ngành-Quý. * mutate(Quarter_Order = ...): Tạo biến thứ
tự quý đã được sắp xếp, đảm bảo hiển thị đúng trình tự thời gian. |
| Dòng 7-10 | Sắp xếp thứ tự Facet: * Tính Mean Lợi suất trung bình toàn kỳ cho từng sector. * Sắp xếp các ngành theo thứ tự giảm dần của Mean đó ( sector_order). * Dòng 10: Áp dụng thứ tự đã sắp xếp cho biến sector trong bộ dữ liệu
chính, đảm bảo các biểu đồ nhỏ (facet) được hiển thị theo thứ tự hiệu
suất trung bình (cao \(\to\)
thấp). |
| Dòng 11-20 | Tạo biểu đồ Facet
(p1343): * aes(..., group = sector, color = sector):
Định nghĩa biểu đồ đường với màu sắc và nhóm theo từng ngành. * geom_line và geom_point: Vẽ
đường và điểm biểu diễn xu hướng. * facet_wrap(~ sector, scales = "free_y", ncol = 2):
Tạo nhiều biểu đồ nhỏ (facet) cho từng ngành. * scales = "free_y": Cho phép trục Y của mỗi
biểu đồ nhỏ có thang đo khác nhau (tự do) để thể hiện rõ hơn biên độ lợi
suất riêng của từng ngành. * Dòng 17-20: Đặt tiêu đề và xoay nhãn trục X 90 độ. |
Nhận xét – Xu hướng lợi suất thực tế trung bình theo quý (Phân tách theo ngành)
Tính đồng pha vĩ mô: Tất cả các ngành đều thể hiện chu kỳ lợi suất đồng pha, với đỉnh tăng trưởng đồng loạt rơi vào giai đoạn 2014.Q4–2015.Q2. Điều này chứng minh sự chi phối của yếu tố vĩ mô toàn thị trường lên lợi suất hơn là đặc điểm riêng của ngành.
Phân hóa biên độ và ổn định:
Kết luận: Lợi suất đầu tư biến động đồng pha theo chu kỳ vĩ mô, nhưng mức độ phản ứng và rủi ro lại phụ thuộc vào bản chất ngành nghề (nhóm tăng trưởng lợi nhuận/rủi ro cao hơn, nhóm phòng thủ ổn định hơn).
Tôi đã điều chỉnh lại cấu trúc và nội dung theo yêu cầu của bạn, sắp xếp lại các mục thành Kết luận chung, Hạn chế, và Đề xuất một cách hợp lý và chuyên nghiệp.
Phân tích thống kê mô tả và trực quan của Nhóm biến 1 (Giao dịch, Hành vi và Hiệu suất) trên 405,258 giao dịch giai đoạn 2013–2018 đã làm rõ các yếu tố cốt lõi ảnh hưởng đến hiệu quả đầu tư trên sàn NYSE:
Amount_Level) không có ảnh hưởng ý nghĩa thống
kê đến Lợi suất trung bình, gợi ý tính độc lập của yếu tố vốn
so với hiệu suất.Real_Return và inflation xác nhận tác
động tiêu cực của yếu tố vĩ mô này lên sức mua của lợi nhuận.Volatility_Buy và Sharpe_Ratio, xác nhận sự dư
thừa thông tin.| Nhóm hạn chế | Nội dung chi tiết | Hàm ý đối với Mô hình hóa |
|---|---|---|
| Phạm vi dữ liệu | Giai đoạn 2013–2018 chưa phản ánh đầy đủ chu kỳ kinh tế và không bao gồm các biến cố lớn (COVID-19, v.v.). | Giới hạn tính tổng quát. |
| Thiếu biến danh mục | Chỉ sử dụng cổ phiếu NYSE; thiếu các chỉ số so sánh (benchmark) như S&P 500 hay các tài sản khác. | Thiếu tính so sánh tuyệt đối. |
| Đặc điểm phân phối | Các biến lợi suất và quy mô có phân phối không chuẩn, tồn tại outlier. | Cần xử lý/chuẩn hóa dữ liệu. |
| Giới hạn phương pháp | Kết quả dừng lại ở thống kê mô tả, kiểm định trung bình (ANOVA) và tương quan Pearson (tuyến tính). | Chưa xác định được quan hệ nhân quả và phi tuyến tính. |
Để chuyển từ phân tích mô tả sang mô hình dự báo và định lượng mối quan hệ, các bước sau được đề xuất:
Sharpe_Ratio để tránh đa cộng tuyến hoàn
hảo.Real_Return, Nominal_Return,
amount và Leverage_Ratio, hoặc sử dụng các
biến đổi Logarit để giảm độ lệch, làm dữ liệu gần chuẩn
hơn cho các mô hình hồi quy.Log_PE, PB,
PS) và hiệu quả tài chính (ROE,
ROA, ESG) để tìm các yếu tố dự báo mới.investment): Sử dụng Hồi quy
Logistic để xác định các yếu tố (Ngành, Thời gian nắm giữ,
Log(PE), Volatility) dự báo xác suất khoản đầu tư thành công
(investment = GOOD).Real_Return): Sử dụng
Hồi quy tuyến tính đa biến hoặc Hồi quy
Robust để mô hình hóa và định lượng tác động của các yếu tố
hành vi, vĩ mô và công ty lên lợi suất thực tế.Mục tiêu phân tích
Mục tiêu chính của phân tích này là đánh giá sức khỏe tài chính và hiệu quả hoạt động của Tổng Công ty Khí Việt Nam (GAS) trong giai đoạn 11 năm (2014-2024). Mục tiêu thứ cấp là xác định mối quan hệ giữa các chỉ số tài chính nội tại (cơ bản) của công ty với giá trị thị trường (giá cổ phiếu) của GAS.
Nguồn dữ liệu và Phạm vi
| Đặc điểm | Mô tả |
|---|---|
| Nguồn dữ liệu | Tài chính cơ bản: Báo cáo tài chính hợp nhất đã được kiểm toán (Bảng Cân đối Kế toán, KQKD, Lưu Chuyển Tiền Tệ) hàng năm của GAS. Thị trường: Giá đóng cửa cổ phiếu GAS tại ngày chốt báo cáo (31/12 hàng năm). |
| Đơn vị quan sát | Dữ liệu được tổng hợp theo Năm. |
| Phạm vi thời gian | 11 năm, từ năm 2014 đến 2024. |
GAS_data_final)Bộ dữ liệu bao gồm 12 biến gốc và phái sinh, được phân loại thành 3 nhóm chính:
Nam).DoanhThu,
LoiNhuanSauThue, TongTaiSan,
NoPhaiTra, VonChuSoHuu,
Gia_DongCua_CuoiNam).ROE_Nam, ROA_Nam,
TySoNo_Tren_VCSH, DoanhThu_TangTruong_YoY,
LoiNhuan_TangTruong_YoY).Bộ dữ liệu được mở rộng thêm các chỉ số phái sinh và chia thành hai nhóm phân tích chuyên sâu:
Nhóm 1 – Hiệu suất hoạt động & Tăng trưởng
Tập trung vào các chỉ số đo lường khả năng sinh lời và tốc độ phát
triển của công ty (Ví dụ: ROE_Nam, ROA_Nam,
BienLoiNhuanRong_Nam, các chỉ số tăng trưởng).
| Tên Biến | Mô tả / Mục đích Phân tích |
|---|---|
| DoanhThu | Doanh thu thuần hàng năm (từ KQKD) |
| LoiNhuanSauThue | Lợi nhuận sau thuế của cổ đông công ty mẹ (từ KQKD) |
| ROE_Nam | Tỷ suất lợi nhuận trên vốn chủ sở hữu (phái sinh) |
| ROA_Nam | Tỷ suất lợi nhuận trên tổng tài sản (phái sinh) |
| DoanhThu_TangTruong_YoY | Tốc độ tăng trưởng doanh thu so với năm trước (phái sinh) |
| LoiNhuan_TangTruong_YoY | Tốc độ tăng trưởng lợi nhuận so với năm trước (phái sinh) |
| EPS | Lợi nhuận trên mỗi cổ phiếu (từ KQKD) |
| BienLoiNhuanRong_Nam | Biên lợi nhuận ròng (LNST / DoanhThu) (phái sinh) |
Nhóm 2 – Cấu trúc tài chính, Định giá & Chất lượng dòng tiền
Tập trung vào sức khỏe tài chính (đòn bẩy, quy mô), hiệu quả định giá của thị trường (P/E, Vốn hóa), và chất lượng lợi nhuận (so sánh Dòng tiền hoạt động kinh doanh CFO với Lợi nhuận).
| Nhóm Phân tích | Tên Biến | Mô tả / Mục đích Phân tích |
|---|---|---|
| Cấu trúc Tài chính (Sức khỏe) | TongTaiSan | Tổng tài sản (phản ánh quy mô) |
| Cấu trúc Tài chính (Sức khỏe) | NoPhaiTra | Tổng nợ phải trả |
| Cấu trúc Tài chính (Sức khỏe) | VonChuSoHuu | Tổng vốn chủ sở hữu |
| Cấu trúc Tài chính (Sức khỏe) | TySoNo_Tren_VCSH | Tỷ số Nợ / Vốn CSH (thước đo đòn bẩy) |
| Định giá Thị trường (Valuation) | Gia_DongCua_CuoiNam | Giá cổ phiếu tại cuối mỗi năm |
| Định giá Thị trường (Valuation) | VonHoa_TyVND | Vốn hóa thị trường (Giá * SL Cổ phiếu) |
| Định giá Thị trường (Valuation) | P_E_CuoiNam | Tỷ số Giá / Thu nhập (thước đo định giá) |
| Chất lượng Dòng tiền (Cash Flow) | CFO | Dòng tiền thuần từ HĐKD (tiền thật tạo ra) |
| Chất lượng Dòng tiền (Cash Flow) | LoiNhuanSauThue | Lợi nhuận sau thuế (lợi nhuận kế toán - dùng để so sánh) |
| Chất lượng Dòng tiền (Cash Flow) | TyLe_CFO_Tren_LNST | Tỷ lệ CFO / LNST (thước đo chất lượng lợi nhuận) |
1. library(readxl); library(dplyr); library(stringr)
2. bctc_path <- "/Users/hotranhongnga/Downloads/UFM/HK3-2025/R/Báo cáo tài chính GAS 2014-2024/BCTC PV GAS.xlsx"
3. bckt_raw <- read_excel(bctc_path, sheet = 1)
4. kqkd_raw <- read_excel(bctc_path, sheet = 2)
5. if ("TÀI SẢN" %in% names(bckt_raw)) bckt_raw <- bckt_raw %>% rename(ChiTieu = `TÀI SẢN`)
6. if ("CHỈ TIÊU" %in% names(kqkd_raw)) kqkd_raw <- kqkd_raw %>% rename(ChiTieu = `CHỈ TIÊU`)| Dòng code | Giải thích |
|---|---|
| Dòng 1 | Tải các gói thư viện cần thiết: readxl (để
đọc file Excel), dplyr (thao tác dữ liệu), và
stringr (xử lý chuỗi, mặc dù không được sử dụng ở khối
này). |
| Dòng 2 | Định nghĩa biến bctc_path, lưu
đường dẫn đến file Excel chứa dữ liệu Báo cáo Tài chính
của GAS. |
| Dòng 3-4 | Tải dữ liệu thô từ Excel: * read_excel(..., sheet = 1): Đọc dữ liệu từ
sheet 1 (Bảng Cân đối Kế toán) và lưu vào
bckt_raw. * read_excel(..., sheet = 2): Đọc dữ liệu từ
sheet 2 (Bảng Kết quả Kinh doanh) và lưu vào
kqkd_raw. |
| Dòng 5-6 | Chuẩn hóa tên cột: * Kiểm tra và đổi tên cột chứa tên chỉ tiêu trong Bảng Cân đối Kế toán: nếu cột tên là "TÀI SẢN", đổi tên thành
ChiTieu. * Kiểm tra và đổi tên cột chứa tên chỉ tiêu trong Bảng Kết quả Kinh doanh: nếu cột tên là "CHỈ TIÊU", đổi tên thành
ChiTieu. |
1. library(dplyr); library(stringr); library(lubridate)
2. nam_vec <- 2014:2024
3. extract_row_data <- function(df, chi_tieu_name) {
4. row <- df %>% filter(grepl(chi_tieu_name, ChiTieu, ignore.case = TRUE, fixed = TRUE))
5. if (nrow(row) == 0) stop(paste("Không tìm thấy chỉ tiêu:", chi_tieu_name))
6. if (nrow(row) > 1) stop(paste("Có >1 dòng khớp:", chi_tieu_name))
7. date_cols <- names(df)[str_detect(names(df), "^\\d{2}/\\d{2}/\\d{4}$")]
8. tmp <- row %>% select(all_of(date_cols)) %>%
9. tidyr::pivot_longer(everything(), names_to = "Ngay", values_to = "val") %>%
10. mutate(Nam = year(lubridate::dmy(Ngay))) %>% arrange(Nam)
11. tmp <- tmp %>% filter(Nam %in% nam_vec) %>%
12. mutate(val = as.character(val)) %>%
13. mutate(val = as.numeric(str_remove_all(val, "\\."))) %>%
14. right_join(tibble(Nam = nam_vec), by = "Nam") %>%
15. arrange(Nam)
16. if (any(is.na(tmp$val))) {
17. warning("Có NA sau khi trích xuất (năm: ", paste(tmp$Nam[is.na(tmp$val)], collapse = ", "),
18. "). Kiểm tra lại ô dữ liệu các năm này trong Excel.")}
19. return(tmp$val)}
20. clean_numeric <- function(x) {x_char <- as.character(x)
21. x_cleaned <- str_remove_all(x_char, "\\.")
22. return(as.numeric(x_cleaned))}
23. extract_row_data <- function(df, chi_tieu_name) {data_row <- df %>%
24. filter(grepl(chi_tieu_name, ChiTieu, ignore.case = TRUE, fixed = TRUE))
25. if(nrow(data_row) == 0) {
26. stop(paste("\n LỖI HÀM extract_row_data ",
27. "\nKhông tìm thấy chỉ tiêu nào khớp chính xác với chuỗi:",
28. "\n'", chi_tieu_name, "'",
29. "\n>>> Hãy kiểm tra lại chuỗi ký tự trong code"))}
30. if(nrow(data_row) > 1) {
31. stop(paste("\n LỖI HÀM extract_row_data ",
32. "\nTìm thấy nhiều hơn 1 hàng khớp với chuỗi:",
33. "\n'", chi_tieu_name, "'",
34. "\n>>> Hãy cung cấp chuỗi ký tự chính xác hơn."))}
35. data_vec <- data_row %>% select(-ChiTieu) %>% select(1:11) %>% unlist(use.names = FALSE)
36. return(data_vec)}| Dòng code | Giải thích |
|---|---|
| Dòng 1 | Tải các gói thư viện R: dplyr (thao tác dữ
liệu), stringr (xử lý chuỗi), và lubridate (xử
lý ngày tháng). |
| Dòng 2 | Khai báo vector nam_vec chứa các năm quan
sát mục tiêu từ 2014 đến 2024 (tổng cộng 11 năm). |
| Dòng 3-23 | Định nghĩa hàm
extract_row_data: Hàm này được định nghĩa ban đầu
để trích xuất dữ liệu theo định dạng ngày tháng. * Dòng 4-6: Lọc hàng chứa chi_tieu_name (không phân biệt
chữ hoa/thường, tìm khớp cố định), đồng thời kiểm tra và dừng
(stop) nếu không tìm thấy hoặc tìm thấy nhiều hơn 1 hàng.
* Dòng 7-10: Trích xuất các cột có tên là ngày tháng ( str_detect(..., "^\\d{2}/\\d{2}/\\d{4}$")), chuyển
dữ liệu từ ngang sang dọc (tidyr::pivot_longer), chuyển cột
ngày sang Năm
(year(lubridate::dmy(Ngay))), và sắp xếp. * Dòng 11-14: Làm sạch dữ liệu: Lọc chỉ giữ lại các năm trong nam_vec, loại bỏ dấu chấm phân cách
hàng nghìn (str_remove_all(..., "\\.")), chuyển sang kiểu
số, và dùng right_join với nam_vec để
đảm bảo kết quả trả về đủ 11 năm (từ 2014-2024). * Dòng 15-18: Cảnh báo nếu có giá trị NA sau khi làm sạch. |
| Dòng 24-27 | Định nghĩa hàm
clean_numeric: Hàm hỗ trợ để chuyển đổi giá trị số
dạng chuỗi (có dấu chấm phân cách hàng nghìn, ví dụ: “1.234.567”) thành
kiểu số học bằng cách loại bỏ tất cả dấu chấm. |
| Dòng 28-44 | Định nghĩa lại hàm
extract_row_data (Phiên bản 2): Phiên bản được tối
ưu hóa và sẽ được sử dụng trong các bước tiếp theo. * Dòng 29-37: Lọc hàng theo chỉ tiêu và kiểm tra lỗi tương tự Phiên bản 1. * Dòng 38-41: Trích xuất giá trị: Chọn tất cả các cột trừ cột ChiTieu, sau đó
chỉ lấy 11 cột đầu tiên (select(1:11))
(giả định đây là 11 năm dữ liệu đã được sắp xếp), và chuyển thành vector
(unlist). * Dòng 42: Trả về vector 11 giá trị này. |
1. dt_vec <- extract_row_data(kqkd_raw, "3. Doanh thu thuần về bán hàng và cung cấp dịch vụ")
2. lnst_vec <- extract_row_data(kqkd_raw, "18.1. Lợi nhuận sau thuế của cổ đông của Công ty mẹ")
3. tts_vec <- extract_row_data(bckt_raw, "TỔNG CỘNG TÀI SẢN")
4. no_vec <- extract_row_data(bckt_raw, "C. NỢ PHẢI TRẢ")
5. vcsh_vec <- extract_row_data(bckt_raw, "D. VỐN CHỦ SỞ HỮU")
6. GAS_data_df <- data.frame(Nam = 2014:2024, DoanhThu = dt_vec, LoiNhuanSauThue = lnst_vec,
7. TongTaiSan = tts_vec, NoPhaiTra = no_vec, VonChuSoHuu = vcsh_vec)
8. GAS_data_final <- GAS_data_df %>% mutate(across(-Nam, clean_numeric))| Dòng code | Giải thích |
|---|---|
| Dòng 1-5 | Trích xuất dữ liệu từ BCTC: * Gọi hàm extract_row_data (Phiên bản 2) để trích xuất 11 giá trị
(từ 2014 đến 2024) tương ứng với các chỉ tiêu sau: * dt_vec: Doanh thu thuần (từ
kqkd_raw). * lnst_vec: Lợi nhuận
sau thuế của cổ đông Công ty mẹ (từ kqkd_raw).
* tts_vec: Tổng Tài sản (từ
bckt_raw). * no_vec: Nợ Phải
Trả (từ bckt_raw). * vcsh_vec:
Vốn Chủ Sở Hữu (từ bckt_raw). |
| Dòng 6 | Tạo khung dữ liệu
(GAS_data_df): * Hợp nhất các vector dữ liệu vừa trích xuất và vector Nam (2014:2024) thành một
data.frame. |
| Dòng 7 | Làm sạch và Chuẩn hóa kiểu dữ liệu:
* Sử dụng mutate(across(-Nam, clean_numeric)) để áp
dụng hàm clean_numeric cho tất cả
các cột trừ cột Nam. Thao tác này loại bỏ dấu chấm
phân cách hàng nghìn (ví dụ: “1.000.000”) và chuyển dữ liệu thành kiểu
số học (numeric). |
1. gia_dong_cua_vec <- c(34154.5,18128.8,33089.0,56311.2,52231.7,58209.9,56436.3,64672.3,70096.3,64830.3,64160.1)
2. GAS_data_final <- GAS_data_final %>%
3. dplyr::mutate(
4. Gia_DongCua_CuoiNam = gia_dong_cua_vec,
5. ROE_Nam = (LoiNhuanSauThue / VonChuSoHuu) * 100,
6. ROA_Nam = (LoiNhuanSauThue / TongTaiSan) * 100,
7. TySoNo_Tren_VCSH = NoPhaiTra / VonChuSoHuu,
8. DoanhThu_TangTruong_YoY = (DoanhThu / dplyr::lag(DoanhThu) - 1) * 100,
9. LoiNhuan_TangTruong_YoY = (LoiNhuanSauThue / dplyr::lag(LoiNhuanSauThue) - 1) * 100) %>%
10. dplyr::mutate(dplyr::across(c(DoanhThu_TangTruong_YoY, LoiNhuan_TangTruong_YoY),
11. ~dplyr::if_else(is.na(.x), 0, .x)))
12. suppressWarnings({lctt_raw <- try(readxl::read_excel(bctc_path, sheet = 3), silent = TRUE)})
13. eps_vec <- NA_real_
14. eps_label <- "19. Lãi cơ bản trên cổ phiếu"
15. eps_ok <- TRUE
16. try({
17. eps_raw_vec <- extract_row_data(kqkd_raw, eps_label)
18. eps_vec <- clean_numeric(eps_raw_vec)}, silent = TRUE)
19. if (all(is.na(eps_vec))) {
20. eps_ok <- FALSE
21. warning("Không trích xuất được EPS bằng nhãn '", eps_label, "'. Vui lòng kiểm tra tên chỉ tiêu trong Excel.")}
22. cfo_vec <- NA_real_
23. cfo_ok <- FALSE
24. if (!inherits(lctt_raw, "try-error")) {
25. if (!"ChiTieu" %in% names(lctt_raw)) {
26. first_col <- names(lctt_raw)[1] # SỬA: CHỈ LẤY TÊN CỘT ĐẦU TIÊN
27. lctt_raw <- lctt_raw %>% dplyr::rename(ChiTieu = !!rlang::sym(first_col))}
28. cfo_candidates <- c(
29. "Lưu chuyển tiền thuần từ hoạt động kinh doanh", "Lưu chuyển tiền thuần từ HĐKD",
30. "LCT thuần từ HĐKD", "Lưu chuyển tiền thuần từ hoạt động kinh doanh trong kỳ")
31. for (lab in cfo_candidates) {
32. tmp <- try(extract_row_data(lctt_raw, lab), silent = TRUE)
33. if (!inherits(tmp, "try-error")) {
34. cfo_vec <- clean_numeric(tmp)
35. cfo_ok <- TRUE
36. break}}}
37. if (!cfo_ok) {
38. warning("Không trích xuất được CFO từ Sheet 3. Có thể tự nhập vector CFO 11 năm hoặc giữ NA.")
39. cfo_vec <- rep(NA_real_, 11)}
40. slcp_calc <- rep(NA_real_, 11)
41. if (eps_ok) {
42. lnst_vec <- GAS_data_final$LoiNhuanSauThue
43. good_idx <- which(!is.na(lnst_vec) & !is.na(eps_vec) & eps_vec != 0)
44. slcp_calc[good_idx] <- round(lnst_vec[good_idx] / eps_vec[good_idx])} else {
45. warning("SL_CoPhieu_LuuHanh không tính được do thiếu EPS.")}
46. vonhoa_calc <- rep(NA_real_, 11)
47. pe_calc <- rep(NA_real_, 11)
48. ratio_cfo <- rep(NA_real_, 11)
49. price_vec <- GAS_data_final$Gia_DongCua_CuoiNam
50. slcp_final <- dplyr::coalesce(GAS_data_final$SL_CoPhieu_LuuHanh, slcp_calc)
51. ok_vonhoa <- which(!is.na(price_vec) & !is.na(slcp_final))
52. vonhoa_calc[ok_vonhoa] <- (price_vec[ok_vonhoa] * slcp_final[ok_vonhoa]) / 1e9
53. ok_pe <- which(!is.na(price_vec) & !is.na(eps_vec) & eps_vec != 0)
54. pe_calc[ok_pe] <- price_vec[ok_pe] / eps_vec[ok_pe]
55. pe_calc[is.infinite(pe_calc)] <- NA_real_
56. ok_ratio <- which(!is.na(cfo_vec) & !is.na(GAS_data_final$LoiNhuanSauThue) & GAS_data_final$LoiNhuanSauThue != 0)
57. ratio_cfo[ok_ratio] <- cfo_vec[ok_ratio] / GAS_data_final$LoiNhuanSauThue[ok_ratio]
58. needed_cols <- c("EPS", "SL_CoPhieu_LuuHanh", "CFO", "VonHoa_TyVND", "P_E_CuoiNam", "TyLe_CFO_Tren_LNST")
59. for (nm in needed_cols) {
60. if (!nm %in% names(GAS_data_final)) {
61. GAS_data_final[[nm]] <- NA_real_}}
62. GAS_data_final <- GAS_data_final %>%
63. dplyr::mutate(
64. EPS = dplyr::coalesce(EPS, eps_vec),
65. SL_CoPhieu_LuuHanh = dplyr::coalesce(SL_CoPhieu_LuuHanh, slcp_final),
66. CFO = dplyr::coalesce(CFO, cfo_vec),
67. VonHoa_TyVND = dplyr::coalesce(VonHoa_TyVND, vonhoa_calc),
68. P_E_CuoiNam = dplyr::coalesce(P_E_CuoiNam, pe_calc),
69. TyLe_CFO_Tren_LNST = dplyr::coalesce(TyLe_CFO_Tren_LNST, ratio_cfo))
70. msg_eps <- if (exists("eps_ok") && eps_ok) "EPS: OK" else "EPS: thiếu (NA)"
71. msg_cfo <- if (exists("cfo_ok") && cfo_ok) "CFO: OK" else "CFO: thiếu (NA)"
72. message("Bổ sung hoàn tất -> ", msg_eps, " | ", msg_cfo,
73. " | Thêm/giữ: SL_CoPhieu_LuuHanh, VonHoa_TyVND, P_E_CuoiNam, TyLe_CFO_Tren_LNST.")Bổ sung hoàn tất -> EPS: OK | CFO: OK | Thêm/giữ: SL_CoPhieu_LuuHanh, VonHoa_TyVND, P_E_CuoiNam, TyLe_CFO_Tren_LNST.
Kiểm tra cấu trúc:
'data.frame': 11 obs. of 6 variables:
$ EPS : num 7140 4400 6748 4994 5911 ...
$ SL_CoPhieu_LuuHanh: num 1977965757 67825115 22563218 50646506 43071660 ...
$ CFO : num 16701449198611 9126872112300 4942224182550 14385549919558 12421887654437 ...
$ VonHoa_TyVND : num 67556 1230 747 2852 2250 ...
$ P_E_CuoiNam : num 4.78 4.12 4.9 11.28 8.84 ...
$ TyLe_CFO_Tren_LNST: num 1.18 30.58 32.46 56.88 48.79 ...
Kiểm tra tóm tắt:
EPS SL_CoPhieu_LuuHanh CFO VonHoa_TyVND
Min. :4028 Min. : 22563218 Min. : 4942224182550 Min. : 747
1st Qu.:4378 1st Qu.: 46859083 1st Qu.: 8314745963780 1st Qu.: 1994
Median :4994 Median :1934673450 Median :12421887654400 Median : 67556
Mean :5518 Mean :1162757089 Mean :10985304279200 Mean : 68670
3rd Qu.:6445 3rd Qu.:1984502284 3rd Qu.:13309557575800 3rd Qu.:132189
Max. :7649 Max. :2388316502 Max. :16701449198600 Max. :153235
P_E_CuoiNam TyLe_CFO_Tren_LNST
Min. : 4.12 Min. : 0.864
1st Qu.: 6.87 1st Qu.: 0.904
Median : 9.48 Median : 1.191
Mean : 9.93 Mean :22.161
3rd Qu.:13.53 3rd Qu.:40.625
Max. :14.85 Max. :69.142
| Dòng code | Giải thích |
|---|---|
| Dòng 1-10 | Tạo các biến phái sinh cơ bản (Dữ liệu thị
trường và Hiệu suất): * Dòng 1: Khai báo vector giá đóng cửa 11 năm. * Dòng 3: Thêm cột Gia_DongCua_CuoiNam từ vector đã nhập.
* Dòng 4-5: Tính ROE_Nam (Tỷ suất LNST/VCSH) và
ROA_Nam (Tỷ suất LNST/TTS) theo tỷ lệ phần
trăm (\(\times 100\)). * Dòng 6: Tính TySoNo_Tren_VCSH (Tỷ số Nợ/VCSH). * Dòng 7-8: Tính DoanhThu_TangTruong_YoY và
LoiNhuan_TangTruong_YoY (Tăng trưởng so
với năm trước - YoY) bằng công thức \((\text{Năm hiện tại} / \text{Năm trước} - 1)
\times 100\). Hàm dplyr::lag() lấy giá trị năm
trước. * Dòng 9-10: Xử lý NA: Gán giá trị 0 cho các ô NA trong hai cột tăng trưởng (vì năm đầu tiên luôn là NA, và nếu không tính được thì coi là 0). |
| Dòng 11-20 | Trích xuất EPS (Lãi cơ bản trên cổ
phiếu): * Dòng 11: Thử đọc dữ liệu từ Sheet 3 (Lưu chuyển tiền tệ), giả định không cần sử dụng. * Dòng 12-14: Khai báo biến EPS. * Dòng 15-17: Sử dụng khối try để thử trích xuất giá trị
EPS từ kqkd_raw bằng nhãn. * Dòng 18-20: Cảnh báo nếu không trích xuất được EPS. |
| Dòng 21-36 | Trích xuất CFO (Dòng tiền HĐKD): * Dòng 24-35: Nếu Sheet 3 ( lctt_raw) đọc
thành công, kiểm tra và chuẩn hóa tên cột đầu tiên thành
ChiTieu. * Dòng 26-31: Dùng một danh sách các nhãn tiềm năng ( cfo_candidates) để thử trích xuất
CFO, dừng ngay khi tìm thấy nhãn khớp và làm sạch dữ liệu. * Dòng 32-36: Nếu thất bại sau tất cả các lần thử, cảnh báo và gán NA cho vector CFO. |
| Dòng 37-43 | Tính số lượng cổ phiếu lưu hành
(SLCP): * Dòng 39-41: Nếu EPS trích xuất thành công, tính slcp_calc bằng công thức \(\text{LNST} / \text{EPS}\) (chỉ tính cho
các năm có dữ liệu hợp lệ và \(\text{EPS} \ne
0\)). |
| Dòng 44-59 | Tính các chỉ số Thị trường/Phái sinh
khác: * Dòng 47-48: Tính VonHoa_TyVND (Vốn hóa): \(\text{Giá} \times \text{SLCP} / 10^9\)
(chia \(10^9\) để tính theo tỷ VND).
* Dòng 49-51: Tính P_E_CuoiNam (Tỷ số P/E): \(\text{Giá} / \text{EPS}\). * Dòng 52-54: Tính TyLe_CFO_Tren_LNST (Tỷ lệ CFO/LNST): \(\text{CFO} / \text{LNST}\) (đo lường chất
lượng lợi nhuận). |
| Dòng 60-68 | Cập nhật khung dữ liệu cuối cùng: * Dòng 60-62: Đảm bảo các cột cần thiết tồn tại trong GAS_data_final (khởi tạo bằng NA nếu chưa có). * Dòng 63-68: Cập nhật các cột mới tính toán (EPS, CFO, P/E, Vốn hóa, Tỷ lệ CFO/LNST) vào GAS_data_final, ưu tiên
giữ lại giá trị cũ nếu đã có (dùng dplyr::coalesce). |
| Dòng 69-74 | Kiểm tra kết quả: In thông báo tóm tắt
về việc trích xuất EPS/CFO và hiển thị cấu trúc (str) và
thống kê cơ bản (summary) của các cột mới được thêm/tính
toán. |
1. library(knitr)
2. kable(GAS_data_final, caption = "Bảng 2.2.1: Dữ liệu GAS sau xử lý (11 năm, 12 biến)")| Nam | DoanhThu | LoiNhuanSauThue | TongTaiSan | NoPhaiTra | VonChuSoHuu | Gia_DongCua_CuoiNam | ROE_Nam | ROA_Nam | TySoNo_Tren_VCSH | DoanhThu_TangTruong_YoY | LoiNhuan_TangTruong_YoY | EPS | SL_CoPhieu_LuuHanh | CFO | VonHoa_TyVND | P_E_CuoiNam | TyLe_CFO_Tren_LNST |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 2014 | 73393403034088 | 14122675507937 | 53791407348105 | 16112058787504 | 37679348560601 | 34154 | 37.481 | 26.255 | 0.428 | 0.000 | 0.000 | 7140 | 1977965757 | 16701449198611 | 67556 | 4.78 | 1.183 |
| 2015 | 64300204038285 | 298430505873 | 56714606287288 | 13825543405185 | 42889062882103 | 18129 | 0.696 | 0.526 | 0.322 | -12.390 | -97.887 | 4400 | 67825115 | 9126872112300 | 1230 | 4.12 | 30.583 |
| 2016 | 59076193175661 | 152256595845 | 56753853518438 | 15910005640211 | 40843847878227 | 33089 | 0.373 | 0.268 | 0.390 | -8.124 | -48.981 | 6748 | 22563218 | 4942224182550 | 747 | 4.90 | 32.460 |
| 2017 | 64522440976234 | 252928652205 | 61889343342437 | 18617834577626 | 43271508764811 | 56311 | 0.585 | 0.409 | 0.430 | 9.219 | 66.120 | 4994 | 50646506 | 14385549919558 | 2852 | 11.28 | 56.876 |
| 2018 | 75611546239412 | 254596584934 | 62614420245293 | 15747295132679 | 46867125112614 | 52232 | 0.543 | 0.407 | 0.336 | 17.186 | 0.659 | 5911 | 43071660 | 12421887654437 | 2250 | 8.84 | 48.790 |
| 2019 | 75005297175406 | 183403281712 | 621787389634 | 12564256032003 | 49614531357631 | 58210 | 0.370 | 29.496 | 0.253 | -0.802 | -27.963 | 6142 | 29860515 | 12680818093290 | 1738 | 9.48 | 69.142 |
| 2020 | 64134965486838 | 7854955921939 | 63208401030103 | 13708720044649 | 49499680985454 | 56436 | 15.869 | 12.427 | 0.277 | -14.493 | 4182.887 | 4028 | 1950088362 | 7330938831075 | 110056 | 14.01 | 0.933 |
| 2021 | 78992156122272 | 8672965062460 | 78768704688654 | 26575344013434 | 52192730675130 | 64672 | 16.617 | 11.011 | 0.509 | 23.166 | 10.414 | 4356 | 1991038811 | 7594281226199 | 128765 | 14.85 | 0.876 |
| 2022 | 100723549227433 | 14798317219715 | 82662652366363 | 21489088811222 | 61173563555141 | 70096 | 24.191 | 17.902 | 0.351 | 27.511 | 70.626 | 7649 | 1934673450 | 12792070499188 | 135613 | 9.16 | 0.864 |
| 2023 | 89953906513325 | 11606030646959 | 87754455330448 | 22455835056055 | 65298620274393 | 64830 | 17.774 | 13.226 | 0.344 | -10.692 | -21.572 | 4972 | 2334278087 | 13827044652491 | 151332 | 13.04 | 1.191 |
| 2024 | 103564126562888 | 10398730049725 | 81854881408158 | 20283886142528 | 61570995265630 | 64160 | 16.889 | 12.704 | 0.329 | 15.130 | -10.402 | 4354 | 2388316502 | 9035210701364 | 153235 | 14.74 | 0.869 |
Nhận xét tổng quan
| Dòng code | Giải thích |
|---|---|
| Dòng 1 | Tải gói thư viện knitr (để tạo bảng). |
| Dòng 2 | Sử dụng hàm kable() để hiển thị
toàn bộ khung dữ liệu GAS_data_final. * caption = "...": Đặt tiêu đề cho bảng
(Bảng 2.2.1). |
1. library(dplyr); library(tidyr); library(knitr); library(moments); library(ggplot2)
2. vars2 <- GAS_data_final %>%
3. dplyr::select(
4. Nam, TongTaiSan, NoPhaiTra, VonChuSoHuu, TySoNo_Tren_VCSH,
5. Gia_DongCua_CuoiNam, SL_CoPhieu_LuuHanh, VonHoa_TyVND,
6. P_E_CuoiNam, CFO, TyLe_CFO_Tren_LNST)
7. stopifnot(nrow(vars2) == 11)
8. stopifnot(!any(is.na(vars2)))
9. kable(head(vars2, 5), caption = "Bảng 2.3.1: Mẫu dữ liệu (5 dòng đầu)")| Nam | TongTaiSan | NoPhaiTra | VonChuSoHuu | TySoNo_Tren_VCSH | Gia_DongCua_CuoiNam | SL_CoPhieu_LuuHanh | VonHoa_TyVND | P_E_CuoiNam | CFO | TyLe_CFO_Tren_LNST |
|---|---|---|---|---|---|---|---|---|---|---|
| 2014 | 53791407348105 | 16112058787504 | 37679348560601 | 0.428 | 34154 | 1977965757 | 67556 | 4.78 | 16701449198611 | 1.18 |
| 2015 | 56714606287288 | 13825543405185 | 42889062882103 | 0.322 | 18129 | 67825115 | 1230 | 4.12 | 9126872112300 | 30.58 |
| 2016 | 56753853518438 | 15910005640211 | 40843847878227 | 0.390 | 33089 | 22563218 | 747 | 4.90 | 4942224182550 | 32.46 |
| 2017 | 61889343342437 | 18617834577626 | 43271508764811 | 0.430 | 56311 | 50646506 | 2852 | 11.28 | 14385549919558 | 56.88 |
| 2018 | 62614420245293 | 15747295132679 | 46867125112614 | 0.336 | 52232 | 43071660 | 2250 | 8.84 | 12421887654437 | 48.79 |
| Dòng code | Giải thích |
|---|---|
| Dòng 1 | Tải các gói thư viện cần thiết: dplyr,
tidyr, knitr, moments (thống kê
hình dạng), và ggplot2. |
| Dòng 2-4 | Chọn lọc và Chuẩn hóa dữ liệu: * Dòng 2-4: Chọn các cột thuộc Nhóm 2 (Cấu trúc, Định giá, Dòng tiền) từ GAS_data_final và lưu vào
vars2. |
| Dòng 5 | Kiểm tra cỡ mẫu: Sử dụng
stopifnot(nrow(vars2) == 11) để đảm bảo bộ dữ liệu có đủ 11
quan sát (từ 2014-2024). Nếu sai, chương trình sẽ dừng. |
| Dòng 6 | Kiểm tra NA: Sử dụng
stopifnot(!any(is.na(vars2))) để đảm bảo không có bất kỳ
giá trị thiếu (NA) nào trong bộ dữ liệu Nhóm 2. Nếu có, chương trình sẽ
dừng. |
| Dòng 7 | Hiển thị 5 quan sát đầu tiên của bộ dữ liệu Nhóm 2 bằng
kable. |
1. na_by_col <- sapply(vars2, function(x) sum(is.na(x)))
2. dup_all <- nrow(vars2) - nrow(distinct(vars2))
3. years_ok <- all(sort(vars2$Nam) == 2014:2024)
4. qc <- tibble(
5. Muc = c("NA (mỗi biến)", "Bản ghi trùng lặp hoàn toàn", "Đủ 11 năm (2014–2024)"),
6. GiaTri = c(paste(names(na_by_col), na_by_col, collapse = "; "), dup_all,years_ok))
7. kable(qc, caption = "Bảng 2.3.2: Kiểm tra chất lượng dữ liệu")| Muc | GiaTri |
|---|---|
| NA (mỗi biến) | Nam 0; TongTaiSan 0; NoPhaiTra 0; VonChuSoHuu 0; TySoNo_Tren_VCSH 0; Gia_DongCua_CuoiNam 0; SL_CoPhieu_LuuHanh 0; VonHoa_TyVND 0; P_E_CuoiNam 0; CFO 0; TyLe_CFO_Tren_LNST 0 |
| Bản ghi trùng lặp hoàn toàn | 0 |
| Đủ 11 năm (2014–2024) | TRUE |
| Dòng code | Giải thích |
|---|---|
| Dòng 1 | Đếm NA theo cột: Dùng
sapply() để áp dụng hàm sum(is.na(x)) cho từng
cột trong vars2, đếm tổng số giá trị thiếu (NA). |
| Dòng 2 | Đếm bản ghi trùng lặp: Tính tổng số
hàng của vars2 trừ đi số hàng duy nhất
(distinct(vars2)). Kết quả là số lượng bản ghi trùng lặp
hoàn toàn. |
| Dòng 3 | Kiểm tra đủ năm: Kiểm tra xem cột
Nam có chứa đầy đủ và chính xác 11 năm từ 2014 đến 2024 hay
không (all(sort(vars2$Nam) == 2014:2024)). |
| Dòng 4-7 | Tạo bảng tóm tắt: Tạo khung dữ liệu
(tibble) qc để tổng hợp các kết quả kiểm tra
chất lượng: * Hàng 1: Số lượng NA của từng biến. * Hàng 2: Số lượng bản ghi trùng lặp. * Hàng 3: Kết quả kiểm tra đủ 11 năm ( TRUE/FALSE). |
| Dòng 8 | Hiển thị bảng tóm tắt kiểm tra chất lượng bằng
kable. |
1. desc_fun <- function(x){
2. x <- as.numeric(x)
3. c(Mean=mean(x), SD=sd(x), Min=min(x), Q1=quantile(x,.25), Median=median(x),
4. Q3=quantile(x,.75), Max=max(x), IQR=IQR(x), CV=ifelse(mean(x)==0, NA, sd(x)/mean(x)))}
5. tab_basic <- sapply(vars2 %>% select(-Nam), desc_fun) %>% t() %>% as.data.frame()
6. kable(round(tab_basic, 3), caption = "Bảng 2.3.3: Thống kê mô tả cơ bản")| Mean | SD | Min | Q1.25% | Median | Q3.75% | Max | IQR | CV | |
|---|---|---|---|---|---|---|---|---|---|
| TongTaiSan | 62421319359538.273 | 23781639432920.168 | 621787389634.000 | 56734229902863.000 | 62614420245293.000 | 80311793048406.000 | 87754455330448.000 | 23577563145543.000 | 0.381 |
| NoPhaiTra | 17935442513008.727 | 4353301098165.625 | 12564256032003.000 | 14786419268932.000 | 16112058787504.000 | 20886487476875.000 | 26575344013434.000 | 6100068207943.000 | 0.243 |
| VonChuSoHuu | 50081910482885.000 | 9160807313157.254 | 37679348560601.000 | 43080285823457.000 | 49499680985454.000 | 56683147115135.500 | 65298620274393.000 | 13602861291678.500 | 0.183 |
| TySoNo_Tren_VCSH | 0.361 | 0.074 | 0.253 | 0.326 | 0.344 | 0.409 | 0.509 | 0.083 | 0.204 |
| Gia_DongCua_CuoiNam | 52029.127 | 16434.014 | 18128.800 | 43193.100 | 56436.300 | 64416.200 | 70096.300 | 21223.100 | 0.316 |
| SL_CoPhieu_LuuHanh | 1162757089.364 | 1082297401.643 | 22563218.000 | 46859083.000 | 1934673450.000 | 1984502284.000 | 2388316502.000 | 1937643201.000 | 0.931 |
| VonHoa_TyVND | 68670.302 | 67953.200 | 746.594 | 1993.942 | 67556.431 | 132189.255 | 153234.626 | 130195.313 | 0.990 |
| P_E_CuoiNam | 9.927 | 4.036 | 4.120 | 6.870 | 9.477 | 13.525 | 14.847 | 6.655 | 0.407 |
| CFO | 10985304279187.545 | 3593651219517.390 | 4942224182550.000 | 8314745963781.500 | 12421887654437.000 | 13309557575839.500 | 16701449198611.000 | 4994811612058.000 | 0.327 |
| TyLe_CFO_Tren_LNST | 22.161 | 26.436 | 0.864 | 0.904 | 1.191 | 40.625 | 69.142 | 39.721 | 1.193 |
| Dòng code | Giải thích |
|---|---|
| Dòng 1-4 | Định nghĩa hàm desc_fun:
Hàm tùy chỉnh để tính toán các chỉ số thống kê mô tả cơ bản cho một
vector số (x). * Hàm trả về một vector chứa Mean, SD, Min, Q1 (Tứ phân vị 25%), Median (Trung vị), Q3 (Tứ phân vị 75%), Max, IQR (Khoảng Tứ phân vị), và CV (Hệ số biến thiên = SD/Mean). |
| Dòng 5 | Áp dụng hàm thống kê và tạo bảng: * sapply(vars2 %>% select(-Nam), desc_fun):
Áp dụng hàm desc_fun cho tất cả các cột trong
vars2 trừ cột Nam. * t(): Chuyển vị ma trận kết quả (biến thành
hàng, thống kê thành cột). * as.data.frame(): Chuyển thành khung dữ
liệu. |
| Dòng 6 | Hiển thị bảng thống kê mô tả cơ bản
(tab_basic) sau khi làm tròn đến 3 chữ số thập phân bằng
kable. |
Nhận xét:
Bảng 2.3.3 cho thấy GAS duy trì cấu trúc tài chính và hiệu quả vận hành ổn định và an toàn giai đoạn 2014–2024.
TongTaiSan thấp bất thường
(\(\approx 621\) tỷ VND) cần kiểm tra
lại dữ liệu gốc.VonHoa_TyVND (Vốn hóa) biến động cao (CV \(\approx 1\)), chủ yếu do biến động
giá.Tổng thể: Nhóm biến này phản ánh một bức tranh tích cực: GAS có nền tảng tài chính vững, định giá ổn định và dòng tiền mạnh.
Lưu ý: n = 11 năm, các kiểm định chuẩn (Shapiro) ít sức mạnh → chỉ tham khảo.
1. library(dplyr); library(moments); library(purrr); library(knitr)
2. shape_fun <- function(x) {
3. x <- suppressWarnings(as.numeric(x))
4. x <- x[is.finite(x)]
5. c(Skewness = if (length(x) > 2) skewness(x) else NA_real_,
6. Kurtosis = if (length(x) > 3) kurtosis(x) else NA_real_ )}
7. tab_shape <- vars2 %>%
8. select(-Nam) %>%
9. map_dfr(shape_fun, .id = "Bien") %>%
10. mutate(across(where(is.numeric), ~ round(.x, 3)))
11. kable(tab_shape, caption = "Bảng 2.3.4: Skewness/Kurtosis (Nhóm 2)")| Bien | Skewness | Kurtosis |
|---|---|---|
| TongTaiSan | -1.566 | 5.29 |
| NoPhaiTra | 0.598 | 2.35 |
| VonChuSoHuu | 0.409 | 1.91 |
| TySoNo_Tren_VCSH | 0.517 | 2.68 |
| Gia_DongCua_CuoiNam | -0.928 | 2.62 |
| SL_CoPhieu_LuuHanh | -0.126 | 1.10 |
| VonHoa_TyVND | 0.087 | 1.21 |
| P_E_CuoiNam | -0.206 | 1.65 |
| CFO | -0.135 | 1.95 |
| TyLe_CFO_Tren_LNST | 0.620 | 1.81 |
1. safe_shapiro <- function(x) {
2. x <- suppressWarnings(as.numeric(x))
3. x <- x[is.finite(x)]
4. if (length(unique(x)) < 3 || length(x) < 3) {
5. return(list(W = NA_real_, p.value = NA_real_))}
6. s <- shapiro.test(x)
7. list(W = unname(s$statistic), p.value = unname(s$p.value))}
8. tab_sw_df <- vars2 %>%
9. select(-Nam) %>%
10. map(safe_shapiro) %>%
11. { tibble(
12. Bien = names(.),
13. W = map_dbl(., "W"),
14. p_value = map_dbl(., "p.value"))} %>%
15. mutate(across(where(is.numeric), ~ round(.x, 4)))
16. kable(tab_sw_df, caption = "Bảng 2.3.5: Shapiro–Wilk (n=11, tham khảo)")| Bien | W | p_value |
|---|---|---|
| TongTaiSan | 0.801 | 0.010 |
| NoPhaiTra | 0.936 | 0.473 |
| VonChuSoHuu | 0.930 | 0.414 |
| TySoNo_Tren_VCSH | 0.956 | 0.724 |
| Gia_DongCua_CuoiNam | 0.866 | 0.069 |
| SL_CoPhieu_LuuHanh | 0.736 | 0.001 |
| VonHoa_TyVND | 0.790 | 0.007 |
| P_E_CuoiNam | 0.901 | 0.190 |
| CFO | 0.957 | 0.738 |
| TyLe_CFO_Tren_LNST | 0.788 | 0.006 |
| Dòng code | Giải thích |
|---|---|
| Dòng 1 | Tải các gói cần thiết: moments (tính
Skewness/Kurtosis) và purrr (hàm chức năng). |
| Dòng 2-6 | Định nghĩa hàm shape_fun:
Hàm tùy chỉnh để tính Độ lệch (Skewness) và Độ
nhọn (Kurtosis). * Dòng 3-4: Xử lý và loại bỏ NA/Inf. * Dòng 5-6: Tính Skewness và Kurtosis (chỉ tính khi đủ số lượng quan sát - 3 cho Skewness, 4 cho Kurtosis). |
| Dòng 7-10 | Tính Skewness/Kurtosis: * map_dfr(vars2 %>% select(-Nam), shape_fun, .id = "Bien"):
Áp dụng hàm shape_fun cho tất cả các cột trừ
Nam, gom kết quả thành một khung dữ liệu. * Làm tròn kết quả đến 3 chữ số thập phân. |
| Dòng 11 | Hiển thị bảng Skewness/Kurtosis (Bảng 2.3.4). |
| Dòng 12-19 | Định nghĩa hàm
safe_shapiro: Hàm tùy chỉnh thực hiện kiểm định
Shapiro–Wilk (kiểm định tính chuẩn) một cách an toàn.
* Dòng 14-16: Lọc NA/Inf và kiểm tra điều kiện cần thiết: cần ít nhất 3 giá trị duy nhất và 3 quan sát để kiểm định. * Dòng 17: Thực hiện kiểm định shapiro.test(x). |
| Dòng 20-25 | Tính Shapiro–Wilk: * map(vars2 %>% select(-Nam), safe_shapiro):
Áp dụng hàm safe_shapiro cho các biến. * Gom kết quả (W và p-value) thành khung dữ liệu ( tibble) và làm
tròn. |
| Dòng 26 | Hiển thị bảng Shapiro–Wilk (Bảng 2.3.5, mang tính tham khảo do cỡ mẫu nhỏ n=11). |
Nhận xét:
TongTaiSan, SL_CoPhieu_LuuHanh,
VonHoa_TyVND, và TyLe_CFO_Tren_LNST có
\(p < 0.05\), cho
thấy bác bỏ giả thuyết phân phối chuẩn.1. library(dplyr); library(knitr); library(tibble)
2. cagr <- function(x) {
3. x_clean <- x[!is.na(x)]
4. if (length(x_clean) < 2 || x_clean[1] == 0) {
5. return(NA_real_) }
6. return((x_clean[length(x_clean)] / x_clean[1]) ^ (1 / (length(x_clean) - 1)) - 1) }
7. res_cagr <- tibble::tibble(
8. ChiSo = c("CAGR_TongTaiSan","CAGR_VonChuSoHuu","CAGR_NoPhaiTra","CAGR_Gia"),
9. GiaTri = c(cagr(vars2$TongTaiSan), cagr(vars2$VonChuSoHuu),
10. cagr(vars2$NoPhaiTra), cagr(vars2$Gia_DongCua_CuoiNam)))
11. range_de <- diff(range(vars2$TySoNo_Tren_VCSH, na.rm = TRUE))
12. knitr::kable(res_cagr,caption = "Bảng 2.3.6: CAGR giai đoạn (2014–2024)",
13. col.names = c("Chỉ số", "Giá trị"), align = "l")| Chỉ số | Giá trị |
|---|---|
| CAGR_TongTaiSan | 0.043 |
| CAGR_VonChuSoHuu | 0.050 |
| CAGR_NoPhaiTra | 0.023 |
| CAGR_Gia | 0.065 |
1. knitr::kable(
2. tibble::tibble(ChiSo="Biến thiên D/E (max-min)", GiaTri=range_de),
3. caption = "Bảng 2.3.7: Biến thiên D/E (max–min)",
4. col.names = c("Chỉ số", "Giá trị"), align = "l")| Chỉ số | Giá trị |
|---|---|
| Biến thiên D/E (max-min) | 0.256 |
| Dòng code | Giải thích |
|---|---|
| Dòng 1 | Tải các gói cần thiết: dplyr,
knitr, tibble. |
| Dòng 2-6 | Định nghĩa hàm cagr: Hàm
tính Tốc độ tăng trưởng kép hàng năm (CAGR). * Dòng 3: Lọc NA. * Dòng 4: Kiểm tra điều kiện tính (phải có ít nhất 2 điểm dữ liệu và điểm đầu tiên khác 0). * Dòng 5: Công thức tính CAGR: \((\text{Giá trị cuối} / \text{Giá trị đầu})^{\frac{1}{\text{Số năm}}} - 1\). |
| Dòng 7-10 | Tính CAGR cho các chỉ số chính: Áp
dụng hàm cagr cho TongTaiSan,
VonChuSoHuu, NoPhaiTra, và
Gia_DongCua_CuoiNam. Kết quả được lưu vào
res_cagr. |
| Dòng 11 | Tính Biến thiên D/E: Tính khoảng biến
thiên (diff(range(...))) của TySoNo_Tren_VCSH
(D/E). |
| Dòng 12-14 | Hiển thị bảng CAGR (Bảng 2.3.6). |
| Dòng 15-18 | Hiển thị bảng Biến thiên D/E (Bảng 2.3.7). |
Nhận xét:
VonChuSoHuu (5.0%/năm) tăng trưởng nhanh hơn
TongTaiSan (4.3%/năm) và NoPhaiTra
(2.3%/năm).1. thres <- c(0.3, 0.5, 1.0)
2. cnt <- sapply(thres, function(t) sum(vars2$TySoNo_Tren_VCSH > t, na.rm=TRUE))
3. kable(tibble(Nguong = thres, SoNam_VuotNguong = as.integer(cnt)),
4. caption="Bảng 2.3.8: Số năm D/E vượt các ngưỡng quản trị (0.3; 0.5; 1.0)")| Nguong | SoNam_VuotNguong |
|---|---|
| 0.3 | 9 |
| 0.5 | 1 |
| 1.0 | 0 |
| Dòng code | Giải thích |
|---|---|
| Dòng 1 | Khai báo vector thres chứa các
ngưỡng quản trị rủi ro phổ biến cho tỷ lệ Nợ/Vốn Chủ Sở
Hữu (D/E): 0.3, 0.5, và 1.0. |
| Dòng 2 | Đếm số năm vượt ngưỡng: * Dùng sapply(thres, function(t) ...) để lặp qua từng ngưỡng trong
thres. * sum(vars2$TySoNo_Tren_VCSH > t, ...):
Đếm số lượng năm mà TySoNo_Tren_VCSH lớn
hơn ngưỡng \(t\) đang
xét. |
| Dòng 3-5 | Tạo và hiển thị bảng: Tạo khung dữ
liệu với cột Nguong và SoNam_VuotNguong
(chuyển sang kiểu số nguyên) và hiển thị bằng kable. |
Nhận xét:
1. df_yoy <- vars2 %>%
2. arrange(Nam) %>%
3. mutate(
4. TTS_YoY = (TongTaiSan/lag(TongTaiSan)-1)*100,
5. VCSH_YoY = (VonChuSoHuu/lag(VonChuSoHuu)-1)*100,
6. No_YoY = (NoPhaiTra/lag(NoPhaiTra)-1)*100,
7. Gia_YoY = (Gia_DongCua_CuoiNam/lag(Gia_DongCua_CuoiNam)-1)*100)
8. kable(df_yoy, caption = "Bảng 2.3.9: Biến động YoY (%) của Tài sản, Vốn chủ, Nợ và Giá")| Nam | TongTaiSan | NoPhaiTra | VonChuSoHuu | TySoNo_Tren_VCSH | Gia_DongCua_CuoiNam | SL_CoPhieu_LuuHanh | VonHoa_TyVND | P_E_CuoiNam | CFO | TyLe_CFO_Tren_LNST | TTS_YoY | VCSH_YoY | No_YoY | Gia_YoY |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 2014 | 53791407348105 | 16112058787504 | 37679348560601 | 0.428 | 34154 | 1977965757 | 67556 | 4.78 | 16701449198611 | 1.183 | NA | NA | NA | NA |
| 2015 | 56714606287288 | 13825543405185 | 42889062882103 | 0.322 | 18129 | 67825115 | 1230 | 4.12 | 9126872112300 | 30.583 | 5.434 | 13.826 | -14.19 | -46.92 |
| 2016 | 56753853518438 | 15910005640211 | 40843847878227 | 0.390 | 33089 | 22563218 | 747 | 4.90 | 4942224182550 | 32.460 | 0.069 | -4.769 | 15.08 | 82.52 |
| 2017 | 61889343342437 | 18617834577626 | 43271508764811 | 0.430 | 56311 | 50646506 | 2852 | 11.28 | 14385549919558 | 56.876 | 9.049 | 5.944 | 17.02 | 70.18 |
| 2018 | 62614420245293 | 15747295132679 | 46867125112614 | 0.336 | 52232 | 43071660 | 2250 | 8.84 | 12421887654437 | 48.790 | 1.172 | 8.309 | -15.42 | -7.25 |
| 2019 | 621787389634 | 12564256032003 | 49614531357631 | 0.253 | 58210 | 29860515 | 1738 | 9.48 | 12680818093290 | 69.142 | -99.007 | 5.862 | -20.21 | 11.45 |
| 2020 | 63208401030103 | 13708720044649 | 49499680985454 | 0.277 | 56436 | 1950088362 | 110056 | 14.01 | 7330938831075 | 0.933 | 10065.597 | -0.231 | 9.11 | -3.05 |
| 2021 | 78768704688654 | 26575344013434 | 52192730675130 | 0.509 | 64672 | 1991038811 | 128765 | 14.85 | 7594281226199 | 0.876 | 24.617 | 5.441 | 93.86 | 14.59 |
| 2022 | 82662652366363 | 21489088811222 | 61173563555141 | 0.351 | 70096 | 1934673450 | 135613 | 9.16 | 12792070499188 | 0.864 | 4.944 | 17.207 | -19.14 | 8.39 |
| 2023 | 87754455330448 | 22455835056055 | 65298620274393 | 0.344 | 64830 | 2334278087 | 151332 | 13.04 | 13827044652491 | 1.191 | 6.160 | 6.743 | 4.50 | -7.51 |
| 2024 | 81854881408158 | 20283886142528 | 61570995265630 | 0.329 | 64160 | 2388316502 | 153235 | 14.74 | 9035210701364 | 0.869 | -6.723 | -5.709 | -9.67 | -1.03 |
| Dòng code | Giải thích |
|---|---|
| Dòng 1 | Sắp xếp dữ liệu: Sao chép
vars2 và sắp xếp theo Nam để chuẩn bị tính
toán YoY. |
| Dòng 2-5 | Tính tăng trưởng YoY: * Sử dụng hàm mutate để tạo 4 cột tăng trưởng YoY (theo tỷ lệ phần
trăm): * TTS_YoY: Tăng trưởng Tổng
Tài sản. * VCSH_YoY: Tăng trưởng Vốn
Chủ Sở Hữu. * No_YoY: Tăng trưởng Nợ
Phải Trả. * Gia_YoY: Tăng trưởng Giá
Đóng Cửa Cuối Năm. * Công thức chung là \((\text{Giá trị hiện tại} / \text{lag}(\text{Giá trị năm trước}) - 1) \times 100\). |
| Dòng 6 | Hiển thị bảng dữ liệu đã được bổ sung các cột tăng
trưởng YoY bằng kable. |
Nhận xét:
Bảng 2.3.9 cho thấy chu kỳ tăng trưởng và biến động YoY của cấu trúc tài chính và giá cổ phiếu GAS:
1. library(dplyr); library(knitr); library(rlang)
2. vars2 <- GAS_data_final %>%
3. dplyr::select(
4. Nam,
5. TongTaiSan, NoPhaiTra, VonChuSoHuu, TySoNo_Tren_VCSH,
6. Gia_DongCua_CuoiNam, SL_CoPhieu_LuuHanh,
7. VonHoa_TyVND, P_E_CuoiNam, CFO, TyLe_CFO_Tren_LNST)
8. detect_outliers_tukey <- function(x) {
9. if (is.numeric(x)) {
10. q1 <- quantile(x, 0.25, na.rm = TRUE)
11. q3 <- quantile(x, 0.75, na.rm = TRUE)
12. iqr <- q3 - q1
13. lower <- q1 - 1.5 * iqr
14. upper <- q3 + 1.5 * iqr
15. outlier_flags <- (x < lower | x > upper)
16. return(outlier_flags)} else {
17. return(rep(FALSE, length(x)))}}
18. outlier_matrix <- sapply(vars2 %>% dplyr::select(-Nam), detect_outliers_tukey)
19. outlier_summary <- data.frame(
20. Bien = colnames(outlier_matrix),
21. SoLuong_NgoaiLai = colSums(outlier_matrix, na.rm = TRUE),
22. TyLe_NgoaiLai = round(colMeans(outlier_matrix, na.rm = TRUE) * 100, 1))
23. knitr::kable(outlier_summary, caption = "Bảng 2.3.10: Số lượng và tỷ lệ ngoại lai theo quy tắc Tukey")| Bien | SoLuong_NgoaiLai | TyLe_NgoaiLai | |
|---|---|---|---|
| TongTaiSan | TongTaiSan | 1 | 9.1 |
| NoPhaiTra | NoPhaiTra | 0 | 0.0 |
| VonChuSoHuu | VonChuSoHuu | 0 | 0.0 |
| TySoNo_Tren_VCSH | TySoNo_Tren_VCSH | 0 | 0.0 |
| Gia_DongCua_CuoiNam | Gia_DongCua_CuoiNam | 0 | 0.0 |
| SL_CoPhieu_LuuHanh | SL_CoPhieu_LuuHanh | 0 | 0.0 |
| VonHoa_TyVND | VonHoa_TyVND | 0 | 0.0 |
| P_E_CuoiNam | P_E_CuoiNam | 0 | 0.0 |
| CFO | CFO | 0 | 0.0 |
| TyLe_CFO_Tren_LNST | TyLe_CFO_Tren_LNST | 0 | 0.0 |
1. outlier_details <- list()
2. for (var in colnames(outlier_matrix)) {
3. if (any(outlier_matrix[, var], na.rm = TRUE)) {
4. outlier_details[[var]] <- vars2 %>%
5. dplyr::filter(outlier_matrix[, var]) %>%
6. dplyr::select(Nam, !!rlang::sym(var))}}
7. if (length(outlier_details) > 0) {
8. cat("\nCác giá trị ngoại lai phát hiện theo Tukey rule:\n")
9. for (var in names(outlier_details)) {
10. cat(paste0("\n Chi tiết ", var, " \n"))
11. print(knitr::kable(outlier_details[[var]],
12. align = "l",
13. caption = paste("Chi tiết Outlier cho biến", var)))
14. cat("\n")}
15. } else {cat("Không phát hiện ngoại lai nào theo quy tắc Tukey.\n")}Các giá trị ngoại lai phát hiện theo Tukey rule:
Chi tiết TongTaiSan
| Nam | TongTaiSan |
|---|---|
| 2019 | 621787389634 |
| Dòng code | Giải thích |
|---|---|
| Dòng 1 | Tải các gói cần thiết. |
| Dòng 2-6 | Chọn lọc biến: Chọn các biến thuộc
Nhóm 2 (trừ Nam) để phân tích ngoại lai. |
| Dòng 7-16 | Định nghĩa hàm
detect_outliers_tukey: Hàm tùy chỉnh để phát hiện
ngoại lai theo Quy tắc Tukey (IQR Rule): * Tính Q1 (25%), Q3 (75%), và IQR (Q3 - Q1). * Ngưỡng Dưới (Lower): \(Q1 - 1.5 \times IQR\). * Ngưỡng Trên (Upper): \(Q3 + 1.5 \times IQR\). * Hàm trả về một vector TRUE/FALSE cho biết giá trị nào là
ngoại lai. |
| Dòng 17 | Áp dụng phát hiện ngoại lai: Áp dụng
hàm detect_outliers_tukey cho tất cả các cột trừ
Nam và lưu kết quả là ma trận
TRUE/FALSE. |
| Dòng 18-22 | Tạo bảng tóm tắt ngoại lai: Tạo khung
dữ liệu với tên biến, số lượng ngoại lai (colSums) và tỷ lệ
ngoại lai (colMeans). |
| Dòng 23 | Hiển thị bảng tóm tắt ngoại lai (Bảng 2.3.10). |
| Dòng 24-34 | Trình bày chi tiết ngoại lai: * Lặp qua ma trận ngoại lai. * Nếu một cột có ngoại lai ( if (any(...))), lọc ra các hàng ngoại lai và hiển thị chi
tiết (Nam và giá trị ngoại lai). |
Nhận xét:
Phát hiện ngoại lai (Bảng 2.3.10): Chỉ có
một ngoại lai duy nhất (chiếm 9.1% dữ liệu) được phát
hiện ở biến TongTaiSan tại năm
2019 (\(\approx 621.8\) tỷ
VND).
Các biến khác: Tất cả các biến còn lại
(NoPhaiTra, VonChuSoHuu,
P_E_CuoiNam, CFO, v.v.) đều không có
ngoại lai.
Kết luận: Dữ liệu GAS có phân phối ổn định và đồng nhất. Ngoại lai duy nhất năm 2019 là một bất thường rõ rệt (có thể do lỗi ghi nhận hoặc tái cơ cấu kế toán) và cần được xử lý/loại trừ trong các phân tích tương quan và hồi quy để tránh sai lệch.
Tóm tắt phát hiện chính của phân tích cấu trúc, định giá và dòng tiền
Dưới đây là tóm tắt các phát hiện chính từ việc phân tích thống kê mô tả, hình dạng phân phối và kiểm tra ngoại lai của nhóm biến Cấu trúc tài chính, Định giá và Dòng tiền của GAS giai đoạn 2014–2024:
VonChuSoHuu
(5.0%/năm) tăng trưởng nhanh hơn TongTaiSan (4.3%/năm) và
NoPhaiTra (2.3%/năm), khẳng định việc mở rộng quy mô chủ
yếu dựa vào nguồn vốn nội tại.TongTaiSan năm 2019,
nguyên nhân có thể do lỗi ghi nhận hoặc thay đổi cấu trúc kế toán. Tất
cả các biến quan trọng khác đều không có ngoại lai theo quy tắc
Tukey.1. data_corr <- GAS_data_final %>% filter(TongTaiSan > 1e12)
2. vars_corr <- data_corr %>%
3. select(
4. TongTaiSan, NoPhaiTra, VonChuSoHuu, TySoNo_Tren_VCSH,
5. Gia_DongCua_CuoiNam, VonHoa_TyVND, P_E_CuoiNam,
6. CFO, TyLe_CFO_Tren_LNST)
7. str(vars_corr)'data.frame': 10 obs. of 9 variables:
$ TongTaiSan : num 53791407348105 56714606287288 56753853518438 61889343342437 62614420245293 ...
$ NoPhaiTra : num 16112058787504 13825543405185 15910005640211 18617834577626 15747295132679 ...
$ VonChuSoHuu : num 37679348560601 42889062882103 40843847878227 43271508764811 46867125112614 ...
$ TySoNo_Tren_VCSH : num 0.428 0.322 0.39 0.43 0.336 ...
$ Gia_DongCua_CuoiNam: num 34154 18129 33089 56311 52232 ...
$ VonHoa_TyVND : num 67556 1230 747 2852 2250 ...
$ P_E_CuoiNam : num 4.78 4.12 4.9 11.28 8.84 ...
$ CFO : num 16701449198611 9126872112300 4942224182550 14385549919558 12421887654437 ...
$ TyLe_CFO_Tren_LNST : num 1.18 30.58 32.46 56.88 48.79 ...
| Dòng code | Giải thích |
|---|---|
| Dòng 1 | Xử lý ngoại lai: Tạo khung dữ liệu
data_corr bằng cách lọc (filter) các hàng có
TongTaiSan lớn hơn 1 nghìn tỷ (\(10^{12}\)). Thao tác này có tác dụng loại
bỏ năm 2019 (năm có tổng tài sản là outlier duy nhất
\(\approx 621\) tỷ VND). |
| Dòng 2-4 | Chọn biến: Chọn các biến thuộc Nhóm 2
(Cấu trúc, Định giá, Dòng tiền) từ dữ liệu đã lọc
(data_corr) và lưu vào vars_corr để chuẩn bị
tính tương quan. |
| Dòng 5 | Hiển thị cấu trúc dữ liệu (str) của bộ dữ
liệu đã được làm sạch và chọn biến. |
Nhận xét:
vars_corr hiện đã sẵn sàng và tập trung vào
các biến chính về cấu trúc vốn, định giá và dòng tiền
để phân tích mối quan hệ tuyến tính.1. cor_matrix <- cor(vars_corr, use = "pairwise.complete.obs")
2. library(knitr)
3. kable(round(cor_matrix, 3), caption = "Bảng 2.3.11: Ma trận Tương quan Pearson (n=10)")| TongTaiSan | NoPhaiTra | VonChuSoHuu | TySoNo_Tren_VCSH | Gia_DongCua_CuoiNam | VonHoa_TyVND | P_E_CuoiNam | CFO | TyLe_CFO_Tren_LNST | |
|---|---|---|---|---|---|---|---|---|---|
| TongTaiSan | 1.000 | 0.807 | 0.967 | -0.013 | 0.827 | 0.839 | 0.705 | 0.039 | -0.539 |
| NoPhaiTra | 0.807 | 1.000 | 0.629 | 0.576 | 0.706 | 0.641 | 0.589 | 0.074 | -0.402 |
| VonChuSoHuu | 0.967 | 0.629 | 1.000 | -0.267 | 0.783 | 0.826 | 0.672 | 0.020 | -0.536 |
| TySoNo_Tren_VCSH | -0.013 | 0.576 | -0.267 | 1.000 | 0.063 | -0.056 | 0.016 | 0.130 | 0.049 |
| Gia_DongCua_CuoiNam | 0.827 | 0.706 | 0.783 | 0.063 | 1.000 | 0.723 | 0.843 | 0.130 | -0.360 |
| VonHoa_TyVND | 0.839 | 0.641 | 0.826 | -0.056 | 0.723 | 1.000 | 0.694 | 0.025 | -0.882 |
| P_E_CuoiNam | 0.705 | 0.589 | 0.672 | 0.016 | 0.843 | 0.694 | 1.000 | -0.130 | -0.366 |
| CFO | 0.039 | 0.074 | 0.020 | 0.130 | 0.130 | 0.025 | -0.130 | 1.000 | 0.058 |
| TyLe_CFO_Tren_LNST | -0.539 | -0.402 | -0.536 | 0.049 | -0.360 | -0.882 | -0.366 | 0.058 | 1.000 |
| Dòng code | Giải thích |
|---|---|
| Dòng 1 | Tính ma trận tương quan: * Sử dụng hàm cor() để tính ma trận tương quan Pearson cho các
biến trong vars_corr (dữ liệu đã được làm sạch, n=10). * use = "pairwise.complete.obs": Chỉ định
sử dụng tất cả các cặp quan sát hợp lệ (nếu có NA) để tính tương
quan. |
| Dòng 2-3 | Hiển thị ma trận tương quan, làm tròn đến 3 chữ số thập
phân bằng kable. |
Nhận xét – Bảng 2.3.11 (Ma trận tương quan Pearson)
Phân tích ma trận tương quan cho thấy mối quan hệ chặt chẽ và hợp lý giữa các nhóm biến Cấu trúc, Định giá và Dòng tiền của GAS (n=10, sau khi loại bỏ outlier 2019):
Kết luận: Các mối tương quan đều hợp lý và có ý nghĩa kinh tế. Kết quả cho thấy GAS có cấu trúc tài chính cân bằng, định giá chủ yếu dựa trên quy mô vốn, và chất lượng dòng tiền cao đóng vai trò điều tiết trong mối quan hệ định giá.
1. cor_TTS_VCSH <- cor(vars_corr$TongTaiSan, vars_corr$VonChuSoHuu)
2. cor_No_VCSH <- cor(vars_corr$NoPhaiTra, vars_corr$VonChuSoHuu)
3. cor_TTS_No <- cor(vars_corr$TongTaiSan, vars_corr$NoPhaiTra)
4. cat(paste("Tương quan TTS–VCSH:", round(cor_TTS_VCSH,3),
5. "| Tương quan Nợ–VCSH:", round(cor_No_VCSH,3),
6. "| Tương quan TTS–Nợ:", round(cor_TTS_No,3)))Tương quan TTS–VCSH: 0.967 | Tương quan Nợ–VCSH: 0.629 | Tương quan TTS–Nợ: 0.807
| Dòng code | Giải thích |
|---|---|
| Dòng 1-3 | Tính toán hệ số tương quan: Tính hệ số
tương quan Pearson giữa 3 cặp biến cấu trúc vốn chính: * cor_TTS_VCSH: Tổng Tài sản và Vốn Chủ Sở
Hữu. * cor_No_VCSH: Nợ phải trả và
Vốn chủ sở hữu. * cor_TTS_No: Tổng
Tài sản và Nợ Phải Trả. |
| Dòng 4-6 | In ra kết quả tương quan của 3 cặp, làm tròn đến 3 chữ số thập phân. |
Nhận xét:
Tổng kết: Bộ ba tương quan này xác nhận cấu trúc vốn của GAS là vững chắc, bền vững, và dựa trên sự tự chủ về tài chính.
1. stopifnot(exists("data_corr"))
2. suppressPackageStartupMessages({library(dplyr); library(tibble); library(knitr)})
3. key_corr <- tibble(
4. Cap = c("ROE–Giá", "ROE–CFO", "CFO–P/E", "TTS–VCSH", "D/E–ROE"),
5. r = c(
6. cor(data_corr$ROE_Nam, data_corr$Gia_DongCua_CuoiNam, use = "complete.obs"),
7. cor(data_corr$ROE_Nam, data_corr$CFO, use = "complete.obs"),
8. cor(data_corr$CFO, data_corr$P_E_CuoiNam, use = "complete.obs"),
9. cor(data_corr$TongTaiSan, data_corr$VonChuSoHuu, use = "complete.obs"),
10. cor(data_corr$TySoNo_Tren_VCSH, data_corr$ROE_Nam, use = "complete.obs"))) %>%
11. mutate(
12. Huong = ifelse(r >= 0, "Dương", "Âm"),
13. MucDo = cut(abs(r),
14. breaks = c(0, 0.3, 0.5, 0.7, 1),
15. labels = c("Yếu", "Vừa", "Mạnh", "Rất mạnh"),
16. include.lowest = TRUE),
17. r = round(r, 3)) %>%
18. arrange(desc(abs(r)))
19. kable(key_corr, caption = "Bảng 2.3.12: Tương quan tổng hợp các cặp biến chính")| Cap | r | Huong | MucDo |
|---|---|---|---|
| TTS–VCSH | 0.967 | Dương | Rất mạnh |
| ROE–CFO | 0.434 | Dương | Vừa |
| ROE–Giá | 0.276 | Dương | Yếu |
| D/E–ROE | 0.153 | Dương | Yếu |
| CFO–P/E | -0.130 | Âm | Yếu |
| Dòng code | Giải thích |
|---|---|
| Dòng 1 | Tải các gói cần thiết và kiểm tra sự tồn tại của
data_corr. |
| Dòng 2-15 | Tính tương quan tổng hợp: * Dòng 3-10: Khai báo khung dữ liệu key_corr
và tính hệ số tương quan Pearson (cor) cho 5 cặp biến quan
trọng, sử dụng data_corr (dữ liệu đã làm sạch). * Dòng 11-14: Phân loại cường độ tương quan: * Huong: Phân loại
hướng tương quan (Dương/Âm). * MucDo:
Phân loại mức độ tương quan (Yếu/Vừa/Mạnh/Rất mạnh) dựa trên trị tuyệt
đối của r (quy tắc phổ biến: \(\le 0.3\) Yếu, \(\le 0.5\) Vừa, \(\le 0.7\) Mạnh, \(\le 1.0\) Rất mạnh). |
| Dòng 16 | Sắp xếp bảng theo trị tuyệt đối của r giảm
dần. |
| Dòng 17 | Hiển thị bảng tương quan tổng hợp (Bảng 2.3.12). |
Nhận xét:
Kết luận: Phân tích tương quan cho thấy GAS có cấu trúc tài chính vững chắc (được mô tả bởi TTS-VCSH) và thị trường đánh giá cao sự ổn định quy mô hơn là phản ứng mạnh mẽ với các biến động ngắn hạn trong hiệu quả sinh lời hay dòng tiền.
1. p1 <- ggplot(gas_idx, aes(Nam, idx_TTS)) +
2. geom_line(size=1) +
3. geom_point(size=2) +
4. geom_smooth(method="loess", se=FALSE, linetype="dashed") +
5. geom_hline(yintercept=100, linetype="dotted", color="grey40") +
6. geom_text(aes(label=round(idx_TTS,0)), vjust=-0.6, size=3) +
7. labs(title="(1) Chỉ số hóa tổng tài sản (2014=100)",
8. y="Index (2014=100)", x=NULL) + base_theme
9. p2 <- ggplot(gas_idx, aes(Nam, idx_VCSH)) +
10. geom_line(size=1) +
11. geom_point(size=2) +
12. geom_smooth(method="loess", se=FALSE, linetype="dashed") +
13. geom_hline(yintercept=100, linetype="dotted", color="grey40") +
14. geom_text(aes(label=round(idx_VCSH,0)), vjust=-0.6, size=3) +
15. labs(title="(2) Chỉ số hóa vốn chủ sở hữu (2014=100)",
16. y="Index (2014=100)", x=NULL) + base_theme
17. p3 <- ggplot(gas_idx, aes(Nam, idx_No)) +
18. geom_line(size=1) +
19. geom_point(size=2) +
20. geom_smooth(method="loess", se=FALSE, linetype="dashed") +
21. geom_hline(yintercept=100, linetype="dotted", color="grey40") +
22. geom_text(aes(label=round(idx_No,0)), vjust=-0.6, size=3) +
23. labs(title="(3) Chỉ số hóa nợ phải trả (2014=100)",
24. y="Index (2014=100)", x=NULL) + base_theme
25. p4 <- ggplot(gas, aes(Nam, TySoNo_Tren_VCSH)) +
26. geom_line(size=1) +
27. geom_point(size=2) +
28. geom_hline(yintercept=0.3, linetype="dashed", color="darkgreen") +
29. geom_hline(yintercept=0.5, linetype="dashed", color="orange") +
30. geom_text(aes(label=round(TySoNo_Tren_VCSH,2)), vjust=-0.6, size=3) +
31. labs(title="(4) Đòn bẩy D/E theo năm (kèm ngưỡng 0.3 | 0.5)",
32. y="D/E", x=NULL, subtitle="Đường đứt: 0.3 (xanh), 0.5 (cam)") + base_theme
33. gridExtra::grid.arrange(p1,p2,p3,p4, ncol=2)| Dòng code | Giải thích |
|---|---|
| Dòng 1-10 | Biểu đồ 1: Chỉ số hóa tổng tài sản
(p1): * Dòng 1-3: Vẽ biểu đồ đường ( geom_line, geom_point) của chỉ số hóa
idx_TTS theo Nam. * geom_smooth(method="loess", se=FALSE):
Thêm đường xu hướng làm mịn (Loess) để làm nổi bật xu hướng dài hạn.
* geom_hline(yintercept=100, ...):
Thêm đường tham chiếu tại 100 (Năm gốc 2014). * geom_text: Thêm nhãn giá trị chỉ số. |
| Dòng 11-19 | Biểu đồ 2: Chỉ số hóa vốn chủ sở hữu
(p2): * Vẽ biểu đồ tương tự, minh họa xu hướng tăng trưởng của idx_VCSH. |
| Dòng 20-28 | Biểu đồ 3: Chỉ số hóa nợ phải Ttả
(p3): * Vẽ biểu đồ tương tự, minh họa xu hướng tăng trưởng của idx_No. |
| Dòng 29-37 | Biểu đồ 4: Đòn bẩy D/E theo năm (p4):
* Vẽ biểu đồ đường của tỷ lệ TySoNo_Tren_VCSH. * Dòng 32-33: Thêm các đường tham chiếu ngang tại ngưỡng quản trị rủi ro 0.3 (xanh) và 0.5 (cam). * geom_text: Thêm nhãn giá trị
D/E. |
| Dòng 38 | Sắp xếp 4 biểu đồ thành 2 hàng x 2 cột bằng
gridExtra::grid.arrange. |
Nhận xét – Bộ biểu đồ (1)–(4): Xu hướng quy mô & cấu trúc vốn
Tăng trưởng bền vững (Biểu đồ 1 & 2): Cả Tổng tài sản và Vốn chủ sở hữu đều có xu hướng tăng đều đặn (đạt đỉnh \(\approx 163\% - 173\%\)), đặc biệt Vốn chủ sở hữu tăng tương đối tuyến tính, khẳng định nền tảng tài chính tự chủ và mở rộng quy mô dựa trên nội lực.
Quản trị nợ chủ động (Biểu đồ 3 & 4):
Lưu ý: Biểu đồ Tổng tài sản (1) có một điểm bất thường rõ rệt ở năm 2019 (chỉ số gần về 0), đây là outlier đã được phát hiện trước đó, cần được xử lý trong các mô hình.
1. p5 <- ggplot(gas_idx, aes(Nam, idx_Gia)) +
2. geom_line(size=1) +
3. geom_point(size=2) +
4. geom_smooth(method="loess", se=FALSE, linetype="dashed") +
5. geom_hline(yintercept=100, linetype="dotted", color="grey40") +
6. geom_text(aes(label=round(idx_Gia,0)), vjust=-0.6, size=3) +
7. labs(title="(5) Chỉ số hóa giá cổ phiếu (2014=100)",
8. y="Index", x=NULL) + base_theme
9. p6 <- ggplot(gas_idx, aes(Nam, idx_VonHoa)) +
10. geom_line(size=1) +
11. geom_point(size=2) +
12. geom_smooth(method="loess", se=FALSE, linetype="dashed") +
13. geom_hline(yintercept=100, linetype="dotted", color="grey40") +
14. geom_text(aes(label=round(idx_VonHoa,0)), vjust=-0.6, size=3) +
15. labs(title="(6) Chỉ số hóa vốn hoá (2014=100)",
16. y="Index", x=NULL) + base_theme
17. pe_band <- data.frame(xmin=min(gas$Nam)-.5,xmax=max(gas$Nam)+.5,ymin=8,ymax=12)
18. p7 <- ggplot(gas, aes(Nam, P_E_CuoiNam)) +
19. geom_rect(data=pe_band, inherit.aes=FALSE,
20. aes(xmin=xmin, xmax=xmax, ymin=ymin, ymax=ymax),
21. alpha=.1) +
22. geom_line(size=1) +
23. geom_point(size=2) +
24. geom_smooth(method="loess", se=FALSE, linetype="dashed") +
25. geom_text(aes(label=round(P_E_CuoiNam,1)), vjust=-0.6, size=3) +
26. labs(title="(7) P/E cuối năm (dải 8–12 đánh dấu vùng hợp lý)",
27. subtitle="Nền nhạt = dải P/E 8–12", y="P/E", x=NULL) + base_theme
28. ext_von <- extreme_labels(gas, "VonHoa_TyVND")
29. p8 <- ggplot(gas, aes(VonChuSoHuu/1e12, VonHoa_TyVND)) +
30. geom_point(size=3) +
31. geom_smooth(method="lm", se=FALSE, linetype="dashed") +
32. geom_rug(alpha=.2) +
33. geom_label_repel(data=ext_von, aes(label=Nam), size=3) +
34. labs(title="(8) Vốn hóa vs Vốn chủ sở hữu",
35. x="Vốn chủ (nghìn tỷ VND)", y="Vốn hóa (tỷ VND)",
36. subtitle="Quan hệ quy mô – định giá") + base_theme
37. gridExtra::grid.arrange(p5,p6,p7,p8, ncol=2)| Dòng code | Giải thích |
|---|---|
| Dòng 1-9 | Biểu đồ 5: Chỉ số hóa giá cổ phiếu
(p5): * Vẽ biểu đồ đường của chỉ số hóa idx_Gia (2014=100) theo Nam. * geom_line, geom_point: Biểu
diễn xu hướng. * geom_smooth(method="loess"): Thêm đường xu
hướng làm mịn. * geom_text: Thêm nhãn
giá trị chỉ số. |
| Dòng 10-18 | Biểu đồ 6: Chỉ số hóa vốn hóa (p6):
* Vẽ biểu đồ tương tự, minh họa xu hướng của idx_VonHoa. |
| Dòng 19-29 | Biểu đồ 7: P/E cuối năm (p7): * Dòng 19: Khai báo pe_band - vùng dữ liệu
(hình chữ nhật) đánh dấu P/E hợp lý (\(8 -
12\)). * geom_rect: Vẽ vùng
hình chữ nhật (nền xám nhạt) để làm nổi bật dải P/E hợp lý. * geom_line, geom_point,
geom_smooth: Vẽ diễn biến của
P_E_CuoiNam qua các năm. |
| Dòng 30-40 | Biểu đồ 8: Vốn hóa vs Vốn chủ sở hữu
(p8): * geom_point: Biểu đồ
phân tán (scatterplot) giữa VonChuSoHuu (trục X) và
VonHoa_TyVND (trục Y). * geom_smooth(method="lm", ...): Thêm đường
hồi quy tuyến tính (lm) để trực quan hóa
mối tương quan mạnh mẽ (r \(\approx
0.826\)). * geom_rug: Thêm các
vạch nhỏ sát trục X/Y để hiển thị mật độ dữ liệu. * geom_label_repel: Dán nhãn năm (Nam) cho
các điểm dữ liệu cực đoan (ext_von) để nhận diện. |
| Dòng 41 | Sắp xếp 4 biểu đồ thành 2 hàng x 2 cột bằng
gridExtra::grid.arrange. |
Nhận xét – Bộ biểu đồ (5)–(8): Diễn biến giá, định giá và quy mô thị trường
Giá và vốn hóa (Biểu đồ 5 & 6): Cả Giá cổ phiếu và Vốn hóa đều có xu hướng tăng mạnh (đạt đỉnh \(> 200\%\)) sau giai đoạn suy giảm 2015–2017. Tốc độ phục hồi vốn hóa cao hơn tốc độ tăng vốn chủ, phản ánh kỳ vọng tích cực và sự mở rộng định giá của thị trường.
Định giá P/E (Biểu đồ 7): P/E duy trì phần lớn trong dải hợp lý 8–12, chỉ vượt nhẹ trong giai đoạn 2020–2022. Điều này khẳng định thị trường định giá GAS một cách thận trọng và hợp lý, ít bị chi phối bởi yếu tố đầu cơ ngắn hạn.
Vốn hóa bám sát vốn chủ (Biểu đồ 8): Mối quan hệ tuyến tính chặt chẽ giữa Vốn hóa và Vốn Chủ Sở Hữu (r \(\approx 0.826\)) chứng minh thị trường định giá GAS dựa trên nền tảng vốn thực (giá trị sổ sách) chứ không phải dựa trên đòn bẩy.
Kết luận: GAS được thị trường đánh giá là một cổ phiếu “blue-chip” ổn định, với định giá hợp lý, có xu hướng tăng trưởng bám sát nền tảng tài chính nội tại.
1. p9 <- ggplot(gas_idx, aes(Nam, idx_CFO)) +
2. geom_line(size=1) +
3. geom_point(size=2) +
4. geom_smooth(method="loess", se=FALSE, linetype="dashed") +
5. geom_hline(yintercept=100, linetype="dotted", color="grey40") +
6. geom_text(aes(label=round(idx_CFO,0)), vjust=-0.6, size=3) +
7. labs(title="(9) Chỉ số hóa CFO (2014=100)", y="Index", x=NULL) + base_theme
8. p10 <- ggplot(gas_idx, aes(Nam, idx_CFO_LNST)) +
9. geom_line(size=1) +
10. geom_point(size=2) +
11. geom_smooth(method="loess", se=FALSE, linetype="dashed") +
12. geom_hline(yintercept=100, linetype="dotted", color="grey40") +
13. geom_text(aes(label=round(idx_CFO_LNST,0)), vjust=-0.6, size=3) +
14. labs(title="(10) Chỉ số hóa tỷ lệ CFO/LNST (2014=100)",
15. y="Index", x=NULL) + base_theme
16. p11 <- ggplot(gas, aes(CFO/1e12, P_E_CuoiNam)) +
17. geom_point(size=3) +
18. geom_smooth(method="lm", se=FALSE, linetype="dashed") +
19. geom_rug(alpha=.2) +
20. geom_label_repel(data=extreme_labels(gas,"CFO"), aes(label=Nam), size=3) +
21. labs(title="(11) CFO vs P/E (log-scale x)",
22. x="CFO (nghìn tỷ VND, log)", y="P/E") +
23. scale_x_log10() + base_theme
24. p12 <- ggplot(gas, aes(CFO/1e12, Gia_DongCua_CuoiNam)) +
25. geom_point(size=3) +
26. geom_smooth(method="lm", se=FALSE, linetype="dashed") +
27. geom_rug(alpha=.2) +
28. geom_label_repel(data=extreme_labels(gas,"Gia_DongCua_CuoiNam"), aes(label=Nam), size=3) +
29. labs(title="(12) CFO vs Giá cổ phiếu",
30. x="CFO (nghìn tỷ VND)", y="Giá (VND)") + base_theme
31. gridExtra::grid.arrange(p9,p10,p11,p12, ncol=2)| Dòng code | Giải thích |
|---|---|
| Dòng 1-9 | Biểu đồ 9: Chỉ số hóa CFO (p9): * Vẽ biểu đồ đường của chỉ số hóa idx_CFO (2014=100) theo
Nam. * geom_line,
geom_point, geom_smooth: Biểu diễn xu
hướng. |
| Dòng 10-18 | Biểu đồ 10: Chỉ số hóa tỷ lệ CFO/LNST
(p10): * Vẽ biểu đồ tương tự, minh họa xu hướng của idx_CFO_LNST (Chất lượng lợi nhuận). |
| Dòng 19-27 | Biểu đồ 11: CFO vs P/E (p11): * Biểu đồ phân tán giữa CFO (nghìn tỷ VND) và
P_E_CuoiNam. * geom_smooth(method="lm"): Thêm đường hồi
quy tuyến tính. * scale_x_log10():
Chuyển trục X (CFO) sang thang logarit để làm dịu độ
lệch và thấy rõ mối quan hệ hơn. |
| Dòng 28-36 | Biểu đồ 12: CFO vs Giá cổ phiếu (p12):
* Biểu đồ phân tán giữa CFO và
Gia_DongCua_CuoiNam. |
| Dòng 37 | Sắp xếp 4 biểu đồ thành 2 hàng x 2 cột bằng
gridExtra::grid.arrange. |
Nhận xét – Bộ biểu đồ (9)–(12): Dòng tiền và chất lượng lợi nhuận
Dòng tiền (CFO - Biểu đồ 9): Biến động mạnh và có tính chu kỳ rõ rệt (suy giảm sâu 2015–2016, phục hồi mạnh 2017–2018), cho thấy CFO nhạy cảm với các yếu tố chu kỳ kinh doanh (giá khí, tồn kho).
Chất lượng lợi nhuận (CFO/LNST - Biểu đồ 10): Tỷ lệ này biến động cực lớn ở các năm đầu, nhưng ổn định quanh mức \(\approx 1\) từ 2021 trở đi, khẳng định lợi nhuận kế toán có chất lượng cao và được hỗ trợ bởi dòng tiền thực.
CFO vs P/E (Biểu đồ 11): Đường hồi quy có độ dốc âm nhẹ (tương quan âm yếu \(\text{r} = -0.13\)). Kết luận: Khi dòng tiền hoạt động mạnh, thị trường có xu hướng định giá thấp hơn (P/E giảm) vì có niềm tin cao hơn vào chất lượng lợi nhuận, giảm nhu cầu trả premium định giá cao.
CFO vs Giá (Biểu đồ 12): Mối quan hệ dương yếu (\(\text{r} \approx 0.13\)). Thị trường phản ứng tích cực khi CFO cải thiện (Min 2015, Max 2022). Kết luận: Sức khỏe dòng tiền là một yếu tố quan trọng, nhưng không phải là yếu tố chi phối Giá cổ phiếu.
Tổng kết: Dòng tiền của GAS là ổn định, có tính chu kỳ, và chất lượng lợi nhuận được duy trì ở mức cao (CFO/LNST \(\approx 1\)). Thị trường phản ứng hợp lý với dòng tiền, nhưng mức tương quan yếu cho thấy Giá cổ phiếu còn bị chi phối bởi nhiều yếu tố vĩ mô và cấu trúc vốn.
1. p13 <- ggplot(gas, aes(TongTaiSan/1e12, Gia_DongCua_CuoiNam)) +
2. geom_point(size=3) +
3. geom_smooth(method="lm", se=FALSE, linetype="dashed") +
4. geom_rug(alpha=.2) +
5. geom_label_repel(data=extreme_labels(gas,"TongTaiSan"), aes(label=Nam), size=3) +
6. labs(title="(13) Giá vs Tổng tài sản",
7. x="Tổng tài sản (nghìn tỷ VND)", y="Giá (VND)") + base_theme
8. p14 <- ggplot(gas, aes(VonChuSoHuu/1e12, Gia_DongCua_CuoiNam)) +
9. geom_point(size=3) +
10. geom_smooth(method="lm", se=FALSE, linetype="dashed") +
11. geom_rug(alpha=.2) +
12. geom_label_repel(data=extreme_labels(gas,"VonChuSoHuu"), aes(label=Nam), size=3) +
13. labs(title="(14) Giá vs Vốn chủ",
14. x="Vốn chủ sở hữu (nghìn tỷ VND)", y="Giá (VND)") + base_theme
15. p15 <- ggplot(gas, aes(P_E_CuoiNam, Gia_DongCua_CuoiNam)) +
16. geom_point(size=3) +
17. geom_smooth(method="lm", se=FALSE, linetype="dashed") +
18. geom_smooth(method="loess", se=FALSE, linetype="dotted") +
19. geom_rug(alpha=.2) +
20. geom_label_repel(aes(label=Nam), size=3) +
21. labs(title="(15) P/E vs Giá: LM (đứt) & LOESS (chấm)",
22. x="P/E", y="Giá (VND)") + base_theme
23. p16 <- ggplot(gas, aes(TongTaiSan/1e12, VonHoa_TyVND)) +
24. geom_point(size=3) +
25. geom_smooth(method="lm", se=FALSE, linetype="dashed") +
26. geom_rug(alpha=.2) +
27. geom_label_repel(data=extreme_labels(gas,"VonHoa_TyVND"), aes(label=Nam), size=3) +
28. labs(title="(16) Vốn hóa vs Tổng tài sản",
29. x="Tổng tài sản (nghìn tỷ VND)", y="Vốn hóa (tỷ VND)") + base_theme
30. gridExtra::grid.arrange(p13,p14,p15,p16, ncol=2)| Dòng code | Giải thích |
|---|---|
| Dòng 1-10 | Biểu đồ 13: Giá vs Tổng tài sản (p13):
* Biểu đồ phân tán giữa TongTaiSan và
Gia_DongCua_CuoiNam. * geom_smooth(method="lm", ...): Thêm đường
hồi quy tuyến tính (LM). * geom_label_repel: Dán nhãn năm cho các
điểm cực trị (sử dụng extreme_labels). |
| Dòng 11-19 | Biểu đồ 14: Giá vs Vốn chủ sở hữu
(p14): * Biểu đồ phân tán giữa VonChuSoHuu và
Gia_DongCua_CuoiNam. * Mối tương quan rất mạnh và tuyến tính (r \(\approx 0.78\)) đã được xác nhận. |
| Dòng 20-28 | Biểu đồ 15: P/E vs Giá (p15): * Biểu đồ phân tán giữa P/E và Giá. * Thêm hai đường xu hướng: lm (tuyến tính, đứt nét) và
loess (phi tuyến tính, chấm), để so sánh
hình dạng mối quan hệ. |
| Dòng 29-37 | Biểu đồ 16: Vốn hóa vs Tổng tài sản
(p16): * Biểu đồ phân tán giữa TongTaiSan và
VonHoa_TyVND. |
| Dòng 38 | Sắp xếp 4 biểu đồ thành 2 hàng x 2 cột bằng
gridExtra::grid.arrange. |
Nhận xét – Bộ biểu đồ (13)–(16): Quan hệ giá trị thị trường và quy mô tài chính
Định giá bám sát giá trị sổ sách (Biểu đồ 14): Mối tương quan tuyến tính rất mạnh giữa Giá cổ phiếu và Vốn Chủ Sở Hữu (\(\text{r} \approx 0.78\)) khẳng định thị trường định giá GAS chủ yếu dựa trên quy mô vốn và giá trị nội tại (book value).
Giá và Tài sản (Biểu đồ 13 & 16): Mối quan hệ dương mạnh giữa Giá/Vốn hóa và Tổng Tài sản (\(\text{r} \approx 0.8\) - \(0.84\)) củng cố rằng tăng trưởng quy mô tài sản là động lực chính của định giá thị trường.
Ngưỡng định giá hợp lý (Biểu đồ 15): Mối quan hệ giữa P/E và Giá cho thấy đường xu hướng phi tuyến tính (LOESS) bắt đầu bão hòa sau mức P/E \(\approx 12 - 13\). Kết luận: Mức P/E này là ngưỡng định giá hợp lý/cân bằng của GAS, phản ánh một thị trường trưởng thành, không đầu cơ quá mức.
Tổng kết: Các biểu đồ xác nhận sự đồng pha mạnh mẽ giữa quy mô tài chính và giá trị thị trường của GAS, một đặc điểm của doanh nghiệp blue-chip có định giá bền vững.
1. library(dplyr); library(tidyr); library(ggplot2); library(scales);
2. library(corrplot); library(RColorBrewer)
3. df_share <- GAS_data_final %>%
4. transmute(Nam,
5. `Nợ/TTS` = NoPhaiTra / TongTaiSan,
6. `VCSH/TTS`= VonChuSoHuu / TongTaiSan) %>%
7. pivot_longer(-Nam, names_to = "Thanh_phan", values_to = "Ty_le")
8. p17 <- ggplot(df_share, aes(x = Nam, y = Ty_le, fill = Thanh_phan)) +
9. geom_area(position = "stack", alpha = 0.35, colour = "grey30", linewidth = 0.2) +
10. stat_summary(aes(group = Thanh_phan, label = after_stat(scales::percent(y, 0.1))),
11. fun = mean, geom = "text", position = position_stack(vjust = 0.5),
12. size = 3, colour = "black", check_overlap = TRUE) +
13. scale_y_continuous(labels = percent_format(accuracy = 1)) +
14. scale_x_continuous(breaks = seq(2014, 2024, by = 2)) + # bớt nhãn trục X
15. scale_fill_manual(values = c("Nợ/TTS" = "#E57373", "VCSH/TTS" = "#64B5F6")) +
16. labs(title = "Cơ cấu vốn (Nợ/TTS & VCSH/TTS) qua thời gian",
17. x = NULL, y = "Tỷ trọng") +
18. coord_cartesian(clip = "off") +
19. theme_minimal(base_size = 11) +
20. theme(plot.margin = margin(10, 20, 20, 20),
21. legend.position = "bottom",
22. axis.text.x = element_text(margin = margin(t = 4)))
23. vars_cor <- GAS_data_final %>%
24. filter(Nam != 2019) %>%
25. select(TongTaiSan, NoPhaiTra, VonChuSoHuu, TySoNo_Tren_VCSH,
26. Gia_DongCua_CuoiNam, VonHoa_TyVND, P_E_CuoiNam, CFO, TyLe_CFO_Tren_LNST)
27. cm <- cor(vars_cor, use = "pairwise.complete.obs")
28. col_palette <- colorRampPalette(brewer.pal(n = 8, name = "RdYlBu"))(100)
29. corrplot(cm, method = "color", type = "upper", col = rev(col_palette),
30. tl.cex = .8, number.cex = .7, addCoef.col = "black", mar = c(0,0,1,0),
31. title = "(18) Heatmap tương quan (n=10, lọc outlier 2019)")r 1. df_slope <- GAS_data_final %>% 2. mutate(across(c(TongTaiSan, VonChuSoHuu, NoPhaiTra, VonHoa_TyVND), 3. ~ .x / first(.x), 4. .names = "{.col}_Norm")) %>% 5. select(Nam, ends_with("_Norm")) %>% 6. pivot_longer(-Nam, names_to = "Chi_so", values_to = "Index") %>% 7. mutate(Chi_so_Label = case_when( 8. Chi_so == "TongTaiSan_Norm" ~ "TTS", 9. Chi_so == "VonChuSoHuu_Norm" ~ "VCSH", 10. Chi_so == "NoPhaiTra_Norm" ~ "Nợ", 11. Chi_so == "VonHoa_TyVND_Norm" ~ "Vốn hoá", 12. TRUE ~ Chi_so)) 13. p19 <- ggplot(df_slope, aes(x = Nam, y = Index, group = Chi_so_Label, colour = Chi_so_Label)) + 14. geom_line(linewidth = 1) + 15. geom_point(size = 2) + 16. geom_text_repel(data = subset(df_slope, Nam == min(Nam)), 17. aes(label = paste0(Chi_so_Label, ": ", round(Index, 2))), 18. size = 3.2, segment.alpha = .3, 19. direction = "y", force_pull = 0.1, xlim = c(NA, 2014)) + 20. geom_text_repel(data = subset(df_slope, Nam == max(Nam)), 21. aes(label = paste0(Chi_so_Label, ": ", round(Index, 2))), 22. size = 3.2, segment.alpha = .3, 23. direction = "y", force_pull = 0.1, xlim = c(2024, NA)) + 24. scale_x_continuous(breaks = c(2014, 2019, 2024), 25. expand = expansion(mult = c(0.2, 0.2))) + 26. labs(title = "(19) Tăng trưởng tương đối (2014 vs 2024) – TTS, VCSH, Nợ, Vốn hóa", 27. subtitle = "Chuẩn hóa theo giá trị năm 2014 = 1.0", 28. x = NULL, y = "Tỷ lệ so với năm đầu (2014 = 1.0)") + 29. coord_cartesian(ylim = c(0.8, NA)) + 30. theme_minimal(base_size = 11) + 31. theme(legend.position = "none") 32. df_pe <- GAS_data_final %>% select(Nam, P_E_CuoiNam) 33. pe_med <- median(df_pe$P_E_CuoiNam, na.rm = TRUE) 34. p20 <- ggplot(df_pe, aes(x = Nam, y = P_E_CuoiNam)) + 35. geom_segment(aes(x = Nam, xend = Nam, y = pe_med, yend = P_E_CuoiNam), 36. linewidth = 0.8, colour = "grey55") + 37. geom_point(size = 3, colour = "black") + 38. geom_hline(yintercept = pe_med, linetype = "dashed", colour = "grey35") + 39. geom_text(aes(label = round(P_E_CuoiNam, 1)), vjust = -0.9, size = 3, 40. colour = "black") + 41. ggrepel::geom_label_repel( 42. data = subset(df_pe, Nam %in% c(2015, 2021)), 43. aes(label = paste0(Nam, ": ", round(P_E_CuoiNam, 1))), 44. nudge_y = 1.2, size = 3, label.size = 0.2, fill = "white", 45. seed = 42, min.segment.length = 0) + 46. scale_x_continuous(breaks = seq(2014, 2024, by = 1)) + 47. scale_y_continuous(limits = c(0, max(df_pe$P_E_CuoiNam, na.rm = TRUE) + 2)) + 48. labs(title = "P/E cuối năm theo thời gian (đường đứt = Median)", 49. x = "Năm", y = "P/E") + 50. theme_minimal(base_size = 11) + 51. theme(plot.margin = margin(10, 20, 20, 20),axis.text.x = element_text(angle = 0, vjust = 1)) 52. gridExtra::grid.arrange(p17, p19, p20, ncol=2)
| Dòng code | Giải thích |
|---|---|
| Dòng 1 | Tải các gói cần thiết: tidyr (xử lý dữ
liệu), ggplot2, scales,
RColorBrewer. |
| Dòng 2-18 | Biểu đồ 17: Cơ cấu vốn (Diện tích xếp
chồng): * Dòng 2-5: Tính tỷ trọng Nợ/TTS và VCSH/TTS, sau đó chuyển dữ liệu sang
dạng dài (pivot_longer) để vẽ biểu đồ diện tích. * geom_area(position = "stack"): Vẽ biểu đồ
diện tích xếp chồng (stack area chart). * stat_summary(..., geom = "text"): Hiển thị
nhãn tỷ lệ phần trăm vào giữa mỗi phần diện tích. * scale_y_continuous(labels = percent_format):
Định dạng trục Y thành phần trăm. |
| Dòng 19-38 | Biểu đồ 19: Tăng trưởng tương đối (Slopegraph -
Biểu đồ độ dốc): * Dòng 19-24: Tính chỉ số hóa của 4 biến ( TTS,
VCSH, Nợ, Vốn hóa) bằng cách chia
cho giá trị Năm đầu tiên (first(.x)),
chuẩn hóa tất cả về \(2014 = 1.0\).
* Dòng 25-28: Chuyển dữ liệu sang dạng dài và đặt tên nhãn thân thiện. * geom_line,
geom_point: Vẽ biểu đồ đường song song. * geom_text_repel: Dán nhãn giá trị
đầu kỳ (2014) và cuối kỳ (2024) để
trực quan hóa sự tăng trưởng tương đối. * coord_cartesian(ylim = c(0.8, NA)): Cắt
trục Y (từ 0.8 trở lên) để làm nổi bật sự khác biệt về độ dốc. |
| Dòng 39-50 | Biểu đồ 20: P/E theo năm (Lollipop):
* Dòng 42: Tính Median P/E toàn bộ giai đoạn ( pe_med). * geom_segment: Vẽ biểu đồ
Lollipop (đường thẳng từ median đến điểm). * geom_point: Vẽ điểm giá trị P/E. * geom_hline: Thêm đường tham chiếu ngang
tại Median P/E. |
| Dòng 51 | Sắp xếp 3 biểu đồ (17, 19, 20) bằng
gridExtra::grid.arrange. |
Nhận xét:
Cơ cấu vốn (Biểu đồ 17): Tỷ trọng Vốn chủ sở hữu/TTS luôn chiếm ưu thế vượt trội so với Nợ/TTS (trừ năm 2019 outlier), xác nhận cấu trúc vốn rất thận trọng và rủi ro thấp.
Tăng trưởng tương đối (Biểu đồ 19 - Slopegraph):
P/E (Biểu đồ 20): P/E dao động quanh vùng Median và vùng hợp lý 8–12, chỉ đạt đỉnh cao nhất năm 2021. Sự dao động không cực đoan cho thấy định giá GAS là ổn định và ít bị ảnh hưởng bởi đầu cơ so với các doanh nghiệp có đòn bẩy cao.
1. library(ggplot2); library(dplyr); library(patchwork); library(ggrepel); library(corrplot); library(zoo)
2. options(scipen=999)
3. data_full <- GAS_data_final
4. data_trim <- GAS_data_final %>% filter(Nam != 2019)
5. p21 <- ggplot(data_full, aes(TongTaiSan/1e12, Gia_DongCua_CuoiNam)) +
6. geom_point(color="#0072B2", size=3) +
7. geom_smooth(method="lm", se=TRUE, color="black", linetype="dashed") +
8. geom_text_repel(aes(label=Nam), size=3) +
9. labs(title="(21) Giá cổ phiếu vs Tổng tài sản (bao gồm 2019)",
10. x="Tổng tài sản (nghìn tỷ VND)", y="Giá đóng cửa cuối năm (VND)") +
11. theme_minimal(base_size=7)
12. p22 <- ggplot(data_trim, aes(TongTaiSan/1e12, Gia_DongCua_CuoiNam)) +
13. geom_point(color="#009E73", size=3) +
14. geom_smooth(method="lm", se=TRUE, color="black", linetype="dashed") +
15. geom_text_repel(aes(label=Nam), size=3) +
16. labs(title="(22) Giá cổ phiếu vs Tổng tài sản (loại năm 2019)",
17. x="Tổng tài sản (nghìn tỷ VND)", y="Giá đóng cửa cuối năm (VND)") +
18. theme_minimal(base_size=7)
19. (p21 + p22) + plot_annotation(title="Hình 21–22: Kiểm chứng nhạy cảm mối quan hệ Giá ↔ Quy mô (TTS)")| Dòng code | Giải thích |
|---|---|
| Dòng 1 | Tải các gói cần thiết: patchwork (kết hợp
biểu đồ) và zoo (mặc dù không dùng trong khối này), và
options(scipen=999) để vô hiệu hóa ký hiệu khoa học. |
| Dòng 2-3 | Chuẩn bị dữ liệu: * data_full: Giữ nguyên bộ dữ liệu gốc (bao
gồm outlier 2019). * data_trim: Loại
bỏ outlier duy nhất (năm 2019) để kiểm chứng độ nhạy
cảm. |
| Dòng 4-11 | Biểu đồ 21: Với Outlier 2019
(p21): * Vẽ biểu đồ phân tán giữa TongTaiSan (chia \(10^{12}\) - nghìn tỷ VND) và
Gia_DongCua_CuoiNam. * geom_smooth(method="lm", se=TRUE): Thêm
đường hồi quy tuyến tính (LM) và dải tin cậy
(se=TRUE). * geom_text_repel: Dán nhãn năm cho các điểm
dữ liệu. |
| Dòng 12-19 | Biểu đồ 22: Loại bỏ Outlier 2019
(p22): * Vẽ biểu đồ tương tự, sử dụng dữ liệu đã loại bỏ năm 2019. |
| Dòng 20 | Kết hợp biểu đồ: Sử dụng
patchwork để hiển thị hai biểu đồ cạnh nhau và thêm tiêu đề
chung. |
Nhận xét: Kiểm chứng nhạy cảm mối quan hệ Giá \(\leftrightarrow\) Quy mô
Ảnh hưởng của Outlier (Hình 21): Khi bao gồm năm 2019 (outlier), đường hồi quy có độ dốc thấp hơn và khoảng tin cậy (dải xám) rộng hơn (độ bất định cao), cho thấy điểm ngoại lai này làm giảm độ chặt của mối quan hệ tuyến tính.
Mối quan hệ ổn định (Hình 22): Sau khi loại bỏ năm 2019, đường hồi quy trở nên dốc hơn và khoảng tin cậy hẹp lại, khẳng định mối quan hệ tuyến tính dương giữa tăng trưởng quy mô tài sản và định giá thị trường là ổn định và bền vững.
Kết luận: Năm 2019 là một điểm dữ liệu ảnh hưởng lớn (high leverage). Tuy nhiên, kết luận cốt lõi vẫn giữ nguyên: thị trường thưởng thêm giá trị cho quy mô tài sản lớn hơn; việc loại bỏ outlier là cần thiết để có được ước lượng hệ số hồi quy chính xác.
1. p23 <- ggplot(data_full, aes(log10(CFO/1e9), Gia_DongCua_CuoiNam)) +
2. geom_point(color="#56B4E9", size=3) +
3. geom_smooth(method="lm", se=TRUE, color="black") +
4. geom_smooth(method="loess", se=FALSE, color="red", linetype="dotted") +
5. geom_text_repel(aes(label=Nam), size=3) +
6. labs(title="(23) CFO (log) vs Giá cổ phiếu (2014–2024)",
7. x="log10(CFO, tỷ VND)", y="Giá cổ phiếu (VND)") +
8. theme_minimal(base_size=7)
9. p24 <- ggplot(data_full, aes(log10(CFO/1e9), P_E_CuoiNam)) +
10. geom_point(color="#E69F00", size=3) +
11. geom_smooth(method="lm", se=TRUE, color="black") +
12. geom_smooth(method="loess", se=FALSE, color="red", linetype="dotted") +
13. geom_text_repel(aes(label=Nam), size=3) +
14. labs(title="(24) CFO (log) vs P/E Cuối năm (2014–2024)",
15. x="log10(CFO, tỷ VND)", y="P/E cuối năm") +
16. theme_minimal(base_size=7)
17. (p23 + p24) + plot_annotation(title="Hình 23–24: Quan hệ Dòng tiền – Giá – Định giá")| Dòng code | Giải thích |
|---|---|
| Dòng 1 | Tải các gói cần thiết và vô hiệu hóa ký hiệu khoa học. |
| Dòng 2 | Giữ nguyên bộ dữ liệu gốc
(data_full). |
| Dòng 3-11 | Biểu đồ 23: CFO (log) vs Giá cổ phiếu:
* aes(log10(CFO/1e9), ...): Chuyển
trục X (CFO) sang thang logarit cơ số
10 (tính theo tỷ VND) để giảm độ lệch của biến CFO. * geom_smooth(method="lm"): Thêm đường hồi
quy tuyến tính (LM) và dải tin cậy. * geom_smooth(method="loess", ...): Thêm
đường xu hướng làm mịn (LOESS) để kiểm tra tính phi tuyến tính. |
| Dòng 12-20 | Biểu đồ 24: CFO (log) vs P/E Cuối năm:
* Vẽ biểu đồ tương tự, kiểm tra mối quan hệ giữa log(CFO) và P/E. |
| Dòng 21 | Kết hợp hai biểu đồ và thêm tiêu đề chung. |
**Nhận xét: Quan hệ Dòng tiền – Giá – Định giá (CFO, Giá, P/E)*
1. roll_fun <- function(x, y, k=3) rollapplyr(data=1:length(x), width=k,
2. FUN=function(i) cor(x[i], y[i], use="pairwise"), fill=NA)
3. roll_df <- data_full %>%
4. mutate(
5. r_Gia_TTS = roll_fun(Gia_DongCua_CuoiNam, TongTaiSan),
6. r_Gia_VCSH = roll_fun(Gia_DongCua_CuoiNam, VonChuSoHuu),
7. r_Gia_CFO = roll_fun(Gia_DongCua_CuoiNam, CFO),
8. r_PE_CFO = roll_fun(P_E_CuoiNam, CFO))
9. p25 <- ggplot(roll_df, aes(Nam, r_Gia_TTS)) +
10. geom_line(color="#0072B2", linewidth=1) +
11. geom_point(size=2) +
12. labs(title="(25) Rolling Corr (3 năm): Giá – Tổng tài sản",
13. y="Hệ số tương quan (r)", x="Năm") + ylim(-1,1) +
14. theme_minimal(base_size=7)
15. p26 <- ggplot(roll_df, aes(Nam, r_Gia_VCSH)) +
16. geom_line(color="#56B4E9", linewidth=1) +
17. geom_point(size=2) +
18. labs(title="(26) Rolling Corr (3 năm): Giá – Vốn chủ sở hữu",
19. y="Hệ số tương quan (r)", x="Năm") + ylim(-1,1) +
20. theme_minimal(base_size=7)
21. p27 <- ggplot(roll_df, aes(Nam, r_Gia_CFO)) +
22. geom_line(color="#E69F00", linewidth=1) +
23. geom_point(size=2) +
24. labs(title="(27) Rolling Corr (3 năm): Giá – CFO",
25. y="Hệ số tương quan (r)", x="Năm") + ylim(-1,1) +
26. theme_minimal(base_size=7)
27. p28 <- ggplot(roll_df, aes(Nam, r_PE_CFO)) +
28. geom_line(color="#009E73", linewidth=1) +
29. geom_point(size=2) +
30. labs(title="(28) Rolling Corr (3 năm): P/E – CFO",
31. y="Hệ số tương quan (r)", x="Năm") + ylim(-1,1) +
32. theme_minimal(base_size=7)
33. (p25 + p26) / (p27 + p28) + plot_annotation(title="Hình 25–28: Rolling correlation 3 năm")| Dòng code | Giải thích |
|---|---|
| Dòng 1 | Tải gói cần thiết: zoo (cung cấp hàm
rollapplyr để tính toán trên cửa sổ trượt). |
| Dòng 2-3 | Định nghĩa hàm roll_fun:
Hàm tùy chỉnh để tính hệ số tương quan di động (Rolling
Correlation). * rollapplyr(...): Áp dụng hàm
(cor) cho một cửa sổ (width=k) trượt trên
vector. * width=k (mặc định \(k=3\)): Kích thước cửa sổ trượt (3
năm). * rollapplyr: Đảm bảo kết quả
tương quan được đặt ở cuối cửa sổ (Right-aligned). |
| Dòng 4-9 | Tính Rolling Correlation: * Áp dụng hàm roll_fun cho 4 cặp biến chính, tạo 4 cột tương
quan di động (ví dụ: r_Gia_TTS là tương quan di động 3 năm
giữa Giá và Tổng Tài sản). |
| Dòng 10-29 | Tạo 4 biểu đồ xu hướng tương quan
(p25 - p28): * Vẽ biểu đồ đường ( geom_line, geom_point) cho từng cột tương
quan (r_Gia_TTS, r_Gia_VCSH,
r_Gia_CFO, r_PE_CFO) theo Nam.
* ylim(-1, 1): Cố định trục Y từ -1
đến 1 (phạm vi của hệ số tương quan). |
| Dòng 30 | Kết hợp 4 biểu đồ thành 2 hàng x 2 cột và thêm tiêu đề chung. |
Nhận xét: Rolling Correlation 3 năm
Giá vs Tổng tài sản (Hình 25): Tương quan biến động mạnh (từ \(\approx +1\) xuống \(\approx -1\) và phục hồi), phản ánh mối quan hệ này không ổn định và phụ thuộc vào chu kỳ thị trường và cấu trúc tài chính ngắn hạn.
Giá vs Vốn chủ sở hữu (Hình 26): Tương quan ổn định và dương trong hầu hết các giai đoạn (duy trì \(\ge 0.5\)), chỉ giảm nhẹ ở các pha điều chỉnh. Kết luận: Vốn Chủ Sở Hữu là chỉ báo ổn định và đáng tin cậy nhất để giải thích biến động giá GAS.
Giá vs CFO (Hình 27): Tương quan dao động mạnh (từ dương sang âm), cho thấy thị trường chỉ phản ứng tích cực với dòng tiền thật trong các chu kỳ tăng trưởng (tương quan dương) nhưng phản ứng ngược lại khi thị trường điều chỉnh (tương quan âm).
P/E vs CFO (Hình 28): Tương quan đảo pha rõ rệt. Chuyển từ dương (pha bùng nổ kỳ vọng 2016–2018) sang âm mạnh (pha ổn định/bình thường hóa sau 2019). Kết luận: Trong pha ổn định, CFO mạnh mẽ giúp điều chỉnh P/E về vùng hợp lý (tương quan âm).
Tổng kết: Mối tương quan của GAS không ổn định theo thời gian (Rolling Correlation), phản ánh tính chu kỳ của ngành năng lượng. Vốn chủ sở hữu là chỉ số cơ bản nhất quán nhất để dự báo giá, trong khi mối quan hệ CFO/P/E phản ánh quá trình bình thường hóa định giá sau các chu kỳ tăng trưởng nóng.
1. library(corrplot); library(RColorBrewer)
2. vars_cor <- c("TongTaiSan", "NoPhaiTra", "VonChuSoHuu", "TySoNo_Tren_VCSH",
3. "Gia_DongCua_CuoiNam", "VonHoa_TyVND", "P_E_CuoiNam",
4. "CFO", "TyLe_CFO_Tren_LNST")
5. data_full <- GAS_data_final %>% select(any_of(vars_cor))
6. data_trim <- GAS_data_final %>%
7. filter(Nam != 2019) %>%
8. select(any_of(vars_cor))
9. cor_full <- cor(data_full, use="pairwise.complete.obs")
10. cor_trim <- cor(data_trim, use="pairwise.complete.obs")
11. par(mfrow = c(1, 2))
12. col_palette <- colorRampPalette(brewer.pal(n = 8, name = "RdYlBu"))(100)
13. corrplot(cor_full, method = "color", type = "upper", addCoef.col = "black",
14. number.cex = 0.5, col = rev(col_palette), tl.cex = 0.5, tl.col = "black",
15. title = "(29) Heatmap Full Sample (n=11)", mar = c(0, 0, 1, 0))
16. corrplot(cor_trim, method = "color", type = "upper", addCoef.col = "black",
17. number.cex = 0.5, col = rev(col_palette), tl.cex = 0.5,
18. tl.col = "black", title = "(30) Heatmap Loại 2019 (n=10)", mar = c(0, 0, 1, 0))r 1. par(mfrow = c(1, 1))
| Dòng code | Giải thích |
|---|---|
| Dòng 1 | Tải các gói cần thiết. |
| Dòng 2-7 | Chuẩn bị dữ liệu và biến: * vars_cor: Khai báo vector các biến cần
tính tương quan. * data_full: Dữ liệu
gốc 11 năm. * data_trim: Dữ liệu đã
loại bỏ năm 2019 (outlier) và chọn biến. |
| Dòng 8-9 | Tính ma trận tương quan: * cor_full: Ma trận tương quan của dữ liệu
gốc (n=11). * cor_trim: Ma trận tương
quan của dữ liệu đã loại bỏ 2019 (n=10). |
| Dòng 10 | par(mfrow = c(1, 2)):
Thiết lập chế độ vẽ 2 biểu đồ cạnh nhau trên cùng một
màn hình đồ họa. |
| Dòng 11-16 | Vẽ Heatmap 1 (Full Sample): * corrplot(cor_full, ...): Vẽ heatmap cho ma
trận tương quan gốc (n=11). * type = "upper": Chỉ vẽ nửa trên ma trận.
* addCoef.col = "black": Hiển thị giá
trị tương quan. |
| Dòng 17-21 | Vẽ Heatmap 2 (Loại 2019): * Vẽ heatmap cho ma trận đã loại bỏ outlier (n=10). |
| Dòng 22 | par(mfrow = c(1, 1)):
Thiết lập lại chế độ vẽ về mặc định. |
Nhận xét: Ảnh hưởng của ngoại lai 2019 đến cấu trúc tương quan
1. library(car); library(dplyr)
2. model1 <- lm(Gia_DongCua_CuoiNam ~ TongTaiSan, data=data_full)
3. model2 <- lm(Gia_DongCua_CuoiNam ~ VonChuSoHuu, data=data_full)
4. model3 <- lm(Gia_DongCua_CuoiNam ~ CFO, data=data_full)
5. model4 <- lm(P_E_CuoiNam ~ CFO, data=data_full)
6. par(mfrow=c(2,2))
7. influencePlot(model1, id.method="noteworthy", main="(31) Influence: Giá~TTS") StudRes Hat CookD
2 -2.739 0.0967 0.2331
6 3.911 0.7662 9.6825
10 0.481 0.2044 0.0325
StudRes Hat CookD
1 -0.0642 0.274 0.000876
2 -3.6483 0.153 0.505989
4 1.4367 0.146 0.158012
10 -0.9533 0.367 0.265950
StudRes Hat CookD
1 -1.73 0.344 0.642
2 -2.60 0.118 0.274
3 -1.11 0.374 0.359
StudRes Hat CookD
1 -1.30 0.344 0.412
2 -1.69 0.118 0.158
3 -2.08 0.374 0.944
| Dòng code | Giải thích |
|---|---|
| Dòng 1 | Tải các gói cần thiết: car (cung cấp hàm
influencePlot) và dplyr. |
| Dòng 2-5 | Xây dựng các mô hình hồi quy tuyến tính
(LM): * model1: Giá \(\sim\) Tổng Tài sản
(TongTaiSan) * model2:
Giá \(\sim\) Vốn Chủ Sở Hữu
(VonChuSoHuu) * model3:
Giá \(\sim\) CFO * model4: P/E \(\sim\) CFO * Tất cả sử dụng dữ liệu đầy đủ ( data_full) bao gồm outlier 2019 để kiểm tra độ nhạy
cảm. |
| Dòng 6 | par(mfrow=c(2,2)): Thiết
lập chế độ vẽ 4 biểu đồ trên cùng một không gian (2
hàng x 2 cột). |
| Dòng 7-10 | Vẽ biểu đồ ảnh hưởng: * Hàm influencePlot() vẽ biểu đồ để xác định các điểm có ảnh
hưởng lớn (Outlier và Leverage). * Trục X: Hat-values (Đòn bẩy - leverage). * Trục Y: Studentized residuals (Sai số chuẩn hóa). * Kích thước bong bóng: Tỷ lệ với Cook’s Distance (Ảnh hưởng tổng thể). * id.method="noteworthy": Dán nhãn tự động
các điểm có Cook’s D cao. |
| Dòng 11 | par(mfrow=c(1,1)): Thiết
lập lại chế độ vẽ về mặc định. |
Nhận xét: Ảnh hưởng điểm dữ liệu tới mô hình hồi quy
Tổng kết: Phân tích ảnh hưởng xác nhận lại rằng năm 2019 là điểm ngoại lai hệ thống cần được loại trừ khi mô hình hóa Giá \(\sim\) Quy mô Tài sản, trong khi Vốn Chủ Sở Hữu là chỉ báo ổn định nhất.
1. library(ggplot2); library(ggrepel); library(dplyr); library(tidyr)
2. slope_data <- GAS_data_final %>%
3. arrange(Nam) %>%
4. mutate(VCSH_idx = VonChuSoHuu / first(VonChuSoHuu) * 100,
5. VonHoa_idx = VonHoa_TyVND / first(VonHoa_TyVND) * 100) %>%
6. select(Nam, VCSH_idx, VonHoa_idx) %>%
7. tidyr::pivot_longer(-Nam, names_to="ChiSo", values_to="Index")
8. ggplot(slope_data, aes(x=Nam, y=Index, color=ChiSo)) +
9. geom_line(linewidth=1) +
10. geom_point(size=2) +
11. geom_text_repel(aes(label=ifelse(Nam %in% c(min(Nam), max(Nam)),
12. paste0(Nam,": ", round(Index,0)),
13. "")), size=3) +
14. scale_color_manual(values=c("#0072B2","#E69F00"),
15. labels=c("Vốn chủ sở hữu","Vốn hoá thị trường")) +
16. labs(title="(35) Tăng trưởng chuẩn hoá theo 2014=100: Vốn chủ ↔ Vốn hoá",
17. x="Năm", y="Chỉ số (2014=100)", color="Chỉ tiêu") +
18. theme_minimal(base_size=11) +
19. theme(legend.position = "bottom")| Dòng code | Giải thích |
|---|---|
| Dòng 1 | Tải các gói cần thiết và thiết lập. |
| Dòng 2-6 | Tính chỉ số hóa và chuyển đổi dạng
dài: * Dòng 3-4: Tính chỉ số hóa (Index) cho VonChuSoHuu và VonHoa_TyVND bằng cách chia
cho giá trị năm đầu tiên (first(...)) và
nhân 100, chuẩn hóa về \(2014 =
100\). * Dòng 5-6: Chuyển dữ liệu sang dạng dài ( pivot_longer) để vẽ hai đường trên cùng một
biểu đồ. |
| Dòng 7-16 | Tạo biểu đồ đường chỉ số hóa: * geom_line và geom_point: Vẽ
biểu đồ đường và điểm. * geom_text_repel: Dán nhãn chỉ số tại
năm đầu (2014) và năm cuối (2024).
* scale_color_manual: Gán màu cụ thể
và đặt nhãn cho hai đường. * theme(legend.position = "bottom"): Đặt chú
giải ở dưới cùng. |
Nhận xét Hình 35: Tăng trưởng chuẩn hóa Vốn chủ \(\leftrightarrow\) Vốn hóa
Tăng trưởng vốn chủ sở hữu (Đường xanh): Duy trì tốc độ tăng đều và ổn định trong suốt giai đoạn, từ 100 điểm lên \(\approx 163\) điểm (CAGR \(\approx 5.0\%/năm\)). Đây là quỹ đạo tăng trưởng tài chính bền vững, phản ánh khả năng tích lũy vốn từ lợi nhuận giữ lại.
Biến động vốn hóa thị trường (Đường vàng): Phản ứng mạnh với thị trường. Sau giai đoạn suy giảm sâu (2015–2019), vốn hóa phục hồi và vượt trội so với vốn chủ, đạt \(\approx 227\) điểm năm 2024 (CAGR \(\approx 8.7\%/năm\)).
Định giá: Khoảng cách giữa đường vốn hóa (cao hơn) và vốn chủ (thấp hơn) sau năm 2021 thể hiện giá trị vượt trội thị trường. Vốn hóa tăng nhanh hơn đáng kể so với tài sản thực, cho thấy thị trường định giá GAS cao hơn giá trị sổ sách (P/B > 1), dựa trên kỳ vọng lợi nhuận và vị thế đầu ngành.
Kết luận: Phân tích này xác nhận mối quan hệ bền vững giữa nền tảng vốn thực và định giá thị trường. Sự chênh lệch tốc độ tăng trưởng là minh chứng cho việc thị trường đã “nâng hạng” định giá GAS, củng cố vị thế “blue-chip” ổn định.
1. library(ggplot2); library(ggrepel); library(patchwork); library(dplyr); library(scales)
2. p36 <- ggplot(GAS_data_final, aes(x = factor(Nam), y = P_E_CuoiNam)) +
3. geom_segment(aes(xend = factor(Nam), y = 0, yend = P_E_CuoiNam),
4. color = "#0072B2", linewidth = 1) +
5. geom_point(size = 3, color = "#E69F00") +
6. geom_text(aes(label = round(P_E_CuoiNam, 1)), vjust = -1, size = 3.5) +
7. geom_hline(yintercept = median(GAS_data_final$P_E_CuoiNam, na.rm = TRUE),
8. linetype = "dashed", color = "blue") +
9. labs(title = "(36) P/E theo năm (2014–2024)",
10. subtitle = "Đường đứt = Median",
11. x = "Năm", y = "P/E Cuối năm (lần)") +
12. theme_minimal(base_size = 7)
13. data_p37 <- GAS_data_final %>%
14. filter(Nam != 2019 & is.finite(VonChuSoHuu) & is.finite(VonHoa_TyVND))
15. p37 <- ggplot(data_p37, aes(x = VonChuSoHuu / 1e12, y = VonHoa_TyVND / 1e3)) +
16. geom_abline(slope = 1, intercept = 0, linetype = "dotted", color = "gray50") +
17. geom_point(color = "#009E73", size = 3) +
18. geom_smooth(method = "lm", se = FALSE, color = "black", linetype = "dashed") +
19. geom_text_repel(aes(label = Nam), size = 3) +
20. labs(title = "(37) So sánh Vốn hoá và Vốn chủ sở hữu (Đã lọc 2019)",
21. subtitle = "Đường chấm (Vốn hóa = VCSH), Đường đứt (Xu hướng hồi quy)",
22. x = "Vốn chủ sở hữu (nghìn tỷ VND)",
23. y = "Vốn hoá thị trường (nghìn tỷ VND)") +
24. theme_minimal(base_size = 7)
25. p36 / p37 + plot_annotation(title = "Hình 36–37: Định giá thị trường (P/E) và Giá trị sổ sách (Vốn hóa vs VCSH)")| Dòng code | Giải thích |
|---|---|
| Dòng 1 | Tải các gói cần thiết và thiết lập. |
| Dòng 2-11 | Biểu đồ 36: P/E theo năm (Lollipop):
* geom_segment: Vẽ biểu đồ Lollipop
(đường thẳng đứng từ 0 đến giá trị P/E). * geom_point: Đánh dấu giá trị P/E cuối năm.
* geom_text: Thêm nhãn giá trị P/E.
* geom_hline: Thêm đường tham chiếu
tại Trung vị (Median) P/E toàn giai đoạn. |
| Dòng 12-21 | Biểu đồ 37: Vốn hóa vs Vốn chủ sở hữu
(y=x): * Dòng 12-13: Tạo khung dữ liệu data_p37 bằng cách loại bỏ năm 2019 và đảm
bảo không có NA. * geom_abline(slope = 1, intercept = 0):
Thêm đường tham chiếu y = x (tức là \(\text{Vốn hóa} = \text{VCSH}\) hay \(P/B = 1\)). * geom_point: Vẽ các điểm dữ liệu. * geom_smooth(method = "lm"): Thêm đường hồi
quy tuyến tính. * geom_text_repel:
Dán nhãn năm cho các điểm dữ liệu. |
| Dòng 22 | Kết hợp hai biểu đồ (36 và 37) theo bố cục 2 hàng và thêm tiêu đề chung. |
Nhận xét hình 36–37: Định giá thị trường và giá trị sổ sách
Diễn biến P/E (Hình 36): P/E duy trì ở mức thấp (\(\approx 4-5\) lần) trong giai đoạn đầu (2014–2016), sau đó tăng dần và ổn định trên mức Trung vị (\(\approx 9.5\) lần) từ 2017 trở đi, với đỉnh cao nhất \(\approx 14.8\) lần năm 2021. Điều này chứng minh niềm tin thị trường vào lợi nhuận tương lai của GAS đã được củng cố.
So sánh vốn hóa vs VCSH (Hình 37):
Tổng kết: Cả P/E và Vốn hóa đều cho thấy sự mở rộng định giá mạnh mẽ kể từ 2020, song hành với hiệu suất tài chính vững chắc và dòng tiền ổn định. Mối quan hệ chặt chẽ giữa vốn hóa và vốn chủ (đường hồi quy) củng cố vai trò của quy mô vốn là yếu tố chi phối giá trị thị trường.
Phân tích dữ liệu tài chính của GAS giai đoạn 2014–2024 cung cấp bằng chứng rõ ràng về sức khỏe tài chính vượt trội và quá trình tái định giá tích cực của thị trường:
| Hạn chế | Mô tả chi tiết | Đề xuất |
|---|---|---|
| Phạm vi thời gian | Chuỗi dữ liệu 11 năm là ngắn đối với chu kỳ tài chính và giá cổ phiếu của ngành năng lượng. | Mở rộng phân tích với dữ liệu hàng quý và kéo dài chu kỳ quan sát. |
| Thiếu biến vĩ mô | Phân tích không bao gồm các yếu tố vĩ mô quan trọng: giá dầu thô, tỷ giá USD/VND, lãi suất cơ bản, vốn là các biến chi phối chính ngành khí. | Xây dựng mô hình Hồi quy Đa biến hoặc Vector Tự hồi quy (VAR) bao gồm các biến vĩ mô để đánh giá quan hệ nhân quả. |
| Tính chu kỳ và đa cộng tuyến | Các mối tương quan và hồi quy có tính không ổn định theo thời gian (Rolling Correlation), gợi ý mối quan hệ không tuyến tính hoặc thay đổi cấu trúc. | Sử dụng Hồi quy phi tuyến (ví dụ: GARCH) hoặc mô hình hóa Mô hình trạng thái để bắt được tính chu kỳ và biến động không đồng nhất. |
| Phân tích nhân quả | Phân tích hiện tại chỉ dừng lại ở tương quan. | Áp dụng Granger Causality Test để xác định liệu CFO/ROE có dự báo được Giá/P/E hay không. |