PHẦN 1. 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

1.1. GIỚI THIỆU VÀ CHUẨN BỊ DỮ LIỆU NYSE

1.1.1. Tổng quan về bộ dữ liệu và mục tiêu phân tích

1.1.1.1. Giới thiệu chung và mục tiêu phân tích

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:

  1. 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.

  2. 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.

  3. 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).

  4. Đá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).

1.1.1.2. Cấu trúc và đặc điểm bộ dữ liệu

  • 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:

Bảng 1.1.1: Mô tả chi tiết các biến trong bộ dữ liệu NYSE gốc
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

1.1.1.3. Tóm tắt cấu trúc phân tích

Nghiên cứu này được cấu trúc thành các mục chính sau:

  1. 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.

  2. 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).

  3. 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).

  4. 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.1.2. Kiểm tra cấu trúc và chất lượng dữ liệu ban đầu

1.1.2.1. Đọc dữ liệu và kiểm tra kích thước

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 
1. cat("Số lượng biến (trường thông tin):", num_vars_raw, "\n") 
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:

  • Tải thư viện và kiểm tra kích thước dữ liệu
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.
  • Chuyển đổi kiểu dữ liệu ngày tháng
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.

1.1.2.2. Kiểm tra kiểu dữ liệu và các dòng đầu

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 …

Bảng 1.1.2: Sáu quan sát đầu tiên (Các cột tiêu biểu)
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:

  • kiểm tra cấu trúc và hiển thị dữ liệu mẫu
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ế.

1.1.2.3. Tóm tắt tổng quan

1. library(skimr); library(dplyr)
2. skim_summary <- skim(nyse_raw)
3. print(skim_summary)
── 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:

  • Tóm tắt dữ liệu thống kê cơ bản
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.1.2.4. Kiểm tra giá trị khuyết thiếu (NA)

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:

  • Kiểm tra và thống kê giá trị khuyết thiếu (NA)
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.1.2.5. Kiểm tra bản ghi trùng lặp

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:

  • Kiểm tra và thống kê bản ghi trùng lặp
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.1.3. Xử lý dữ liệu, làm sạch và tạo biến phái sinh

1.1.3.1. Chuẩn hóa tên/Kiểu dữ liệu cơ bản

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 
1. cat("Kiểu dữ liệu mới 'sector':", class(nyse_processed$sector), "\n")
Kiểu dữ liệu mới 'sector': factor 
1. cat("Kiểu dữ liệu mới 'Date_Buy':", class(nyse_processed$Date_Buy), "\n")
Kiểu dữ liệu mới 'Date_Buy': Date 
1. cat("6 tên cột đầu tiên:\n")
6 tên cột đầu tiên:
1. print(head(colnames(nyse_processed)))
[1] "company"      "sector"       "Horizon_Days" "amount"       "Date_Buy"    
[6] "price_BUY"   

Giải thích code:

  • Dọn dẹp và chuẩn bị dữ liệu
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()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.

1.1.3.2. Xử lý thời gian và tạo biến chu kỳ

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")
Bảng 1.1.3: Ví dụ về các biến thời gian đã tạo
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:

  • Xử lý và tạo biến thời gian
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_SellDate_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()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).

1.1.3.3. Tạo biến phái sinh

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

  • Tạo và xử lý biến phái sinh
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_ReturnMedian dương, Log_PE đối xứng hơn, Leverage_Ratio lệch phải, Risk_LevelAmount_Level phân bổ đều).

