# Đọc và làm sạch dữ liệu (Cập nhật tên file tiếng Anh)
dax <- read.csv("DAX Historical Data.csv") %>% select(Date, Price) %>% rename(DAX = Price, Ngày = Date)
hnx <- read.csv("HNX Historical Data.csv") %>% select(Date, Price) %>% rename(HNX30 = Price, Ngày = Date)
vn30 <- read.csv("VN 30 Historical Data.csv") %>% select(Date, Price) %>% rename(VN30 = Price, Ngày = Date)
clean_num <- function(x) as.numeric(gsub(",", "", as.character(x)))
dax$DAX <- clean_num(dax$DAX)
hnx$HNX30 <- clean_num(hnx$HNX30)
vn30$VN30 <- clean_num(vn30$VN30)
# Định dạng ngày
dax$Ngày <- as.Date(dax$Ngày, tryFormats = c("%m/%d/%Y", "%d/%m/%Y", "%Y-%m-%d"))
hnx$Ngày <- as.Date(hnx$Ngày, tryFormats = c("%m/%d/%Y", "%d/%m/%Y", "%Y-%m-%d"))
vn30$Ngày <- as.Date(vn30$Ngày, tryFormats = c("%m/%d/%Y", "%d/%m/%Y", "%Y-%m-%d"))
# Gộp dữ liệu lấy các ngày trùng khớp và cắt đúng 280 mẫu mới nhất
df <- vn30 %>%
inner_join(hnx, by = "Ngày") %>%
inner_join(dax, by = "Ngày") %>%
na.omit() %>%
arrange(Ngày) %>%
tail(280)
# IN RA KẾT QUẢ DỮ LIỆU
head(df)
## Ngày VN30 HNX30 DAX
## 150 2025-08-14 1793.78 285.15 24377.50
## 151 2025-08-15 1783.25 282.34 24359.30
## 152 2025-08-18 1786.37 283.87 24314.77
## 153 2025-08-19 1810.46 286.45 24423.07
## 154 2025-08-20 1828.46 283.73 24276.97
## 155 2025-08-21 1874.91 284.39 24293.34
dim(df)
## [1] 280 4
names(df)
## [1] "Ngày" "VN30" "HNX30" "DAX"
str(df)
## 'data.frame': 280 obs. of 4 variables:
## $ Ngày : Date, format: "2025-08-14" "2025-08-15" ...
## $ VN30 : num 1794 1783 1786 1810 1828 ...
## $ HNX30: num 285 282 284 286 284 ...
## $ DAX : num 24378 24359 24315 24423 24277 ...
# Nhận xét
# Dữ liệu gồm 280 quan sát và các biến định lượng: VN30, HNX30, DAX.
# Các chỉ số này được đo lường bằng thang đo Ratio Scale.
# CÂU b
# MA HOA VN30
# Chuyển biến VN30 từ thang đo Ratio sang Ordinal Scale thành 2 nhóm:
# Nhóm 1 (Thấp) là các giá trị nhỏ hơn hoặc bằng giá trị trung vị.
# Nhóm 2 (Cao) là các giá trị lớn hơn giá trị trung vị.
VN30_GOC = as.numeric(df$VN30)
VN30_MH = VN30_GOC
VN30_MH = replace(VN30_MH, VN30_GOC <= median(VN30_GOC), 1)
VN30_MH = replace(VN30_MH, VN30_GOC > median(VN30_GOC), 2)
# Gắn biến đã mã hóa vào bảng dữ liệu gốc để dùng cho các câu sau
df$VN30_Cat = factor(VN30_MH, levels = c(1, 2), labels = c("Thấp", "Cao"), ordered = TRUE)
# In ra 15 dòng đầu tiên để kiểm tra
bang_ma_hoa = data.frame(VN30_GOC, VN30_MH)
head(bang_ma_hoa, 15)
## VN30_GOC VN30_MH
## 1 1793.78 1
## 2 1783.25 1
## 3 1786.37 1
## 4 1810.46 1
## 5 1828.46 1
## 6 1874.91 1
## 7 1814.02 1
## 8 1783.12 1
## 9 1849.05 1
## 10 1848.55 1
## 11 1861.20 1
## 12 1865.38 1
## 13 1859.59 1
## 14 1883.59 1
## 15 1845.48 1
# CÂU c
# Thống kê cơ bản các biến Ratio Scale
dulieu_ratio <- df %>% select(VN30, HNX30, DAX)
summary(dulieu_ratio)
## VN30 HNX30 DAX
## Min. :1741 Min. :235.4 Min. :22301
## 1st Qu.:1876 1st Qu.:255.8 1st Qu.:23960
## Median :1940 Median :267.4 Median :24423
## Mean :1938 Mean :270.6 Mean :24554
## 3rd Qu.:1997 3rd Qu.:280.1 3rd Qu.:25119
## Max. :2097 Max. :336.2 Max. :26570
# Thống kê chi tiết bằng thư viện psych giống bài mẫu
library(psych)
describe(dulieu_ratio)
## vars n mean sd median trimmed mad min max
## VN30 1 280 1937.89 76.73 1940.37 1938.17 94.09 1741.05 2096.76
## HNX30 2 280 270.55 19.06 267.44 268.39 18.32 235.36 336.16
## DAX 3 280 24554.18 856.64 24422.82 24535.94 870.70 22300.75 26569.99
## range skew kurtosis se
## VN30 355.71 -0.04 -0.75 4.59
## HNX30 100.80 0.95 0.71 1.14
## DAX 4269.24 0.09 -0.24 51.19
# Số lượng quan sát ở mỗi nhóm của biến phụ thuộc vào dữ liệu thực tế
# và được xác định thông qua bảng tần số sau khi mã hóa.
table(df$VN30_Cat)
##
## Thấp Cao
## 140 140
library(ggplot2)
# CÂU d.1: Đồ thị đường cho 3 biến định lượng (Ratio Scale)
# Chia DAX cho 10 để đồ thị dễ nhìn hơn khi đặt cạnh VN30 và HNX30
ggplot(df, aes(x = Ngày)) +
geom_line(aes(y = VN30, color = "VN30"), linewidth=1) +
geom_line(aes(y = HNX30, color = "HNX30"), linewidth=1) +
geom_line(aes(y = DAX/10, color = "DAX (thu nhỏ 10 lần)"), linewidth=1) +
labs(title = "Biểu đồ biến động giá: VN30, HNX30 và DAX",
y = "Điểm chỉ số", x = "Thời gian") +
theme_minimal()
# CÂU d.2: Đồ thị cột cho biến phân loại (Ordinal Scale)
ggplot(df, aes(x = VN30_Cat, fill = VN30_Cat)) +
geom_bar() +
labs(title = "Biểu đồ cột: Phân loại VN30 (Cao/Thấp)",
x = "Nhóm", y = "Tần số") +
theme_minimal()
# CÂU d.3: Đồ thị tròn cho biến phân loại (Ordinal Scale)
ggplot(as.data.frame(table(df$VN30_Cat)), aes(x="", y=Freq, fill=Var1)) +
geom_bar(stat="identity", width=1) +
coord_polar("y", start=0) +
labs(title = "Biểu đồ tròn: Tỷ trọng phân loại VN30") +
theme_void()
## 5. Phân tích tương quan (Câu e)
library(corrplot)
# Tính ma trận tương quan
cor_matrix <- cor(df[, c("VN30", "HNX30", "DAX")])
print("Ma trận tương quan:")
## [1] "Ma trận tương quan:"
print(cor_matrix)
## VN30 HNX30 DAX
## VN30 1.000000000 0.004535101 0.3851028
## HNX30 0.004535101 1.000000000 0.4238143
## DAX 0.385102783 0.423814257 1.0000000
# Vẽ biểu đồ tương quan (Heatmap)
corrplot(cor_matrix, method = "color", addCoef.col = "black",
title = "Ma trận tương quan giữa VN30, HNX30 và DAX",
mar=c(0,0,2,0), tl.col = "black", tl.srt = 0)
## 6. Mô hình Hồi quy và Kiểm định (Câu f & g)
library(car)
library(lmtest)
# CÂU f: Chạy mô hình hồi quy tuyến tính (VN30 theo HNX30 và DAX)
model <- lm(VN30 ~ HNX30 + DAX, data = df)
print("KẾT QUẢ MÔ HÌNH HỒI QUY:")
## [1] "KẾT QUẢ MÔ HÌNH HỒI QUY:"
summary(model)
##
## Call:
## lm(formula = VN30 ~ HNX30 + DAX, data = df)
##
## Residuals:
## Min 1Q Median 3Q Max
## -166.079 -51.247 -4.772 57.925 128.274
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 1.121e+03 1.202e+02 9.331 < 2e-16 ***
## HNX30 -7.787e-01 2.420e-01 -3.218 0.00144 **
## DAX 4.183e-02 5.384e-03 7.771 1.53e-13 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 69.77 on 277 degrees of freedom
## Multiple R-squared: 0.179, Adjusted R-squared: 0.1731
## F-statistic: 30.2 on 2 and 277 DF, p-value: 1.37e-12
# CÂU g: Thực hiện các kiểm định chẩn đoán mô hình
print("---------------------------------------------------")
## [1] "---------------------------------------------------"
print("1. Kiểm định Đa cộng tuyến (VIF):")
## [1] "1. Kiểm định Đa cộng tuyến (VIF):"
# Nếu VIF < 2 (hoặc < 10) là tốt, không bị đa cộng tuyến
vif(model)
## HNX30 DAX
## 1.218945 1.218945
print("---------------------------------------------------")
## [1] "---------------------------------------------------"
print("2. Kiểm định Phương sai sai số (Breusch-Pagan):")
## [1] "2. Kiểm định Phương sai sai số (Breusch-Pagan):"
# H0: Phương sai sai số không đổi. (p-value > 0.05 là tốt)
bptest(model)
##
## studentized Breusch-Pagan test
##
## data: model
## BP = 14.293, df = 2, p-value = 0.0007878
print("---------------------------------------------------")
## [1] "---------------------------------------------------"
print("3. Kiểm định Phân phối chuẩn của phần dư (Shapiro-Wilk):")
## [1] "3. Kiểm định Phân phối chuẩn của phần dư (Shapiro-Wilk):"
# H0: Phần dư có phân phối chuẩn. (p-value > 0.05 là tốt)
shapiro.test(resid(model))
##
## Shapiro-Wilk normality test
##
## data: resid(model)
## W = 0.97708, p-value = 0.0001808
print("---------------------------------------------------")
## [1] "---------------------------------------------------"
print("4. Kiểm định Tự tương quan (Durbin-Watson):")
## [1] "4. Kiểm định Tự tương quan (Durbin-Watson):"
# Thêm kiểm định DW giống bài mẫu
dwtest(model)
##
## Durbin-Watson test
##
## data: model
## DW = 0.15104, p-value < 2.2e-16
## alternative hypothesis: true autocorrelation is greater than 0
print("---------------------------------------------------")
## [1] "---------------------------------------------------"
print("5. Vẽ đồ thị chẩn đoán phần dư:")
## [1] "5. Vẽ đồ thị chẩn đoán phần dư:"
# Chia màn hình làm 4 phần (2 dòng, 2 cột) để hiển thị cùng lúc 4 hình
par(mfrow = c(2, 2))
# Vẽ đồ thị
plot(model)