1.1.3.4. Phân chia và lưu trữ dữ liệu theo nhóm biến

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):
1. cat("Số biến:", ncol(nyse_group1_data), "\n")
Số biến: 19 
1. print(colnames(nyse_group1_data))
 [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"
1. cat("\nNhóm 2 (Chỉ số Công ty, Định giá, ESG):\n")

Nhóm 2 (Chỉ số Công ty, Định giá, ESG):
1. cat("Số biến:", ncol(nyse_group2_data), "\n")
Số biến: 17 
1. print(colnames(nyse_group2_data))
 [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

  • Phân nhóm và lưu dữ liệu đã xử lý
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 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_varsnyse_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.2. PHÂN TÍCH ĐẶC ĐIỂM GIAO DỊCH, HÀNH VI VÀ HIỆU SUẤT ĐẦU TƯ

1.2.1. Phân tích thống kê mô tả và phân phối (Giao dịch, Hành vi, Hiệu suất đầu tư)

1.2.1.1. Tóm tắt phân bố trung tâm và phân tán

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")
Bảng 1.2.1: Thống kê mô tả (Giao dịch, Hành vi, Hiệu suất đầu tư)
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

  • Thống kê mô tả nhóm biến giao dịch, hành vi, hiệu suất
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 (transposerownames_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ư:

  • Xu hướng trung tâm:
    • Lợi suất: Lợi suất thực 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.
    • Giao dịch: Giá trị trung vị phổ biến (Median) của amount là 2,000 USD và Horizon_Days là 90 ngày.
    • Rủi ro: Volatility_BuySharpe_RatioMedianMean rất gần nhau (~0.23 / ~0.25).
  • Độ phân tán và độ lệch:
    • Biên độ lớn: Biên độ Min-Max cực lớn của lợi suất thực tế (-0.952 đến 16.353) và lợi suất danh nghĩa (-0.872 đến 8.847) khẳng định mức độ rủi ro và tiềm năng lợi nhuận/thua lỗ rất cao.
    • Độ lệch: Các biến 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).
  • Kết luận sơ bộ: Dữ liệu có sự đa dạng lớn. Các biến lợi suất, đặc biệt là 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.2.1.2. Độ lệch chuẩn

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")
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ư)
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:

  • Tính toán và hiển thị độ lệch chuẩn (sd)
Dòng code Giải thích
1-2 Tính độ lệch chuẩn (SD):
* Sử dụng summarise()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()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ư:

  • Biến động lợi suất & giá:
    • Lợi suất thực 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.
    • Giá mua/bán (price_BUY/price_SELL, SD ~ 200+) cũng có sự biến động mạnh về giá trị.
  • Biến động quy mô & thời gian:
    • Quy mô giao dịch (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ư.
    • Thời gian nắm giữ (Horizon_Days, SD = 210.7 ngày) cũng biến động đáng kể.
  • Biến động rủi ro và lạm phát:
    • 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.
  • Kết luận: Độ lệch chuẩn cho thấy các biến Giao dịch, Hành vi và Hiệu suất đều có độ phân tán cao, với sự khác biệt rõ rệt nhất nằm ở quy mô giao dịch (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.2.1.3. Bảng tần suất biến phân loại

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")
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. cat("\n")
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ư")
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. cat("\n")
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ữ")
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. cat("\n")
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")
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. cat("\n")
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ư")
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:

  • Phân tích tần suất biến phân loại
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:

  • Phân bổ theo ngành (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%).
  • Phân bổ theo kết quả đầu tư (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%).
  • Phân bổ theo thời gian nắm giữ (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%).
  • Phân bổ theo mức độ rủi ro (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.
  • Kết luận: Dữ liệu cho thấy sự tập trung giao dịch theo ngành và sự mất cân bằng giữa kết quả đầu tư BADGOOD. Các biến hành vi và rủi ro mới tạo (Holding_Period, Risk_Level, Amount_Level) có sự cân bằng tốt hơn.

1.2.1.4. Phân tích phân vị biến định lượng

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")
Bảng 1.2.8: Các điểm phân vị của các biến định lượng
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:

  • Phân tích các điểm phân vị
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:

  • Lợi suất (Real_Return, Nominal_Return):
    • Phạm vi cực đoan (Max ~1635%/885% và Min ~-95%/-87%) tái khẳng định rủi ro cao.
    • 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.
    • Khoảng tứ phân vị (IQR) của lợi suất thực tế (-62.1% đến 48.6%) cho thấy sự phân tán rộng của 50% dữ liệu trung tâm.
  • Quy mô & Thời gian (amount, Horizon_Days):
    • Giá trị trung vị (Median) là 2,000 USD90 ngày.
    • Phân bố lệch phải (Q3 cách Median xa hơn Q1) được xác nhận: 75% giao dịch có amount \(\leq\) 10,000 USD và Horizon_Days \(\leq\) 300 ngày, nhưng giá trị Max vượt trội rất xa.
  • Rủi ro & Hiệu quả (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.
  • Kỳ vọng & Lạm phát: Lợi suất kỳ vọng có 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.
  • Kết luận: Phân vị cho phép định lượng mức độ tập trung và các ngưỡng quan trọng trong dữ liệu. Dữ liệu Lợi suất thể hiện sự phân tán lớn, trong khi các biến rủi ro tập trung hơ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")
Bảng 1.2.9: Tần suất giao dịch theo dấu của lợi suất thực tế
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:

  • Phân tích tần suất theo dấu của lợi suất
Dòng code Giải thích
1-5 Phân loại dấu Lợi suất:
* Sử dụng mutate()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):

  • Tỷ lệ lãi/lỗ thực tế:
    • 56.7% giao dịch đạt lợi suất thực tế dương (> 0).
    • 43.3% giao dịch có lợi suất thực tế âm hoặc bằng không (<= 0).
  • So sánh với biến phân loại investment:
    • Tỷ lệ giao dịch có lãi thực tế (56.7%) cao hơn đáng kể so với tỷ lệ nhóm GOOD (34.8% - Bảng 1.2.4).
    • Kết luận: Điều này khẳng định rằng tiêu chí phân loại investmentGOOD 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.2.1.5. Phân tích hình dạng phân phối

(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.

  • Skewness = 0: Gần đối xứng.
  • Skewness > 0: Lệch phải (đuôi dài về phía giá trị lớn).
  • Skewness < 0: Lệch trái (đuôi dài về phía giá trị nhỏ).
  • Giá trị tuyệt đối |Skewness| càng lớn, độ lệch càng mạnh.
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")
Bảng 1.2.10: Độ xiên (Skewness) của các biến định lượng
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:

  • Tính toán và hiển thị độ xiên
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()rename() để đặt tên cột là VariableSkewness.
* 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:

  • Lệch phải rất mạnh (Skewness \(\gg\) 1): Các biến 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.
    • Ý nghĩa: Phần lớn dữ liệu tập trung ở các giá trị thấp (gần Median), nhưng có đuôi dài kéo về phía các giá trị cực lớn (lợi suất/giá/số lượng giao dịch rất cao). Điều này giải thích vì sao Mean cao hơn Median cho hầu hết các biến này.
  • Lệch phải đáng kể (Skewness \(\approx\) 1): Horizon_Days (1.11), Sharpe_Ratio (1.10), và Volatility_Buy (1.10) cũng lệch phải rõ rệt.
  • Gần đối xứng (|Skewness| \(< 1\)): Chỉ có biến inflation (0.34) có độ xiên thấp, gần với phân phối đối xứng.
  • Kết luận: Phân phối dữ liệu Giao dịch, Hành vi và Hiệu suất không đối xứng. Các sự kiện cực đoan ở phía giá trị cao chi phối phân phối, cho thấy sự tồn tại của các trường hợp “Lãi lớn” (hoặc giá/số lượng giao dịch lớn) làm sai lệch đáng kể các chỉ số trung bình.

(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).

  • Excess Kurtosis = 0: Độ nhọn như phân phối chuẩn (Mesokurtic).
  • Excess Kurtosis > 0: Nhọn hơn chuẩn, đuôi dày hơn (Leptokurtic - rủi ro outlier cao).
  • Excess Kurtosis < 0: Bẹt hơn chuẩn, đuôi mỏng hơn (Platykurtic).
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")
Bảng 1.2.11: Độ nhọn (Excess Kurtosis = Kurtosis − 3)
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:

  • Tính toán và hiển thị độ nhọn
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:

  • Đuôi cực dày (Kurtosis \(\gg\) 3):
    • Nominal_Return (117.3) và Real_Return (31.9) có độ nhọn cực kỳ cao.
    • Các biến giá (price_BUY, price_SELL) cũng rất cao (~26.6).
    • Ý nghĩa: Phân phối của các biến lợi suất và giá này rất nhọn ở đỉnh và có đuôi cực dày (heavy tails). Điều này xác nhận rằng các sự kiện cực đoan (lãi hoặc lỗ rất lớn) xảy ra thường xuyên hơn nhiều so với phân phối chuẩn. Đây là một đặc trưng điển hình của dữ liệu thị trường tài chính.
  • Đuôi dày (Kurtosis \(> 1\)): Hầu hết các biến còn lại đều có Excess Kurtosis dương và lớn hơn 1 (ví dụ: Expected_Return_Yr (13.9), amount (7.1), Volatility_Buy/Sharpe_Ratio (4.4)).
    • Ý nghĩa: Tất cả các biến này đều nhọn hơn và có đuôi dày hơn phân phối chuẩn.
  • Kết luận: Đặc tính đuôi dày là phổ biến đối với hầu hết các biến quan trọng trong Nhóm 1. Việc sử dụng các mô hình dựa trên giả định phân phối chuẩn có thể dẫn đến đánh giá thấp rủi ro xảy ra các sự kiện cực đoan.

(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")
Bảng 1.2.12: Kiểm định Shapiro–Wilk trên mẫu con (N≤5000)
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:

  • Kiểm định tính phân phối chuẩn (shapiro-wilk)
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:

  • Bác bỏ giả thuyết chuẩn: Kết quả kiểm định Shapiro-Wilk trên mẫu con (N=5000) cho cả ba biến chính (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.
  • Nhất quán với các chỉ số hình dạng: Kết luận này hoàn toàn phù hợp với các kết quả về Độ xiên (Skewness) dương và Độ nhọn (Kurtosis) rất cao đã được tính toán ở các bảng trước.
  • Tổng kết phân tích hình dạng phân phối: Phân tích sâu hơn xác nhận dữ liệu Giao dịch, Hành vi và Hiệu suất không tuân theo phân phối chuẩn mà có đặc tính lệch (thường là lệch phải)đuôi rất dày (lợi suất, giá). Phát hiện này là rất quan trọng, nhấn mạnh sự cần thiết phải thận trọng khi sử dụng các phương pháp thống kê giả định phân phối chuẩn, và gợi ý việc sử dụng các phương pháp thống kê phi tham số hoặc robust hơn trong các mô hình sau này.

1.2.1.6. Phân tích giá trị ngoại lai

(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")
Bảng 1.2.13: Ngưỡng xác định giá trị ngoại lai bằng phương pháp IQR
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

  • Xác định ngưỡng giá trị ngoại lai (outlier)
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\))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:

  • Ngưỡng ngoại lai (Bảng 1.2.13): Bảng này xác định rõ các ngưỡng giá trị cụ thể để phân loại outlier cho từng biến theo phương pháp IQR (\(Q1 \pm 1.5 \times IQR\)):
    • 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.
  • Kết luận: Phương pháp IQR cho thấy các biến Lợi suất và Quy mô giao dịch có số lượng outlier rất lớn (cả hai phía cho lợi suất, phía trên cho quy mô). Riêng biến Thời gian nắm giữ, dù có phân tán rộng, lại không có outlier theo tiêu chí IQR, gợi ý rằng sự phân tán này là do phân phối tự nhiên bị kéo dài chứ không phải do các điểm dữ liệu cực đoan biệt lập.

(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")
Bảng 1.2.14: Số lượng và tỷ lệ phần trăm giá trị ngoại lai
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

  • Định lượng số lượng và tỷ lệ giá trị ngoại lai
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:

  • Tỷ lệ ngoại lai (Bảng 1.2.14): Bảng này định lượng mức độ phổ biến của outliers theo phương pháp IQR:
    • Tỷ lệ cao: 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.
    • Tỷ lệ thấp: 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.
    • Không có Outlier: Horizon_Days xác nhận không có outlier (0.00%), củng cố nhận xét từ bước ngưỡng.
  • Kết luận: Dữ liệu cho thấy sự hiện diện đáng kể của outliers, đặc biệt là trong lợi suất danh nghĩa và quy mô giao dịch. Điều này yêu cầu cân nhắc các phương pháp xử lý outlier hoặc các mô hình ít nhạy cảm với outlier trong quá trình phân tích tiếp theo.

(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. cat("\n")
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")
Bảng 1.2.15: Phân bổ Outlier lợi suất thực tế theo ngành
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. cat("\n")
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")
Bảng 1.2.16: Phân bổ Outlier lợi suất thực tế theo năm mua
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

  • Phân tích đặc điểm giá trị ngoại lai của 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:

  • Đặc điểm ngoại lai của 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:
    • Theo ngành (Bảng 1.2.15): Ngành 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.
    • Theo năm mua (Bảng 1.2.16): Outliers tập trung chủ yếu vào các giao dịch mua năm 2015 (69.30%)2016 (28.09%). Giai đoạn này rõ ràng là thời kỳ thị trường có biến động lợi suất cực đoan nhất, đặc biệt đối với các giao dịch của ngành TECH.
  • Kết luận: Sự hiện diện của outliers trong 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")
Bảng 1.2.17: Ảnh hưởng Outlier lên Mean/SD lợi suất thực tế
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

  • Đánh giá tác động của việc loại bỏ giá trị ngoại lai
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:

  • Ảnh hưởng của ngoại lai lên thống kê 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:
    • Tác động lên Mean: 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.
    • Tác động lên SD: 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ể.
  • Kết luận: Mặc dù chỉ chiếm một tỷ lệ nhỏ (0.77%), các outlier lợi suất thực tế lại có ảnh hưởng không cân xứng đến giá trị trung bình và độ biến động. Dù SD đã giảm, nó vẫn còn rất lớn so với Mean mới, tái khẳng định rằng 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đ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.2.2. Phân tích tương quan và mối quan hệ (Giao dịch, Hành vi, Hiệu suất đầu tư)

(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. cat("\n")
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")
Bảng 1.2.18: Ma trận tương quan Pearson
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

  • Tính toán và hiển thị ma trận tương quan pearson
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")
Bảng 1.2.19: Top 5 cặp tương quan tuyến tính mạnh nhất
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. cat("\n") 
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")
Bảng 1.2.20: Top 3 cặp tương quan dương mạnh nhất
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. cat("\n")
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")}
Bảng 1.2.21: Top 3 cặp tương quan âm Mạnh nhất
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:

  • Phân tích các mối tương quan mạnh nhất
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")
Bảng 1.2.22: Kết quả kiểm định ý nghĩa thống kê cho các tương quan chính
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

  • Kiểm định ý nghĩa thống kê của tương quan pearson
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:

  • Vấn đề dữ liệu nổi cộm (Ma trận 1.2.18 & Bảng 1.2.19): Phát hiện quan trọng nhất là tương quan hoàn hảo (r = 1.000) giữa Volatility_BuySharpe_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.
  • Lợi suất thực tế (Real_Return):
    • Tương quan mạnh nhất: Mối tương quan âm rất mạnh (r = -0.800) với 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ế).
    • Tương quan yếu: Mối quan hệ với các yếu tố khác lại rất yếu:
      • 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).
    • Ý nghĩa thống kê (Bảng 1.2.22): Do cỡ mẫu lớn (\(\approx\) 405K), hầu hết các tương quan yếu đều có ý nghĩa thống kê (p < 0.001). Tuy nhiên, độ lớn của hệ số tương quan (r) cho thấy chúng không có ý nghĩa thực tiễn trong việc giải thích tuyến tính Real_Return.
  • Các mối quan hệ khác: price_BUYprice_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_DaysNominal_Return.
  • Kết luận: Phân tích tương quan cho thấy mối quan hệ tuyến tính giữa lợi suất thực tế và các yếu tố hành vi/giao dịch chính (rủi ro, thời gian, quy mô) là cực kỳ yếu hoặc không tồn tại. Điều này gợi ý rằng mô hình cần phải khai thác các mối quan hệ phi tuyến tính, hoặc các yếu tố giải thích chính nằm ở nhóm biến công ty/định giá (Nhóm 2).

1.2.3. Kiểm định sự khác biệt trung bình giữa các biến (Giao dịch, Hành vi, Hiệu suất đầu tư)

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.

1.2.3.1. So sánh Real_Return theo các nhóm

1. 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)

Bảng: Kết quả Welch t-test cho Lợi suất thực tế theo Kết quả đầu tư (investment)
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)

Kết quả ANOVA cho Lợi suất thực tế theo Ngành (sector)
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)

Kết quả ANOVA cho Lợi suất thực tế theo Thời gian nắm giữ (Holding_Period)
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)

Kết quả ANOVA cho Lợi suất thực tế theo Mức độ rủi ro (Risk_Level)
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)

Kết quả ANOVA cho Lợi suất thực tế theo Quy mô giao dịch (Amount_Level)
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:

  • Kiểm định khác biệt trung bình lợi suất thực tế giữa các nhóm
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 GOODBAD.
* 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ớncó ý 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ú ý.

1.2.3.2. So sánh Volatility_Buy theo các nhóm

1. 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)

Kết quả ANOVA cho Độ biến động (Volatility_Buy) theo Ngành (sector)
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)

Kết quả ANOVA cho Độ biến động (Volatility_Buy) theo Thời gian nắm giữ (Holding_Period)
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)

Kết quả ANOVA cho Độ biến động (Volatility_Buy) theo Mức độ rủi ro (Risk_Level)
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:

  • Kiểm định khác biệt trung bình độ biến động giữa các nhóm
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:

  • Theo ngành (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).
  • Theo thời gian nắm giữ (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.
  • Theo mức độ rủi ro (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ư.

1.2.3.3. So sánh Sharpe_Ratio theo các nhóm

1. 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)

Bảng: Kết quả Welch t-test cho Tỷ suất Sharpe theo Kết quả đầu tư (investment)
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)

Kết quả ANOVA cho Tỷ suất Sharpe theo Ngành (sector)
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)

Kết quả ANOVA cho Tỷ suất Sharpe theo Mức độ rủi ro (Risk_Level)
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:

  • Kiểm định khác biệt trung bình tỷ suất sharpe giữa các nhóm
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 GOODBAD. 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:

  • Theo kết quả đầu 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.
  • Theo ngành (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).
    • Theo ngành: AUTO (cao nhất) > TECH \(\approx\) RETAIL > BANK > FMCG (thấp nhất).
    • Theo rủi ro: High (cao nhất) > Medium > Low (thấp nhất).
  • Kết luận: Sự trùng khớp hoàn toàn giữa kết quả kiểm định của Sharpe_RatioVolatility_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.

1.2.3.4. So sánh amount theo các nhóm

1. 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)

Kết quả ANOVA cho Quy mô giao dịch (amount) theo Ngành (sector)
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)

Bảng: Kết quả Welch t-test cho Quy mô giao dịch (amount) theo Kết quả đầu tư (investment)
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)

Kết quả ANOVA cho Quy mô giao dịch (amount) theo Thời gian nắm giữ (Holding_Period)
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:

  • Kiểm định khác biệt trung bình quy mô giao dịch giữa các nhóm
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à GOODBAD. 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:

  • Theo Ngành (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.
  • Theo Kết quả đầu tư (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 GOODBAD là cực kỳ nhỏ (\(\approx\) 9 USD).
  • Theo Thời gian nắm giữ (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.

1.2.3.5. So sánh Horizon_Days theo các nhóm

1. 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)

Kết quả ANOVA cho Thời gian nắm giữ (Horizon_Days) theo Ngành (sector)
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)

Bảng: Kết quả Welch t-test cho Thời gian nắm giữ (Horizon_Days) theo Kết quả đầu tư (investment)
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:

  • Kiểm định khác biệt trung bình thời gian nắm giữ giữa các nhóm
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à GOODBAD. Hiển thị kết quả t-test.

Nhận xét:

  • Theo ngành (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.
  • Theo kết quả đầu tư (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.
    • Ý nghĩa: Phát hiện này gợi ý rằng các khoản đầu tư thành công (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:

  1. 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)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ể.

  2. 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.

  3. Tương quan hoàn hảo: Sharpe_RatioVolatility_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.

  4. 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 ý.

1.3. TRỰC QUAN HÓA DỮ LIỆU (GIAO DỊCH, HÀNH VI, HIỆU SUẤT ĐẦU TƯ)

1.3.1. Trực quan hóa phân bố đơn biến

1.3.1.1. Phân bố lợi suất: Thực tế (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

  • Trực quan hóa phân bố lợi suất (biểu đồ 1.3.1 và 1.3.2)
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_ReturnReal_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_TypeReturn_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)

  1. Đặc tính phi chuẩn (Độ nhọn và Lệch):
    • Độ nhọn cao (Leptokurtic): Cả hai đường mật độ đều có đỉnh rất nhọn và tập trung cực kỳ sát mốc 0%. Điều này trực quan hóa các kết quả Độ nhọn dư (Excess Kurtosis) cực kỳ cao đã được tính toán, xác nhận rằng phần lớn các giao dịch có lợi suất gần bằng 0 (lợi suất “bình thường” xảy ra rất thường xuyên).
    • Tính Lệch: Trong khung nhìn tập trung này, đường cong cho thấy sự lệch phải nhẹ (đuôi phải kéo dài hơn).
  2. Tác động của lạm phát (inflation):
    • Sự dịch chuyển: Đường mật độ của 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).
    • Ý nghĩa: Sự dịch chuyển này cho thấy yếu tố lạm phát được sử dụng (có giá trị trung vị âm) đã điều chỉnh giảm giá trị lợi suất thực tế so với lợi suất danh nghĩa trong nhiều trường hợp, làm cho đỉnh phân bố củ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)

  1. Sự biến động và tập trung của dữ liệu:
    • Trung vị (Median): Đường giữa hộp nằm sát mốc 0% (Trung vị 16.7% theo Bảng 1.2.8), xác nhận 50% dữ liệu có lợi suất rất gần điểm hòa vốn sau khi điều chỉnh lạm phát.
    • Khoảng tứ phân vị (IQR): Phần hộp kéo dài từ khoảng \(\approx-40\%\) đến \(\approx+40\%\). Điều này trực quan hóa sự thật rằng 50% giao dịch trung tâm có độ biến động rất lớn, khẳng định độ lệch chuẩn gốc (SD = 0.774) là rất cao.
  2. Bằng chứng trực quan về đuôi dày:
    • Box Plot bị giới Hạn: Biểu đồ này đã phải giới hạn trục Y (từ 0.5% đến 99.5% dữ liệu) và hầu như không hiển thị đường râu. Điều này là bằng chứng trực quan mạnh mẽ nhất về đặc tính đuôi dày của lợi suất.
    • Ý nghĩa: Các giá trị ngoại lai cực đoan (outliers, Min \(\approx -95.2\%\) đến Max \(\approx 1635\%\)) nằm rải rác rất xa ngoài phạm vi hộp và râu, kéo giá trị Trung bình (Mean = 8.7%) lên cao và làm tăng đáng kể Độ lệch chuẩn.

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:

  1. 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)đuôi dày chứa đầy các giá trị cực đoan.

  2. 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.

  3. 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.3.1.2. Phân bổ tần suất của các biến phân loại chính.

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

  • Trực quan hóa phân bố tần suất biến phân loại
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_sectorplot_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

  • Sự tập trung: Biểu đồ trực quan hóa sự tập trung giao dịch mạnh mẽ vào ba ngành: RETAIL, TECH, và BANK, với số lượng giao dịch cao và tương đương nhau (\(\approx 85K\) đến \(\approx 96K\)).
  • Ngành có hoạt động thấp nhất: Ngành FMCG có số lượng giao dịch thấp nhất (\(\approx 52K\)), thấp hơn rõ rệt so với mức trung vị (đường chấm).
  • Kết luận: Hoạt động đầu tư trong mẫu dữ liệu này tập trung chủ yếu vào các ngành tiêu dùng, công nghệ và tài chính.

B. Biểu đồ 1.3.4: Phân bổ giao dịch theo kết quả đầu tư

  • Mất cân bằng lớp: Biểu đồ minh họa rõ ràng sự mất cân bằng giữa hai nhóm kết quả đầu tư:
    • Nhóm BAD chiếm ưu thế tuyệt đối với 65.2% tổng số giao dịch.
    • Nhóm GOOD chỉ chiếm 34.8%.
  • Kết luận: Biểu đồ nhấn mạnh thách thức của việc đầu tư sinh lời vượt trội trên thị trường này và xác nhận sự cần thiết phải xử lý vấn đề mất cân bằng lớp (class imbalance) nếu sử dụng mô hình học máy.

** C. Biểu đồ 1.3.5: Phân bổ giao dịch theo thời gian nắm giữ**

  • Sự đa dạng của chiến lược: Số lượng giao dịch được phân bổ tương đối đồng đều giữa ba chiến lược thời gian nắm giữ: Long-term (cao nhất, \(\approx 147K\)), Short-term (\(\approx 135K\)), và Mid-term (\(\approx 122K\)).
  • Chiến lược ưu tiên: Số lượng giao dịch theo chiến lược Long-term chiếm ưu thế hơn một chút, cho thấy một phần lớn nhà đầu tư có xu hướng nắm giữ cổ phiếu lâu dài (\(\ge 6\) tháng).
  • Ý nghĩa: Sự cân bằng tương đối giữa các nhóm này là một ưu điểm lớn, đảm bảo tính đại diện và độ tin cậy thống kê cao cho các phân tích so sánh hiệu suất giữa các chiến lược thời gian khác nhau.

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.3.1.3. Đánh giá phân bố của các biến phái sinh

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)

  • Trực quan hóa phân bố các biến định lượng khác
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 RDatachuẩ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) (1.3.6): Phân bố đã gần đối xứng sau chuyển đổi Logarit, với đỉnh chính (\(\text{mode}\)) tương đương \(\text{PE} \approx 12.2\). Điều này xác nhận việc chuẩn hóa đã thành công và biến 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\)).
  • Leverage_Ratio (1.3.8): Phân bố lệch phải mạnh và có nhiều đỉnh, tập trung cao nhất quanh \(\text{Leverage} \approx 1\) (công ty ít nợ/bảo thủ). Điều này cho thấy sự tồn tại của các nhóm công ty có cấu trúc vốn khác nhau (bảo thủ, nợ vừa phải, nợ lớn). Mối tương quan âm đã được phát hiện giữa đòn bẩy và lợi suất thực tế củng cố nguyên tắc thận trọng.

B. Phân bố hành vi đầu tư (amount, Horizon_Days)

  • amount (Quy mô Giao dịch) (1.3.7): Trên thang logarit, phân bố trở nên gần đối xứng và tập trung quanh ngưỡng \(1,000 - 10,000\) USD. Đáng chú ý là trên thang tuyến tính, tần suất các mức tiền giao dịch cụ thể là gần như đồng đều, cho thấy hành vi phân bổ tiền đầu tư mang tính chất ngẫu nhiên và đa dạng.
  • Horizon_Days (Thời gian Nắm giữ) (1.3.9): Phân bố lệch phải mạnh với đỉnh mật độ cao nhất nằm ở gần 0 ngày. Điều này cho thấy hầu hết các giao dịch là cực kỳ ngắn hạn. Tuy nhiên, các phân tích trước đã chứng minh chiến lược Long-term (mốc \(\ge 180\) ngày) mang lại lợi suất thực tế trung bình cao hơn.

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 amountLeverage_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.

1.3.1.4. Phân bố rủi ro Volatility_Buy và tỷ suất Sharpe Sharpe_Ratio

1. 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

  • Trực quan hóa phân bố độ biến động và tỷ suất 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 MedianQ1/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 MedianQ1/Q3.
* Dòng 24-27: Đặt tiêu đề và định dạng.
Dòng 30 Kết hợp hai biểu đồ (plot_volplot_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)

  • Tính tập trung: Phân bố có đỉnh cao nhất (\(\text{mode}\)) tập trung rõ rệt trong khoảng 0.20 đến 0.25 (vùng Rủi ro Thấp/Trung bình).
  • Đặc tính phi chuẩn: Phân bố thể hiện sự lệch phải và có đuôi kéo dài về phía các giá trị cao hơn, phù hợp với kết quả độ xiên dương đã tính toán.
  • Kết luận rủi ro thấp: Đa số giao dịch được thực hiện trên các cổ phiếu có rủi ro biến động thấp/trung bình. Phân tích trước đó gợi ý rằng việc lựa chọn cổ phiếu ở vùng rủi ro này (vùng \(\text{mode}\)) có thể là chiến lược hiệu quả nhất về mặt lợi suất trung bình.

B. Phân bố tỷ suất Sharpe (Sharpe_Ratio - Biểu đồ 1.3.11)

  • Đồng nhất với độ biến động: Phân bố của 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 đó.
  • Hệ quả phân tích: Sự đồng nhất này xác nhận rằng biến 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).
  • Quyết định mô hình hóa: 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.3.2. Trực quan hóa mối quan hệ hai biến

1.3.2.1. Phân tích rủi ro và lợi suất

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)

  • Trực quan hóa mối quan hệ rủi ro-lợi suất
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)

  1. Mối quan hệ Volatility vs Real_Return (Biểu đồ 1.3.2.1):
    • Tuyến tính rất yếu: Đường hồi quy tuyến tính (màu đen) gần như nằm ngang. Điều này trực quan hóa hệ số tương quan r = 0.040 (rất yếu), xác nhận rằng không có mối quan hệ tuyến tính thực tiễn nào giữa Rủi ro Biến động và Lợi suất Thực tế.
    • Bất thường rủi ro: Phát hiện này củng cố kết quả về “Bất thường rủi ro thấp” (Low-Volatility Anomaly), nơi việc chấp nhận rủi ro biến động cao hơn không được đền bù bằng lợi suất trung bình cao hơn.
  2. Mối quan hệ Volatility vs Sharpe_Ratio (Biểu đồ 1.3.2.2):
    • Tương quan hoàn hảo: Các điểm dữ liệu nằm gần như tuyệt đối trên đường thẳng hồi quy tuyến tính (màu đỏ). Điều này khẳng định mối tương quan r = 1.000 giữa Volatility_BuySharpe_Ratio.
    • Hệ quả phân tích: Sự tương quan hoàn hảo này làm cho hai biến trở nên thừa thãi và gây ra vấn đề đa cộng tuyến hoàn hảo trong các mô hình hồi quy. Do đó, 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.3.2.2. Phân tích hành vi và tác động vĩ mô

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

  • Trực quan hóa so sánh lợi suất trung bình và tương quan
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ả

  1. Biểu đồ 3 (Amount_Level): Mean của cả ba nhóm Small, Medium, Largegầ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\)).

  2. 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.

  3. Biểu đồ 5 (Nominal vs Real Return):

    • Vị trí: Đường hồi quy OLS (cam) nằm thấp hơn đường tham chiếu y = x (đỏ), và hầu hết các điểm dữ liệu nằm dưới đường y=x.
    • Ý nghĩa: Điều này trực quan hóa rằng Lợi suất Thực tế luôn thấp hơn Danh nghĩa do sự bào mòn của lạm phát.
    • Tương quan: Hệ số tương quan \(r = 0.487\) (dương trung bình) cho thấy mối quan hệ tồn tại nhưng không chặt chẽ, do sự thay đổi của yếu tố vĩ mô (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.3.2.3. Tóm tắt tương quan

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)

  • Trực quan hóa ma trận tương quan (heatmap)
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:

  1. Mối quan hệ cực mạnh và tiềm ẩn đa cộng tuyến:
    • \(\text{r} = 1.00\) (Sharpe vs Volatility): Ô vuông sáng rực (màu đỏ) xác nhận tương quan hoàn hảo, đây là dấu hiệu của sự dư thừa thông tin.
    • \(\text{r} = 0.97\) (price_SELL vs price_BUY): Tương quan dương rất mạnh, hợp lý vì giá bán phụ thuộc chặt chẽ vào giá mua ban đầu.
  2. Mối quan hệ chi phối lợi suất thực tế:
    • \(\text{r} = -0.80\) (Real_Return vs inflation): Ô vuông màu tím đậm nhất, khẳng định mối tương quan âm mạnh nhất. Điều này cho thấy inflation là yếu tố tuyến tính mạnh nhất quyết định Lợi suất Thực tế.
  3. Yếu tố hành vi và lợi suất (Tương quan yếu):
    • Các ô tương quan giữa 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).
    • Kết luận: Heatmap trực quan xác nhận rằng mối quan hệ tuyến tính giữa hành vi đầu tư/rủi ro và lợi suất thực tế là không đáng kể. Điều này gợi ý rằng các mô hình cần xem xét các mối quan hệ phi tuyến tính hoặc tập trung vào các biến Định giá/Công ty (Nhóm 2) để giải thích lợi suất.

1.3.3. Trực quan hóa so sánh giữa các nhóm

1.3.3.1. So sánh lợi suất thực tế (Real_Return) theo yếu tố cấu trúc & hành vi

1. 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)

  • Trực quan hóa so sánh lợi suất thực tế theo nhóm (box plots)
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)

  • Lưu ý: Biểu đồ hộp biểu thị Trung vị (Median) bằng đường kẻ ngang trong hộp, trong khi hình thoi biểu thị Trung bình (Mean). Trục Y được cắt tại \(1\%\)\(99\%\) để tập trung vào phần lõi dữ liệu.
  1. Theo Ngành (sector - Biểu đồ 1):
    • Khác biệt rõ rệt: Ngành TECH có Mean và Median cao nhất, cùng với phân phối rộng (biến động lớn nhất). Ngược lại, BANK và FMCG nằm ở mức thấp nhất.
    • Kết luận: Lựa chọn ngành là yếu tố quyết định lợi suất trung bình.
  2. Theo Thời gian nắm giữ (Holding_Period - Biểu đồ 2):
    • Xu hướng rõ ràng: Mean và Median có thứ tự Long-term \(\gg\) Mid-term \(\gg\) Short-term. Hộp của Long-term cũng hẹp hơn, gợi ý lợi suất ổn định hơn.
    • Kết luận: Chiến lược nắm giữ dài hạn mang lại lợi suất trung bình cao hơn và ổn định hơn.
  3. Theo Mức độ rủi ro (Risk_Level - Biểu đồ 3):
    • Bất thường: Nhóm Medium có Mean cao nhất (hình thoi cao nhất), trong khi nhóm High và Low có Mean thấp hơn.
    • Kết luận: Trái ngược với lý thuyết truyền thống, rủi ro cao không đảm bảo lợi suất cao, củng cố dấu hiệu của “Bất thường rủi ro thấp” (Low-Risk Anomaly).
  4. Theo Quy mô giao dịch (Amount_Level - Biểu đồ 4):
    • Không khác biệt: Mean, Median, và hình dạng phân phối của ba nhóm Small/Medium/Large gần như trùng khớp.
    • Kết luận: Quy mô giao dịch không phải là yếu tố quyết định hiệu suất đầu tư.

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.3.3.2. So sánh đặc điểm và tỷ lệ thành công

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):
1. print(table(nyse_group1_finite$Holding_Period, useNA = "ifany"))

Short-term   Mid-term  Long-term 
    135001     122731     147526 
1. cat("\nBảng tần suất tương đối (Holding_Period vs investment):\n")

Bảng tần suất tương đối (Holding_Period vs investment):
1. print(hp_inv_prop)
# 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)

  • Khối Code 1: Chuẩn bị dữ liệu và tính toán tỷ lệ phân bổ
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 factorsắ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.
  • Khối Code 2: Trực quan hóa biểu đồ tỷ lệ và Box Plot Volatility
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_prophp_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ả

  1. Biểu đồ 5 (Volatility theo Ngành):
    • Khác biệt rõ rệt: Ngành AUTO có độ biến động (Volatility_Buy) trung bình cao nhất (hình thoi trắng cao nhất), trong khi FMCG thấp nhất.
    • Kết luận: Đặc điểm ngành ảnh hưởng trực tiếp đến rủi ro biến động của cổ phiếu.
  2. Biểu đồ 6 (GOOD/BAD theo Risk_Level):
    • Xu hướng yếu: Tỷ lệ giao dịch GOOD tăng nhẹ từ nhóm Low-Risk (\(\approx 30.6\%\)) lên nhóm High-Risk (\(\approx 37.8\%\)).
    • Kết luận: Mối quan hệ rủi ro-lợi suất tồn tại (chấp nhận rủi ro cao hơn \(\to\) tỷ lệ lời cao hơn) nhưng rất yếu, củng cố nhận định về tính bất thường của thị trường.
  3. Biểu đồ 7 (GOOD/BAD theo Holding_Period):
    • Ưu thế Dài hạn: Tỷ lệ giao dịch GOOD cao nhất ở nhóm Long-term (\(\approx 39.0\%\)), và thấp nhất ở nhóm Short-term (\(\approx 30.0\%\)).
    • Kết luận: Chiến lược nắm giữ dài hạn giúp cải thiện xác suất thành công của một khoản đầu tư, củng cố mối liên hệ đã phát hiện: 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ấtyếu.

1.3.4. Trực quan hóa xu hướng thời gian

1.3.4.1. Xu hướng tổng thể (Năm và Quý)

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)

  • Trực quan hóa xu hướng lợi suất thực tế theo thời gian
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_linegeom_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

  1. Biểu đồ 1 (Theo năm mua):
    • Biến động chu kỳ: Xu hướng lợi suất trung bình thể hiện sự biến động mạnh theo chu kỳ, đạt đỉnh cực đại 107.4% vào năm 2015 (giai đoạn thị trường bùng nổ) và giảm sâu xuống mức âm vào năm 2017–2018 (giai đoạn điều chỉnh/thắt chặt vĩ mô).
    • Kết luận: Hiệu suất đầu tư chịu ảnh hưởng mạnh mẽ bởi chu kỳ kinh tế vĩ môthời điểm đầu tư (market timing).
  2. Biểu đồ 2 (Theo quý mua):
    • Biến động: Phân tích theo quý cho thấy đỉnh lợi suất rõ rệt nhất rơi vào Q4/2014 đến Q2/2015, sau đó là sự suy giảm liên tục và ổn định ở vùng lợi suất âm từ giữa năm 2017.
    • Kết luận: Tồn tại xu hướng theo quý rõ ràng, củng cố rằng hiệu suất đầu tư mang tính chu kỳ và có thể chịu ảnh hưởng của các sự kiện mùa vụ hoặc chính sách vĩ mô cụ thể.

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.3.4.2. Phân tích xu hướng ngành theo thời gian

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)

  • Trực quan hóa xu hướng lợi suất thực tế theo ngành và quý (facet plot)
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 sectorBuy_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_linegeom_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)

  1. 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.

  2. Phân hóa biên độ và ổn định:

    • Ngành tăng trưởng (TECH, RETAIL): Có biên độ lợi suất lớn nhất (TECH đạt đỉnh \(> 150\%\)), thể hiện sự nhạy cảm cao với chu kỳ tăng trưởng.
    • Ngành phòng thủ (FMCG, BANK): Có biên độ lợi suất ổn định và mượt mà hơn, đặc biệt FMCG thể hiện tính phòng thủ cao khi suy giảm.
    • Thứ tự hiệu suất: Thứ tự lợi suất trung bình toàn kỳ (thứ tự hiển thị facet) là: TECH \(\to\) RETAIL \(\to\) AUTO \(\to\) BANK \(\to\) FMCG, xác nhận TECH là ngành sinh lời nhất (đi kèm rủi ro lớn nhất).
  3. 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).

1.4. KẾT LUẬN VÀ ĐỀ XUẤT

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.

1.4. KẾT LUẬN VÀ ĐỀ XUẤT

1.4.1. Kết luận phân tích tổng thể

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:

  1. Hiệu suất và chiến lược nắm giữ:
    • Ưu thế dài hạn: Chiến lược nắm giữ Long-term (\(\ge 180\) ngày) mang lại lợi suất thực tế trung bình cao nhất (\(\approx 18.9\%\)) và có tỷ lệ thành công (GOOD) cao nhất (\(\approx 39\%\)), cao hơn đáng kể so với Short-term. Điều này cung cấp bằng chứng mạnh mẽ ủng hộ chiến lược “Buy and Hold”.
    • Mức rủi ro: Mối quan hệ Rủi ro-Lợi suất là không tuyến tính và có dấu hiệu của “Bất thường rủi ro thấp” (Low-Risk Anomaly), với nhóm rủi ro Medium đạt Mean Lợi suất cao nhất.
    • Quy mô giao dịch: Quy mô đầu tư (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.
  2. Tác động vĩ mô và chu kỳ:
    • Chu kỳ thị trường: Lợi suất đầu tư có tính chu kỳ mạnh, đạt đỉnh cực đại năm 2015 (\(\approx 107\%\)) và suy giảm mạnh mẽ, đồng pha ở mọi ngành, cho thấy sự chi phối của các yếu tố vĩ mô (chu kỳ thị trường và chính sách tiền tệ) lên hiệu suất chung.
    • Ảnh hưởng của lạm phát: Tương quan âm mạnh nhất (\(\text{r} = -0.80\)) giữa Real_Returninflation 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.
  3. Đặc điểm ngành và dữ liệu:
    • Ngành: Ngành TECH dẫn đầu về Lợi suất Trung bình, trong khi FMCG có độ biến động và lợi suất thấp nhất (phòng thủ).
    • Phi chuẩn: Phân phối của các biến lợi suất và quy mô là lệch phải và có đuôi dày chứa các giá trị cực đoan (outliers), đòi hỏi các phương pháp phân tích mô hình phải được điều chỉnh.
    • Đa cộng tuyến: Phát hiện tương quan hoàn hảo (\(\text{r} = 1.00\)) giữa Volatility_BuySharpe_Ratio, xác nhận sự dư thừa thông tin.

1.4.2. Hạn chế của phân tích mô tả

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ảphi tuyến tính.

1.4.3. Đề xuất hướng phân tích tiếp theo

Để 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:

  1. Xử lý dữ liệu tiền mô hình:
    • Loại bỏ biến Thừa: Loại bỏ biến Sharpe_Ratio để tránh đa cộng tuyến hoàn hảo.
    • Xử lý Outlier/Chuẩn hóa: Áp dụng kỹ thuật Winsorize (cắt biên) cho các biến lệch mạnh như Real_Return, Nominal_Return, amountLeverage_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.
  2. Khám phá nhóm biến công ty (Nhóm 2):
    • Tiến hành thống kê mô tả và kiểm định tương quan/khác biệt trung bình cho các biến định giá (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.
  3. Xây dựng mô hình dự báo:
    • Dự báo Thành công/Thất bạ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).
    • Dự báo lợi suất (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ế.

PHẦN 2. PHÂN TÍCH DỮ LIỆU TÀI CHÍNH VÀ THỊ TRƯỜNG CỦA MÃ CHỨNG KHOÁN GAS (2014-2024)

2.1 GIỚI THIỆU BỘ DỮ LIỆU

  • 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ínhhiệ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.
  • Cấu trúc bộ dữ liệu (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:

  1. Biến định danh: Năm quan sát (Nam).
  2. Biến tài chính cơ bản: (DoanhThu, LoiNhuanSauThue, TongTaiSan, NoPhaiTra, VonChuSoHuu, Gia_DongCua_CuoiNam).
  3. Biến phái sinh (được tính toán): (ROE_Nam, ROA_Nam, TySoNo_Tren_VCSH, DoanhThu_TangTruong_YoY, LoiNhuan_TangTruong_YoY).
  • Phân nhóm biến phân tích

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).

Bảng 2.1.1: Các biến chính trong Nhóm 1: Hiệu suất & 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).

Bảng 2.1.2: Các biến chính trong Nhóm 2: Cấu trúc, Định giá, Dòng tiề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)

2.2 ĐỌC DỮ LIỆU, XỬ LÝ, MÃ HOÁ VÀ TẠO BIẾN MỚI

2.2.1. Đọc dữ liệu

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`)
  • Tải và chuẩn bị dữ liệu báo cáo tài chính
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.

2.2.2. Hàm hỗ trợ trích xuất & làm sạch

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)}
  • Định nghĩa hàm hỗ trợ trích xuất dữ liệu
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.

2.2.3. Trích xuất chỉ tiêu và tạo bảng dữ liệu chuẩn

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))
  • Xây dựng khung dữ liệu
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).

2.2.4. Tạo biến phái sinh

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.
1. cat("\nKiểm tra cấu trúc:\n")

Kiểm tra cấu trúc:
1. str(GAS_data_final[, needed_cols])
'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 ...
1. cat("\nKiểm tra tóm tắt:\n")

Kiểm tra tóm tắt:
1. summary(GAS_data_final[, needed_cols])
      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    
  • Tạo biến phái sinh và bổ sung dữ liệu thị trường
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_YoYLoiNhuan_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.

2.2.5. Kiểm tra và mô tả dữ liệu sau xử lý

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)")
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

  • Kiểm tra dữ liệu gas sau xử lý
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).
  • Dữ liệu gồm 11 quan sát (2014–2024), hoàn toàn không có giá trị thiếu hoặc sai định dạng.
  • Các chỉ tiêu tài chính được trích xuất và chuẩn hóa từ báo cáo gốc, đảm bảo độ chính xác tuyệt đối theo từng năm.

2.3. PHÂN TÍCH: CẤU TRÚC TÀI CHÍNH, ĐỊNH GIÁ VÀ DÒNG TIỀN

2.3.1. Thống kê mô tả và phân phối

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)")
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
  • Chuẩn bị dữ liệu (Cấu trúc, Định giá, Dòng tiền)
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.

2.3.1.1. Kiểm tra chất lượng dữ liệu

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")
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
  • Kiểm tra chất lượng dữ liệu
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.

2.3.1.2. Bảng mô tả cơ bản

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")
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
  • Thống kê mô tả cơ bản
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.

  • Quy mô & Cấu trúc tài chính:
    • Tổng tài sản trung bình \(\approx 62.4\) nghìn tỷ VND, cho thấy mở rộng ổn định (CV = 0.38).
    • Tỷ số nợ trên vốn chủ sở hữu trung bình \(\approx 0.36\), phản ánh mức đòn bẩy thấp và cấu trúc vốn thận trọng.
    • Lưu ý: Giá trị Min TongTaiSan thấp bất thường (\(\approx 621\) tỷ VND) cần kiểm tra lại dữ liệu gốc.
  • Định giá thị trường:
    • Giá cổ phiếu biến động mạnh (\(18K - 70K \text{ đồng/cp}\)).
    • P/E trung bình \(\approx 9.93\) và trung vị \(\approx 9.48\), phản ánh mức định giá ổn định và hợp lý.
    • VonHoa_TyVND (Vốn hóa) biến động cao (CV \(\approx 1\)), chủ yếu do biến động giá.
  • Dòng tiền & Chất lượng lợi nhuận:
    • CFO trung bình \(\approx 10.9\) nghìn tỷ VND/năm, phù hợp với quy mô lợi nhuận.
    • Tỷ lệ CFO/LNST trung vị \(\approx 1.19\), khẳng định chất lượng lợi nhuận lành mạnh và khả năng chuyển đổi lợi nhuận thành tiền mặt hiệu quả.

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.

2.3.1.3. Hình dạng phân phối (Skewness/Kurtosis) & Kiểm định chuẩn hóa

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)")
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)")
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
  • Phân tích hình dạng phân phối và kiểm định tính chuẩn nhóm
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)Độ 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:

  • Độ lệch (Skewness - Bảng 2.3.4): Hầu hết các biến có độ lệch trong khoảng \(\pm 1\) (gần đối xứng). Ngoại lệ: Tổng tài sản (\(\approx -1.57\)) bị lệch âm cao, do ảnh hưởng của outlier nhỏ.
  • Độ nhọn (Kurtosis - Bảng 2.3.4): Hầu hết các biến có phân phối bẹt (platykurtic, K < 3), tức là ít giá trị cực đoan hơn so với phân phối chuẩn. Ngoại lệ: Tổng tài sản (\(\approx 5.29\)) có phân phối nhọn (leptokurtic), do outlier gây ra.
  • Shapiro–Wilk (Bảng 2.3.5): Một số biến quan trọng như TongTaiSan, SL_CoPhieu_LuuHanh, VonHoa_TyVND, và TyLe_CFO_Tren_LNST\(p < 0.05\), cho thấy bác bỏ giả thuyết phân phối chuẩn.
  • Kết luận: Dữ liệu GAS Nhóm 2 có phân phối tương đối cân bằng nhưng bị ảnh hưởng bởi outlierkhông tuân theo phân phối chuẩn (xác nhận qua Shapiro–Wilk). Do cỡ mẫu nhỏ (n=11), các phân tích mô hình sau này nên ưu tiên phương pháp phi tham số hoặc kiểm soát ảnh hưởng outlier.

2.3.1.4. Tăng trưởng giai đoạn (CAGR) & Biến thiên đòn bẩy

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")
Bảng 2.3.6: CAGR giai đoạn (2014–2024)
Chỉ số Giá trị
CAGR_TongTaiSan 0.043
CAGR_VonChuSoHuu 0.050
CAGR_NoPhaiTra 0.023
CAGR_Gia 0.065
1. cat("\n")
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")
Bảng 2.3.7: Biến thiên D/E (max–min)
Chỉ số Giá trị
Biến thiên D/E (max-min) 0.256
  • Tính toán và phân tích cagr và độ biến thiên đòn bẩy
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:

  • Tăng trưởng CAGR (Bảng 2.3.6):
    • VonChuSoHuu (5.0%/năm) tăng trưởng nhanh hơn TongTaiSan (4.3%/năm) và NoPhaiTra (2.3%/năm).
    • Kết luận: GAS mở rộng quy mô chủ yếu dựa vào nguồn vốn nội tại, giảm dần sự phụ thuộc vào nợ vay.
    • Giá cổ phiếu tăng trưởng cao nhất (6.5%/năm), cho thấy thị trường có kỳ vọng tích cực vượt trội hơn so với tăng trưởng quy mô tài sản đơn thuần.
  • Biến thiên Đòn bẩy (Bảng 2.3.7):
    • Tỷ lệ nợ trên vốn chủ sở hữu (D/E) chỉ dao động trong khoảng 0.26 (max-min).
    • Kết luận: Thể hiện sự ổn định cao và chính sách tài chính an toàn, bảo thủ của GAS.

2.3.1.5. Chỉ báo ngưỡng rủi ro đòn bẩy (đếm số năm vượt ngưỡng)

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)")
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
  • Kiểm tra số năm tỷ lệ nợ/vốn chủ sở hữu vượt ngưỡng
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 NguongSoNam_VuotNguong (chuyển sang kiểu số nguyên) và hiển thị bằng kable.

Nhận xét:

  • Kiểm tra cho thấy có 9/11 năm D/E vượt ngưỡng 0.3 (an toàn cao), chỉ 1 năm vượt ngưỡng 0.5, và 0 năm vượt ngưỡng 1.0 (mất cân đối).
  • Kết luận: GAS luôn duy trì chính sách sử dụng đòn bẩy ở mức rất thấp và an toàn cao, phù hợp với đặc thù ngành có dòng tiền ổn định nhưng cần tránh rủi ro lãi suất. Cấu trúc vốn vững chắc và rủi ro tài chính thấp là cơ sở cho các phân tích hiệu suất tiếp theo.

2.3.1.6. Biến động năm (YoY) cho Tài sản – Vốn chủ – Nợ – Giá

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á")
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
  • Phân tích biến động hàng năm (yoy) cấu trúc tài chính và giá
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:

  • Tài sản & Vốn Chủ (TTS_YoY, VCSH_YoY): Duy trì mức tăng trưởng ổn định (trung bình 5–8%/năm), ngoại trừ một vài năm biến động mạnh (ví dụ: 2019–2020 do dữ liệu ngoại lai), cho thấy nền tảng tài chính ổn định.
  • Nợ phải trả (No_YoY): Biến động theo chu kỳ (tăng mạnh 2021, giảm mạnh 2022–2023), củng cố nhận định GAS chủ động kiểm soát đòn bẩy theo giai đoạn đầu tư.
  • Giá cổ phiếu (Gia_YoY): Biến động với biên độ lớn hơn nhiều so với các biến cơ bản (giảm -46.9% năm 2015, tăng +70.2% năm 2017).
  • Kết luận: Nền tảng tài chính GAS ổn định, nhưng giá cổ phiếu nhạy cảm cao với các yếu tố ngoài báo cáo (vĩ mô, giá dầu khí), hàm ý thị trường phản ứng mạnh mẽ theo kỳ vọng.

2.3.1.7. Phát hiện ngoại lai

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")
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. cat("\n")
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

Chi tiết Outlier cho biến TongTaiSan
Nam TongTaiSan
2019 621787389634
  • Phát hiện và thống kê giá trị ngoại lai (tukey rule)
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:

  1. Cấu trúc tài chính ổn định và an toàn:
    • Quy mô: Tổng tài sản trung bình vượt 62,4 nghìn tỷ VND.
    • Đòn bẩy (D/E): Tỷ lệ Nợ/Vốn Chủ Sở Hữu trung bình chỉ 0.36 và không có năm nào vượt ngưỡng rủi ro 1.0. Tỷ lệ này ổn định cao (Biến thiên max–min chỉ 0.26), cho thấy chính sách tài chính thận trọng và mức độ an toàn cao.
  2. Tăng trưởng bền vững, lợi thế vốn chủ sở Hữu:
    • Tăng trưởng Kép (CAGR): 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.
    • Giá trị Thị trường: Giá cổ phiếu tăng trưởng kép 6.5%/năm, cao hơn tăng trưởng tài sản cơ bản, phản ánh kỳ vọng tích cực của thị trường.
  3. Định giá và chất lượng lợi nhuận cao:
    • Định giá (P/E): Hệ số P/E trung bình \(\approx 9.93\), nằm trong vùng hợp lý và ổn định (Trung vị \(\approx 9.48\)).
    • Chất lượng lợi nhuận (CFO/LNST): Tỷ lệ trung vị \(\approx 1.19\), cho thấy lợi nhuận kế toán được chuyển đổi thành dòng tiền thực dồi dào, xác nhận chất lượng lợi nhuận của GAS là lành mạnh.
  4. Phân phối tương đối chuẩn và ít ngoại lai:
    • Hình dạng: Phần lớn các biến có độ lệch gần đối xứng (Skewness \(\approx \pm 1\)) và phân phối bẹt (Kurtosis \(< 3\)).
    • Ngoại lai: Chỉ phát hiện một ngoại lai duy nhất (chiếm 9.1%) ở biến 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.
  5. Kết luận tổng thể: Nhóm biến 2 phản ánh một doanh nghiệp có cấu trúc tài chính rất vững, định giá hợp lý, và khả năng tạo tiền mạnh. Dữ liệu ổn định, chỉ cần xử lý kỹ thuật đối với một điểm ngoại lai duy nhất ở Tổng tài sản trước khi thực hiện các phân tích mô hình hóa.

2.3.2. Phân tích tương quan & mối quan hệ

2.3.2.1. Chuẩn bị dữ liệu và lọc ngoại lai

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 ...
  • Làm sạch dữ liệu và chọn biến để phân tích tương quan
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:

  • Dữ liệu đã được làm sạch bằng cách loại bỏ năm 2019 (điểm ngoại lai duy nhất) thông qua bộ lọc Tổng tài sản.
  • Bộ dữ liệu 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.

2.3.2.2. Ma trận tương quan Pearson (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)")
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
  • Tính toán và phân tích ma trận tương quan pearson
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):

  1. Cấu trúc tài chính (TTS, Nợ, VCSH):
    • Tương quan cực mạnh (r = 0.967) giữa TTS và VCSH: Chứng tỏ tăng trưởng tổng tài sản chủ yếu được tài trợ bởi Vốn chủ sở hữu (cấu trúc vốn vững chắc).
    • D/E vs TTS (r = –0.013): Mối tương quan gần như bằng 0, khẳng định tính ổn định cao của đòn bẩy tài chính dù quy mô có thay đổi.
  2. Định giá và quy mô:
    • VonHoa vs TTS/VCSH (r \(\approx\) 0.83): Vốn hóa thị trường tương quan rất mạnh với Tổng tài sản và Vốn chủ sở hữu, chứng minh thị trường định giá GAS chủ yếu dựa trên quy mô tài sản và sức mạnh tài chính (định giá theo quy mô).
    • Giá vs TTS/VCSH (r \(\approx\) 0.8): Giá cổ phiếu tương quan cao với quy mô tài chính nội tại.
  3. Dòng tiền (CFO) và chất lượng lợi nhuận:
    • TyLe_CFO_Tren_LNST vs VonHoa (r = –0.882): Tương quan âm rất mạnh (nghịch đảo). Điều này hàm ý khi chất lượng lợi nhuận cao (tỷ lệ \(> 1\)), tốc độ mở rộng vốn hóa không bị thổi phồng, mà có thể có sự ổn định hơn.
    • CFO vs P/E (r = –0.13): Tương quan âm yếu, gợi ý rằng khi dòng tiền mạnh, P/E có xu hướng thấp hơn (định giá hợp lý hơn).

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á.

2.3.2.3. Phân tích tương quan nội tại (cấu trúc tài chính)

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
  • Phân tích tương quan giữa các yếu tố cấu trúc vốn nội tại
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:

  1. Tổng tài sản – Vốn chủ sở hữu (r = 0.967):
    • Mối tương quan dương cực mạnh, gần như hoàn hảo.
    • Kết luận: Sự tăng trưởng quy mô tài sản của GAS được tài trợ chủ yếu và tương ứng bằng nguồn vốn chủ sở hữu (từ lợi nhuận giữ lại). Đây là đặc trưng của doanh nghiệp có nền tảng tài chính lành mạnh và an toàn dài hạn.
  2. Tổng tài sản – Nợ phải trả (r = 0.807):
    • Mối tương quan cao, thể hiện Nợ là thành phần cấu trúc của Tài sản.
    • Tuy nhiên, do r (TTS–VCSH) \(>\) r (TTS–Nợ), Vốn chủ sở hữu là yếu tố chi phối trong tăng trưởng Tài sản.
  3. Nợ phải trả – Vốn chủ sở hữu (r = 0.629):
    • Mối tương quan dương trung bình, cho thấy Nợ và Vốn Chủ cùng tăng, nhưng Nợ chỉ tăng ở mức vừa phải.
    • Kết luận: GAS thực hiện chính sách kiểm soát nợ ổn định, sử dụng nợ như một công cụ bổ trợ chứ không phải là động lực chính để mở rộng quy mô.

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.

2.3.2.4. Phân tích mối quan hệ tổng hợp

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")
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
  • Phân tích tương quan tổng hợp các cặp biến chính
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:

  1. Tương quan mạnh nhất (r = 0.967): TTS–VCSH (Dương, Rất mạnh).
    • Ý nghĩa: Tăng trưởng Tài sản gần như song hành với Vốn Chủ Sở Hữu, xác nhận mô hình tăng trưởng dựa trên vốn tự có, bền vững và an toàn.
  2. Tương quan vừa/yếu (r \(\approx\) 0.434 / 0.276): ROE–CFO và ROE–Giá (Dương).
    • ROE–CFO: Tương quan dương vừa, cho thấy hiệu quả sinh lời (kế toán) và dòng tiền thực có cùng xu hướng nhưng không hoàn toàn đồng bộ.
    • ROE–Giá: Tương quan dương yếu, cho thấy thị trường định giá GAS dựa trên tiềm lực dài hạn hơn là chỉ phản ứng với ROE ngắn hạn.
  3. Tương quan yếu (r \(\approx\) 0.153 / -0.130): D/E–ROE và CFO–P/E.
    • Các mối quan hệ này yếu nhưng đi đúng hướng kỳ vọng kinh tế: Nợ vay không tạo ra hiệu quả vượt trội, và dòng tiền mạnh có xu hướng làm định giá P/E hợp lý hơn (P/E giảm).

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.

2.3.3. Trực quan hóa dữ liệu

2.3.3.1. Xu hướng theo thời gian (Index 2014 = 100)

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)

  • Trực quan hóa xu hướng quy mô và cấu trúc vốn
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

  1. 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.

  2. Quản trị nợ chủ động (Biểu đồ 3 & 4):

    • Nợ phải trả biến động theo chu kỳ đầu tư (đạt đỉnh 165% năm 2021) và sau đó giảm dần.
    • Tỷ lệ D/E luôn duy trì trong vùng an toàn (\(\le 0.5\)), với hầu hết các năm dao động quanh 0.3–0.4.
    • Kết luận: GAS thể hiện khả năng quản trị đòn bẩy xuất sắc, duy trì cấu trúc vốn ổn định, bền vững và rủi ro nợ thấp.
  3. 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.

2.3.3.2. Giá – Định giá – Quy mô thị trường

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)

  • Trực quan hóa diễn biến giá, định giá và quy mô thị trường
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

  1. 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.

  2. Đị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.

  3. 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.

2.3.3.3. Dòng tiền & Chất lượng lợi nhuận

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)

  • Trực quan hóa diễn biến dòng tiền và chất lượng lợi nhuận
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 CFOGia_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

  1. 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).

  2. 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.

  3. 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.

  4. 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.

2.3.3.4. Quan hệ Giá/Định giá với Quy mô – 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)

  • Trực quan hóa mối quan hệ giá trị thị trường và quy mô tài chính
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 TongTaiSanGia_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 VonChuSoHuuGia_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)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 TongTaiSanVonHoa_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

  1. Đị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).

  2. 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.

  3. 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.

2.3.3.5. Cấu trúc vốn – Tương quan tổng hợp

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)

  • Trực quan hóa cơ cấu vốn, tăng trưởng tương đối và p/e theo thời gian
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ợ/TTSVCSH/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)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:

  1. 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.

  2. Tăng trưởng tương đối (Biểu đồ 19 - Slopegraph):

    • Vốn hóa (\(\approx 2.27\times\)) tăng trưởng nhanh nhất, cho thấy định giá thị trường được “nâng hạng” (re-rating).
    • VCSH (\(\approx 1.63\times\))TTS (\(\approx 1.52\times\)) tăng trưởng đồng pha, chứng tỏ sự mở rộng quy mô là thực chất.
    • Nợ (\(\approx 1.26\times\)) tăng trưởng chậm nhất.
    • Kết luận: Sự kết hợp giữa (tăng trưởng vốn chủ) và (kỷ luật nợ) đã dẫn đến (tăng trưởng vốn hóa vượt trội), cho thấy thị trường đánh giá cao chất lượng tài chính nội tại.
  3. 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ít bị ảnh hưởng bởi đầu cơ so với các doanh nghiệp có đòn bẩy cao.

2.3.3.6. Giá và Quy mô (TTS, VCSH) - Có và không có outlier (2019)

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)")

  • Kiểm chứng nhạy cảm mối quan hệ giá ↔︎ quy mô (tổng tài sản)
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ô

  1. Ả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.

  2. 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ơnkhoả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ổn định và bền vững.

  3. 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.

2.3.3.7. CFO - Giá & P/E - có và không có outlier (2019)

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á")

  • Trực quan hóa mối 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)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. CFO vs Giá cổ phiếu (Hình 23):
    • Xu hướng: Mối quan hệ dương nhẹ (độ dốc dương). Các năm có CFO cao hơn thường đi kèm với giá cổ phiếu cao hơn.
    • Độ chặt: Khoảng tin cậy (LM) khá rộng, cho thấy CFO là yếu tố hỗ trợ nhưng không chi phối đơn lẻ giá cổ phiếu (giá còn bị ảnh hưởng bởi nhiều yếu tố khác).
    • Hình dạng: Đường LOESS (đỏ chấm) gợi ý quan hệ phi tuyến tính, đặc biệt nhạy cảm với các biến động chu kỳ (như giai đoạn 2020–2022).
  2. CFO vs P/E cuối năm (Hình 24):
    • Xu hướng: Mối quan hệ âm yếu (độ dốc âm nhẹ). Khi CFO tăng, P/E có xu hướng giảm nhẹ.
    • Ý nghĩa: Điều này phản ánh thực tế: khi dòng tiền thực dồi dào, thị trường có niềm tin cao vào chất lượng lợi nhuận và có xu hướng định giá hợp lý hơn (P/E giảm về vùng chiết khấu theo thực lực), thay vì đẩy P/E lên cao dựa trên kỳ vọng mù quáng.
  3. Kết luận: Dòng tiền hoạt động (CFO) là yếu tố hỗ trợ quan trọng cho giá cổ phiếu trong dài hạn. Về mặt định giá, CFO giúp điều tiết P/E về vùng hợp lý, giảm rủi ro định giá quá cao.

2.3.3.8. Rolling correlation (3 năm)

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")

  • Phân tích tương quan di động 3 năm (rolling correlation)
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

  1. 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 địnhphụ thuộc vào chu kỳ thị trường và cấu trúc tài chính ngắn hạn.

  2. 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.

  3. 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).

  4. 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.

2.3.3.9. Heatmap tương quan (trước/sau loại 2019)

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))

  • Kiểm tra ảnh hưởng của ngoại lai đến ma trận tương quan (heatmap)
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. Hình 29 (Full Sample - n=11): Sự hiện diện của outlier 2019 đã làm méo mó và suy yếu nghiêm trọng các mối quan hệ nền tảng:
    • Tương quan TTS – VCSH giảm mạnh từ \(\approx 0.97\) xuống 0.50.
    • Tương quan Giá – TTS giảm xuống 0.31 (yếu).
    • Tương quan Tỷ lệ CFO/LNST với các biến quy mô bị kéo về âm rất mạnh (\(\text{r} \approx -0.87\)), do hiệu ứng phóng đại của LNST nhỏ bất thường năm 2019.
  2. Hình 30 (Loại 2019 - n=10): Sau khi loại bỏ outlier, cấu trúc tương quan ổn định trở lại và nhất quán về mặt kinh tế:
    • TTS – VCSH phục hồi lên 0.97.
    • Giá – TTS/VCSH phục hồi lên \(\approx 0.8\) (rất mạnh).
    • Toàn bộ cấu trúc mạng lưới tương quan chuyển sang “ấm hơn” (mối quan hệ dương mạnh chiếm ưu thế).
  3. Kết luận: Năm 2019 là nguồn sai lệch hệ thống duy nhất trong chuỗi thời gian. Việc loại bỏ nó là bắt buộc để các phân tích hồi quy sau này phản ánh đúng mối quan hệ tuyến tính bền vững của GAS: định giá thị trường được xác định bởi quy mô tài sản và vốn chủ sở hữu.

2.3.3.10. Biểu đồ chẩn đoán ảnh hưởng và đòn bẩy (Influence plots)

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
1. influencePlot(model2, id.method="noteworthy", main="(32) Influence: Giá~VCSH")
   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
1. influencePlot(model3, id.method="noteworthy", main="(33) Influence: Giá~CFO")
  StudRes   Hat CookD
1   -1.73 0.344 0.642
2   -2.60 0.118 0.274
3   -1.11 0.374 0.359
1. influencePlot(model4, id.method="noteworthy", main="(34) Influence: P/E~CFO")

  StudRes   Hat CookD
1   -1.30 0.344 0.412
2   -1.69 0.118 0.158
3   -2.08 0.374 0.944
1. par(mfrow=c(1,1))
  • Phân tích ảnh hưởng điểm dữ liệu đến mô hình hồi quy (cook’s distance)
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

  1. Ảnh hưởng hệ thống (Hình 31: Giá ~ TTS):
    • Năm 2019 (mã số 6) có Cook’s Distance cực cao (9.68), vượt xa ngưỡng 1 (ngưỡng loại bỏ).
    • Kết luận: Mối quan hệ Giá \(\sim\) TTS bị méo mó nghiêm trọng do outlier 2019. Cần loại bỏ năm này khi mô hình hóa.
  2. Ảnh hưởng trung bình (Hình 33 & 34):
    • Giá ~ CFO (33): Các năm 2014, 2016 là các điểm đòn bẩy cao.
    • P/E ~ CFO (34): Năm 2016 có Cook’s D cao nhất (\(\approx 0.94\)), gần ngưỡng 1.
    • Kết luận: Các mô hình này nhạy cảm hơn với các năm đầu chu kỳ (2014–2016) nhưng không bị chi phối bởi một outlier duy nhất như mô hình Giá~TTS.
  3. Ảnh hưởng thấp (Hình 32: Giá ~ VCSH):
    • Không có điểm nào có Cook’s D cao một cách nguy hiểm.
    • Kết luận: Mối quan hệ Giá \(\sim\) Vốn Chủ Sở Hữuổn định nhất và bền vững nhất trong các mô hình được kiểm tra.

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.

2.3.3.11. Slopegraph (VCSH → Vốn hoá)

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")

  • Trực quan hóa tăng trưởng tương đối vốn chủ sở hữu và vốn hóa thị trường
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 VonChuSoHuuVonHoa_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_linegeom_point: Vẽ biểu đồ đường và điểm.
* geom_text_repel: Dán nhãn chỉ số tại năm đầu (2014)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

  1. 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.

  2. 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\)).

  3. Đị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.

2.3.3.12. Biểu đồ bổ trợ (P/E và Vốn hoá – VCSH)

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)")

  • Trực quan hóa diễn biến p/e và so sánh vốn hóa - vốn chủ
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

  1. 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ố.

  2. So sánh vốn hóa vs VCSH (Hình 37):

    • Pha 1 (2015–2019): Hầu hết các điểm nằm dưới đường y=x (\(\text{Vốn hóa} < \text{VCSH}\)), phản ánh định giá chiết khấu (P/B < 1) do tâm lý thận trọng trong giai đoạn giá dầu khí suy giảm.
    • Pha 2 (2020–2024): Các điểm dữ liệu vượt lên trên đường y=x, đặc biệt năm 2024, cho thấy thị trường định giá cao hơn giá trị sổ sách (P/B \(\approx 2.4\)).
    • Kết luận: GAS đã chuyển từ “định giá chiết khấu” sang “định giá cao hơn sổ sách” (Giá trị Vượt trội), phản ánh sự tin tưởng vào khả năng sinh lời và vị thế độc quyền của doanh nghiệp.

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.

2.4. KẾT LUẬN VÀ ĐỀ XUẤT

2.4.1. Kết luận tổng hợp

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ộiquá trình tái định giá tích cực của thị trường:

  1. Cấu trúc tài chính tự chủ và an toàn:
    • Tăng trưởng tổng tài sản và Vốn chủ sở hữu có mối tương quan cực mạnh (\(\text{r} = 0.97\)), khẳng định mô hình tăng trưởng dựa trên vốn nội tại (lợi nhuận giữ lại).
    • Tỷ lệ Nợ/VCSH (D/E) luôn ở mức an toàn (0.25–0.50), chứng minh quản trị đòn bẩy hiệu quả và rủi ro tài chính thấp.
  2. Chất lượng dòng tiền và Định giá hợp lý:
    • Tỷ lệ CFO/LNST ổn định \(\approx 1\) trong những năm gần đây, khẳng định lợi nhuận có chất lượng cao (được hiện thực hóa thành tiền mặt).
    • Mối tương quan giữa Vốn hóa và Vốn Chủ Sở Hữurất mạnh (\(\text{r} \approx 0.78\))bền vững (Hình 37), cho thấy thị trường định giá dựa trên giá trị sổ sách và quy mô vốn thực.
    • Mặt bằng P/E duy trì trong vùng hợp lý (9–14 lần)P/E \(\sim\) CFO có xu hướng âm yếu (Hình 24), phản ánh thị trường định giá hợp lý, ít bị đầu cơ.
  3. Tái định giá vượt trội:
    • GAS đã chuyển từ giai đoạn “định giá chiết khấu” (2015–2019, Vốn hóa \(<\) VCSH) sang giai đoạn “định giá vượt giá trị sổ sách” (2020–2024, Vốn hóa \(\approx 2.4 \times\) VCSH).
    • Hiện tượng Vốn hóa tăng nhanh hơn VCSH (Hình 35) chứng minh thị trường đang thưởng thêm giá trị (premium) cho GAS nhờ vị thế độc quyền, dòng tiền ổn định và kỳ vọng tăng trưởng bền vững.
  4. Độ tin cậy Dliệu:
    • Dữ liệu ổn định và đáng tin cậy sau khi xử lý kỹ thuật 1 điểm ngoại lai hệ thống duy nhất năm 2019 (Cook’s D cao).

2.4.2. Hạn chế và Đề xuất

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.