Lời đầu tiên, em xin gửi lời cảm ơn chân thành và sâu sắc nhất đến thầy Trần Mạnh Tường, giảng viên bộ môn Ngôn ngữ lập trình trong phân tích dữ liệu.
Trong suốt quá trình học tập và thực hiện bài tiểu luận này, em đã nhận được sự hướng dẫn, chỉ bảo tận tình và những góp ý chuyên môn quý báu từ Thầy. Thầy không chỉ truyền đạt cho em những kiến thức nền tảng vững chắc mà còn khơi dậy trong em niềm đam mê và tư duy phân tích khoa học.
Mặc dù đã rất nỗ lực, nhưng do kiến thức và kinh nghiệm còn hạn chế, bài tiểu luận chắc chắn không thể tránh khỏi những thiếu sót. Em rất mong nhận được những ý kiến đóng góp của Thầy để bài làm của em được hoàn thiện hơn.
Một lần nữa, em xin chân thành cảm ơn Thầy!
library(readxl)
library(dplyr)
library(ggplot2)
library(data.table)
library(tidyr)
library(knitr)
library(kableExtra)
library(viridis)
library(ggrepel)
library(scales)
library(lubridate)
library(moments)
library(corrplot)
library(treemapify)
library(ggridges)
library(tibble)
library(showtext)
library(patchwork)
Trong quá trình thực hiện đề tài, một số gói thư viện trong R được sử dụng nhằm hỗ trợ xử lý, phân tích và trực quan hóa dữ liệu một cách hiệu quả:
data <- read.csv(file.choose(), header = TRUE)
Ở dòng lệnh này, tác giả đã chọn file transactions_data.csv để thêm file csv vào RStudio. Sau đó tác giả đã gán data.frame này vào biến data để thuận lợi phân tích.
setDT(data)
Lệnh setDT(data) chuyển đối tượng data từ dạng data frame sang data.table, giúp xử lý dữ liệu nhanh và tiết kiệm bộ nhớ hơn.
Lệnh này tối ưu hiệu năng xử lý, hỗ trợ phân tích, tổng hợp và trích xuất thông tin được thực hiện nhanh và chính xác hơn.
kable(head(data, 5),
caption = "5 dòng dữ liệu đầu tiên của bộ dữ liệu.",
format = "latex", booktabs = TRUE) %>%
kable_styling(latex_options = c("striped", "scale_down", "hold_position"),
font_size = 9,
position = "center")
Giải thích
Lệnh kable(head(data, 5) hiển thị 5 dòng đầu tiên của bộ dữ liệu dưới dạng bảng trong báo cáo PDF, kết hợp với kable_styling() để định dạng bảng có đường kẻ, căn giữa và thu gọn kích thước font. Mục đích nhằm kiểm tra nhanh cấu trúc dữ liệu.
Nhận xét
Kết quả cho thấy có một giao dịch âm (-77.00) thể hiện hoàn tiền, các giao dịch còn lại có giá trị nhỏ và diễn ra vào ban đêm (hour = 0).
Phương thức thanh toán chủ yếu là Swipe Transaction, các cột lỗi và rủi ro đều ở trạng thái FALSE, chứng tỏ dữ liệu ban đầu ổn định và hợp lệ.
kable(tail(data, 5),
caption = "5 dòng dữ liệu cuối cùng của bộ dữ liệu.", booktabs = TRUE) %>%
kable_styling(latex_options = c("striped", "scale_down", "hold_position"),
font_size = 9,
position = "center")
| id | date | client_id | card_id | amount | use_chip | merchant_id | merchant_city | merchant_state | zip | mcc | errors |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 23761868 | 2019-10-31 23:56:00 | 1718 | 2379 | $1.11 | Chip Transaction | 86438 | West Covina | CA | 91792 | 5499 | |
| 23761869 | 2019-10-31 23:56:00 | 1766 | 2066 | $12.80 | Online Transaction | 39261 | ONLINE | NA | 5815 | ||
| 23761870 | 2019-10-31 23:57:00 | 199 | 1031 | $40.44 | Swipe Transaction | 2925 | Allen | TX | 75002 | 4900 | |
| 23761873 | 2019-10-31 23:58:00 | 1986 | 5443 | $4.00 | Chip Transaction | 46284 | Daly City | CA | 94014 | 5411 | |
| 23761874 | 2019-10-31 23:59:00 | 489 | 5697 | $12.88 | Chip Transaction | 24658 | Greenbrier | TN | 37073 | 5921 |
Giải thích
Lệnh kable(tail(data, 5), …) được sử dụng để hiển thị 5 dòng dữ liệu cuối cùng trong bộ dữ liệu. Mục đích của đoạn mã là kiểm tra tính toàn vẹn của dữ liệu ở phần cuối, xác định xem dữ liệu có bị thiếu, lỗi hoặc ngắt quãng trước khi đưa vào phân tích.
Nhận xét
Kết quả cho thấy toàn bộ 5 quan sát cuối đều thuộc ngày 31/10/2019, diễn ra trong khung giờ tối (23h) và có giá trị giao dịch nhỏ dưới 50 USD, phản ánh nhóm chi tiêu thấp.
Phương thức thanh toán chủ yếu là giao dịch bằng “Chip”, xen kẽ với “Swipe” và “Online”, thể hiện tính đa dạng trong hành vi chi tiêu của khách hàng vào giai đoạn cuối.
str(data)
## Classes 'data.table' and 'data.frame': 13305915 obs. of 12 variables:
## $ id : int 7475327 7475328 7475329 7475331 7475332 7475333 7475334 7475335 7475336 7475337 ...
## $ date : chr "2010-01-01 00:01:00" "2010-01-01 00:02:00" "2010-01-01 00:02:00" "2010-01-01 00:05:00" ...
## $ client_id : int 1556 561 1129 430 848 1807 1556 1684 335 351 ...
## $ card_id : int 2972 4575 102 2860 3915 165 2972 2140 5131 1112 ...
## $ amount : chr "$-77.00" "$14.57" "$80.00" "$200.00" ...
## $ use_chip : chr "Swipe Transaction" "Swipe Transaction" "Swipe Transaction" "Swipe Transaction" ...
## $ merchant_id : int 59935 67570 27092 27092 13051 20519 59935 39021 50292 3864 ...
## $ merchant_city : chr "Beulah" "Bettendorf" "Vista" "Crown Point" ...
## $ merchant_state: chr "ND" "IA" "CA" "IN" ...
## $ zip : num 58523 52722 92084 46307 20776 ...
## $ mcc : int 5499 5311 4829 4829 5813 5942 5499 4784 7801 5813 ...
## $ errors : chr "" "" "" "" ...
## - attr(*, ".internal.selfref")=<externalptr>
Hàm str(data) trong R được dùng để mô tả cấu trúc tổng thể của đối tượng dữ liệu, giúp nắm nhanh loại dữ liệu, số lượng quan sát, số biến, cũng như kiểu dữ liệu của từng biến. Trong trường hợp này, data là một đối tượng thuộc lớp data.table và data.frame, gồm 13.305.915 quan sát và 21 biến. Các biến bao gồm:
unique_use_chip <- data[, .N, by = .(`Phương thức` = use_chip)][order(-N)]
kable(unique_use_chip,
caption = "Thống kê số lượng theo phương thức thanh toán.", booktabs = TRUE,
col.names = c("Phương thức thanh toán", "Số lượng giao dịch")) %>%
kable_styling(font_size = 11, position = "center",latex_options = "hold_position")
| Phương thức thanh toán | Số lượng giao dịch |
|---|---|
| Swipe Transaction | 6967185 |
| Chip Transaction | 4780818 |
| Online Transaction | 1557912 |
Giải thích
Đoạn mã trên sử dụng cú pháp của data.table để thống kê tần suất xuất hiện của từng phương thức thanh toán trong biến use_chip.
= use_chip) dùng để gom nhóm dữ
liệu theo từng loại phương thức.Nhận xét
Kết quả cho thấy phương thức “Swipe Transaction” chiếm tỷ trọng lớn nhất với 6.967.185 giao dịch, tiếp theo là “Chip Transaction” với 4.780.818 và “Online Transaction” với 1.557.912 giao dịch.
dim(data)
## [1] 13305915 12
Lệnh dim(data) trong R được dùng để kiểm tra kích thước của bộ dữ liệu, bao gồm số dòng (quan sát) và số cột (biến). Kết quả cho biết bộ dữ liệu có 13.305.915 quan sát và 12 biến.
names(data)
## [1] "id" "date" "client_id"
## [4] "card_id" "amount" "use_chip"
## [7] "merchant_id" "merchant_city" "merchant_state"
## [10] "zip" "mcc" "errors"
Lệnh names(data) được sử dụng để liệt kê tên các biến (cột) trong bộ dữ liệu, giúp nắm tổng quan cấu trúc dữ liệu, từ đó dễ dàng định hướng các bước xử lý, phân tích và trích xuất thông tin sau này. Trong bảng hiển thị, bao gồm các thông tin chi tiết sau:
Biến id là mã định danh duy nhất cho từng giao dịch, giúp phân biệt các giao dịch với nhau.
Biến date ghi nhận thời điểm giao dịch xảy ra, bao gồm ngày và giờ chính xác.
Biến client_id (mã khách hàng) cho biết thông tin lưu trữ về khách hàng.
Biến card_id (mã thẻ được sử dụng), cho phép theo dõi hành vi tiêu dùng theo từng cá nhân hoặc thiết bị thanh toán.
Biến amount thể hiện số tiền của giao dịch, trong đó giá trị dương tương ứng với số tiền giao dịch thành công và giá trị âm tương ứng với số tiền giao dịch thất bại(hoàn tiền).
Biến use_chip thể hiện phương thức thanh toán giao dịch, bao gồm ba loại: Chip Transaction (giao dịch qua chip), Online Transaction (thanh toán trực tuyến), và Swipe Transaction (quẹt thẻ từ).
Biến merchant_id thể hiện mã nhà bán hàng
Biến merchant_city thể hiện thành phố/ nơi thực hiện giao dịch
Biến merchant_state thể hiện bang/ tiểu bang của nơi thực hiện giao dịch
Biến zip thể hiện mã bưu điện tương ứng với từng thành phố - nơi thực hiện giao dịch
Biến mcc (Merchant Category Code) phân loại ngành nghề kinh doanh của nhà bán hàng
Ví dụ:
Biến errors ghi nhận mã lỗi (nếu có) trong quá trình xử lý giao dịch; giá trị bằng 0 cho thấy giao dịch thành công, trong khi các lỗi chi tiết sẽ được ghi chú ở mục này phản ánh sự cố kỹ thuật hoặc từ chối thanh toán.
unique_cat_counts <- data[, lapply(.SD, uniqueN), .SDcols = c(
"use_chip", "merchant_city", "merchant_state", "mcc")]
unique_cat_df <- data.frame(
`Biến` = names(unique_cat_counts),
`Số lượng duy nhất` = as.integer(unique_cat_counts))
kable(unique_cat_df,
caption = "Số lượng giá trị duy nhất cho các biến định tính.", booktabs = TRUE,
col.names = c("Biến", "Số lượng duy nhất")) %>%
kable_styling(font_size = 11, position = "center",latex_options = "hold_position")
| Biến | Số lượng duy nhất |
|---|---|
| use_chip | 3 |
| merchant_city | 12492 |
| merchant_state | 200 |
| mcc | 109 |
Giải thích
Đoạn lệnh sử dụng kết hợp các hàm trong data.table để đếm số lượng giá trị duy nhất (unique) của các biến định tính.
Lệnh lapply(.SD, uniqueN) được áp dụng cho bốn biến: use_chip, merchant_city, merchant_state, và mcc. Kết quả được lưu sau đó chuyển thành data frame để trình bày bằng kable.
Nhận xét
Kết quả cho thấy:
na_counts <- colSums(is.na(data))
na_df <- data.frame(
`Biến` = names(na_counts),
`Số lượng NA` = na_counts)
if(sum(na_counts) > 0) { na_df[na_counts > 0, ] %>%
kable(caption = "Số lượng giá trị thiếu theo từng cột.",
booktabs = TRUE, row.names = FALSE) %>%
kable_styling(font_size = 11, position = "center", latex_options = "hold_position")
} else {print("Bộ dữ liệu không có giá trị NA.")}
| Biến | Số.lượng.NA |
|---|---|
| zip | 1652706 |
Giải thích
Lệnh is.na(data) tạo một ma trận logic xác định vị trí các giá trị bị thiếu, sau đó colSums() tính tổng số lượng NA cho mỗi cột. Kết quả được lưu vào na_counts và trình bày lại trong na_df.
Cấu trúc if(sum(na_counts) > 0) giúp tự động phát hiện xem dữ liệu có bị thiếu hay không — nếu có, hiển thị bảng kết quả; nếu không, in ra thông báo rằng dữ liệu đầy đủ.
Nhận xét
Kết quả cho thấy có ba biến chứa giá trị thiếu:
if ("zip" %in% names(data)) {
data[, zip := NULL]
}
print(paste("Số biến còn lại:", ncol(data)))
## [1] "Số biến còn lại: 11"
Giải thích
Lệnh if (“zip” %in% names(data)) kiểm tra xem biến zip có tồn tại trong bộ dữ liệu hay không.
Nếu có, hàm data[, zip := NULL] sẽ xóa hoàn toàn cột đó ra khỏi data. Sau cùng, lệnh print(paste(“Số biến còn lại:”, ncol(data))) in ra số lượng biến còn lại nhằm xác nhận kết quả sau khi xử lý.
Nhận xét
Sau khi loại bỏ biến zip. Việc loại bỏ biến này là hợp lý vì zip là mã bưu điện, không mang ý nghĩa thống kê đáng kể trong phân tích hành vi giao dịch hoặc dự báo tài chính. Bước xử lý này giúp tối ưu hóa bộ dữ liệu, giảm độ phức tạp và tập trung hơn vào các biến có giá trị phân tích thực tiễn.
dup_count <- data[, .N, by = names(data)][N > 1, .N]
print(paste("Số hàng bị trùng lặp hoàn toàn:", dup_count))
## [1] "Số hàng bị trùng lặp hoàn toàn: 0"
Giải thích
Lệnh data[, .N, by = names(data)] nhóm toàn bộ các biến trong bảng, đếm số lần xuất hiện của từng hàng bằng .N.
Sau đó, điều kiện [N > 1, .N] lọc ra các hàng có số lần xuất hiện lớn hơn 1, tức là những hàng bị lặp lại, và đếm tổng số lượng trùng lặp này.
Nhận xét
Kết quả cho thấy không có bản ghi nào bị trùng lặp hoàn toàn trong bộ dữ liệu. Điều này chứng tỏ dữ liệu được thu thập và lưu trữ có tính toàn vẹn cao, không xảy ra lỗi nhập liệu hoặc sao chép thông tin
data[, merchant_city := toupper(merchant_city)]
kable(head(data[, .N, by = merchant_city][order(-N)], 5),
caption = "Top 5 thành phố có khối lượng giao dịch cao nhất.",
booktabs = TRUE, col.names = c("Thành phố", "Số lượng"))%>%
kable_styling(font_size = 11, position = "center",latex_options = "hold_position")
| Thành phố | Số lượng |
|---|---|
| ONLINE | 1563700 |
| HOUSTON | 146917 |
| MIAMI | 87388 |
| BROOKLYN | 84020 |
| LOS ANGELES | 82004 |
Giải thích
Lệnh data[, merchant_city := toupper(merchant_city)] được sử dụng để chuẩn hóa tên thành phố bằng cách chuyển toàn bộ ký tự sang chữ in hoa, giúp tránh sai lệch khi phân tích do khác biệt định dạng.
Tiếp đó, data[, .N, by = merchant_city][order(-N)] tính tần suất xuất hiện của từng thành phố, sau đó sắp xếp giảm dần theo số lượng giao dịch.
Nhận xét
Kết quả cho thấy “ONLINE” là danh mục có số lượng giao dịch cao nhất, đạt 1.563.700 giao dịch, phản ánh xu hướng thanh toán trực tuyến đang chiếm ưu thế mạnh mẽ. Về mặt thống kê – kinh tế, kết quả này cho thấy hành vi tiêu dùng hiện đại đang chuyển dịch mạnh về thanh toán số, đồng thời phản ánh sức mua lớn tại các trung tâm kinh tế trọng điểm.
if (class(data$amount) != "numeric") {
data[, amount := as.numeric(gsub("\\$", "", amount))]
}
print(paste("Kiểu dữ liệu mới của amount:", class(data$amount)))
## [1] "Kiểu dữ liệu mới của amount: numeric"
Câu lệnh if (class(data$amount) != “numeric”) kiểm tra xem cột này có phải kiểu số học hay chưa.
Nếu chưa, phần data[, amount := as.numeric(gsub(“\$”, ““, amount))] sẽ loại bỏ ký hiệu $ khỏi dữ liệu rồi chuyển toàn bộ sang dạng số (numeric) để phục vụ cho việc tính toán, thống kê và vẽ biểu đồ.
if (!inherits(data$date, "POSIXct")) {
data[, date := as.POSIXct(date)]
}
str(data$date)
## POSIXct[1:13305915], format: "2010-01-01 00:01:00" "2010-01-01 00:02:00" ...
Điều kiện if (!inherits(data$date, “POSIXct”)) kiểm tra xem cột date có đang ở kiểu ngày giờ chuẩn POSIXct hay chưa.
Nếu chưa, lệnh data[, date := as.POSIXct(date)] sẽ chuyển toàn bộ dữ liệu trong cột này sang định dạng POSIXct – định dạng đặc trưng trong R để xử lý dữ liệu ngày và giờ. Sau cùng, hàm str(data$date) được gọi để xác nhận kiểu dữ liệu đã được chuyển đổi thành công.
data[, payment_type := gsub(" Transaction", "", use_chip)]
kable(head(data[, .(use_chip, payment_type)]),
caption = "Mã hóa biến phương thức thanh toán",
booktabs = TRUE, col.names = c("use_chip","payment_type")) %>%
kable_styling(font_size = 11, position = "center",latex_options = "hold_position")
| use_chip | payment_type |
|---|---|
| Swipe Transaction | Swipe |
| Swipe Transaction | Swipe |
| Swipe Transaction | Swipe |
| Swipe Transaction | Swipe |
| Swipe Transaction | Swipe |
| Swipe Transaction | Swipe |
Giải thích
Lệnh data[, payment_type := gsub(” Transaction”, ““, use_chip)] sẽ tạo biến mới payment_type bằng cách loại bỏ chuỗi “ Transaction” trong cột use_chip, giúp tên phương thức trở nên ngắn gọn và dễ đọc hơn.
Nhận xét
Kết quả cho thấy biến use_chip ban đầu như “Swipe Transaction” hay “Chip Transaction” đã được rút gọn thành “Swipe” và “Chip”, giúp bảng dữ liệu trở nên sạch và dễ xử lý hơn.
data[, hour := hour(date)]
kable(head(data[, .(date, hour)]),
caption = "Mã hóa biến giờ",
booktabs = TRUE, col.names = c("Ngày giờ gốc", "Giờ")) %>%
kable_styling(font_size = 11, position = "center",latex_options = "hold_position")
| Ngày giờ gốc | Giờ |
|---|---|
| 2010-01-01 00:01:00 | 0 |
| 2010-01-01 00:02:00 | 0 |
| 2010-01-01 00:02:00 | 0 |
| 2010-01-01 00:05:00 | 0 |
| 2010-01-01 00:06:00 | 0 |
| 2010-01-01 00:07:00 | 0 |
Giải thích
Lệnh data[, hour := hour(date)] sử dụng hàm hour() trong gói lubridate để tạo biến mới hour, chứa giá trị giờ (từ 0 đến 23) tương ứng với mỗi giao dịch.
Nhận xét
Kết quả cho thấy các giao dịch được thực hiện vào thời điểm đầu ngày (giờ 0) được trích xuất chính xác. Việc mã hóa này rất hữu ích cho phân tích hành vi giao dịch theo khung giờ, chẳng hạn xác định khung thời gian cao điểm trong ngày hoặc thói quen tiêu dùng theo thời gian.
data[, wday_num := as.POSIXlt(date)$wday]
vietnamese_days <- c("Chủ nhật", "Thứ hai", "Thứ ba", "Thứ tư", "Thứ năm",
"Thứ sáu", "Thứ bảy")
data[, weekday := vietnamese_days[wday_num + 1]]
display_levels <- c("Thứ hai", "Thứ ba", "Thứ tư", "Thứ năm", "Thứ sáu",
"Thứ bảy", "Chủ nhật")
data[, weekday := factor(weekday, levels = display_levels)]
data[, wday_num := NULL]
kable(head(data[, .(date, weekday)]),
caption = "Mã hóa biến thứ trong tuần",
booktabs = TRUE, col.names = c("Ngày giờ gốc", "Thứ"))%>%
kable_styling(font_size = 11, position = "center",latex_options = "hold_position")
| Ngày giờ gốc | Thứ |
|---|---|
| 2010-01-01 00:01:00 | Thứ sáu |
| 2010-01-01 00:02:00 | Thứ sáu |
| 2010-01-01 00:02:00 | Thứ sáu |
| 2010-01-01 00:05:00 | Thứ sáu |
| 2010-01-01 00:06:00 | Thứ sáu |
| 2010-01-01 00:07:00 | Thứ sáu |
Giải thích
Hàm as.POSIXlt(date)$wday trích xuất giá trị số tương ứng với các ngày trong tuần (0 = Chủ nhật, 6 = Thứ bảy) và lưu vào biến tạm wday_num. Tiếp đó, vector vietnamese_days được tạo nhằm chuyển đổi các giá trị số thành tên thứ bằng tiếng Việt.
Lệnh data[, weekday := vietnamese_days[wday_num + 1]] gán tên thứ phù hợp cho mỗi dòng dữ liệu. Sau đó, weekday được định dạng thành biến nhân tố (factor) với thứ tự hiển thị logic trong tuần (display_levels), giúp dễ dàng trong việc sắp xếp hoặc trực quan hóa. Cuối cùng, biến tạm wday_num được xóa để tránh dư thừa dữ liệu.
Nhận xét
Kết quả cho thấy các bản ghi ngày 01/01/2010 đều được xác định là “Thứ sáu”, chứng tỏ quá trình mã hóa ngày trong tuần được thực hiện chính xác. Việc chuyển đổi này giúp dữ liệu trở nên dễ phân tổ, đặc biệt khi phân tích xu hướng giao dịch theo ngày trong tuần. Nhờ đó, tác giả có thể phát hiện xu hướng giao dịch chẳng hạn giao dịch nhiều hơn vào cuối tuần hoặc giảm mạnh vào đầu tuần,
data[, year := year(date)]
data[, month := month(date)]
kable(head(data[, .(date, year, month)]),
caption = "Mã hóa biến tháng và năm",
booktabs = TRUE, col.names = c("Ngày giờ gốc", "Năm", "Tháng"))%>%
kable_styling(font_size = 11, position = "center",latex_options = "hold_position")
| Ngày giờ gốc | Năm | Tháng |
|---|---|---|
| 2010-01-01 00:01:00 | 2010 | 1 |
| 2010-01-01 00:02:00 | 2010 | 1 |
| 2010-01-01 00:02:00 | 2010 | 1 |
| 2010-01-01 00:05:00 | 2010 | 1 |
| 2010-01-01 00:06:00 | 2010 | 1 |
| 2010-01-01 00:07:00 | 2010 | 1 |
Giải thích
Đoạn mã trên hiển thị một phần dữ liệu sau khi mã hóa hai biến thời gian mới là “năm” và “tháng” từ biến gốc date. Hàm year và month được dùng để tách riêng phần năm và tháng từ biến ngày giờ dạng POSIXct.
Nhận xét
Kết quả cho thấy các giá trị năm và tháng được trích xuất chính xác từ dữ liệu thời gian gốc, với các giao dịch đầu tiên đều rơi vào tháng 1 năm 2010.Việc tạo thêm hai biến này rất hữu ích cho các bước phân tổ giúp phát hiện những xu hướng dài hạn.
data[, khung_gio := cut(hour,
breaks = c(-1, 5, 11, 17, 24),
labels = c("Đêm", "Sáng", "Chiều", "Tối"))]
kable(head(data[, .(hour, khung_gio)]),
caption = "Mã hóa biến khung giờ",
booktabs = TRUE, col.names = c("Giờ", "Khung giờ")) %>%
kable_styling(font_size = 11, position = "center",latex_options = "hold_position")
| Giờ | Khung giờ |
|---|---|
| 0 | Đêm |
| 0 | Đêm |
| 0 | Đêm |
| 0 | Đêm |
| 0 | Đêm |
| 0 | Đêm |
Giải thích
Hàm cut() chia biến hour (giờ trong ngày) thành 4 khoản:“Đêm”, “Sáng”, “Chiều”, “Tối. Việc đặt breaks = c(-1, 5, 11, 17, 24) giúp bao phủ toàn bộ dải giá trị từ 0 đến 23, tránh lỗi bỏ sót giá trị biên.
Nhận xét
Kết quả minh họa cho thấy các giao dịch có “hour = 0” được xếp chính xác vào khung giờ “Đêm”, thể hiện logic mã hóa hợp lý. Biến này hỗ trợ các bước phân tích hành vi giao dịch theo thời gian, chẳng hạn như: Phát hiện khung giờ có rủi ro giao dịch cao, và hỗ trợ trực quan hóa dữ liệu bằng biểu đồ tần suất hoặc biểu đồ mật độ theo khung giờ . ## 2.12. Mã hóa biến mới: cuối tuần
data[, cuoi_tuan := ifelse(weekday %in% c("Thứ bảy", "Chủ nhật"), TRUE, FALSE)]
kable(tail(data[, .(weekday, cuoi_tuan)]),
caption = "Mã hóa biến cuối tuần'.",
booktabs = TRUE, col.names = c("Thứ", "Cuối tuần")) %>%
kable_styling(font_size = 11, position = "center",latex_options = "hold_position")
| Thứ | Cuối tuần |
|---|---|
| Thứ năm | FALSE |
| Thứ năm | FALSE |
| Thứ năm | FALSE |
| Thứ năm | FALSE |
| Thứ năm | FALSE |
| Thứ năm | FALSE |
Giải thích
Đoạn mã trên tạo biến logic mới tên “cuoi_tuan” nhằm xác định liệu một giao dịch có diễn ra vào cuối tuần hay không.Đầu tiên tác giả sử dụng hàm ifelse() để gán giá trị: TRUE nếu biến weekday thuộc “Thứ bảy” hoặc “Chủ nhật”, FALSE cho các ngày còn lại trong tuần.
Nhận xét
Kết quả mẫu cho thấy các giao dịch rơi vào “Thứ năm” được gán giá trị FALSE, phản ánh chính xác rằng đây không phải cuối tuần. Biến này sẽ hỗ trợ cho việc phân tích hành vi giao dịch theo thời gian chẳng hạn như: so sánh khối lượng và giá trị giao dịch giữa ngày thường và cuối tuần, nhận diện xu hướng rủi ro giao dịch tăng giảm theo từng giai đoạn trong tuần.
data[, co_loi := ifelse(errors == "" | is.na(errors), FALSE, TRUE)]
kable(data[, .N, by = co_loi],
caption = "Mã hóa biến có lỗi",
booktabs = TRUE, col.names = c("Có lỗi", "Số lượng"))%>%
kable_styling(font_size = 11, position = "center",latex_options = "hold_position")
| Có lỗi | Số lượng |
|---|---|
| FALSE | 13094522 |
| TRUE | 211393 |
Giải thích
Đoạn mã trên thực hiện mã hóa biến logic mới co_loi để phản ánh tình trạng lỗi của từng giao dịch trong tập dữ liệu. Sử dụng hàm ifelse() nhằm gán: FALSE cho các dòng có giá trị errors trống (““) hoặc thiếu (NA), tức là không phát sinh lỗi, TRUE nếu biến errors có nội dung khác rỗng, nghĩa là giao dịch có lỗi được ghi nhận.
Nhận xét
Kết quả cho thấy trong tổng số hơn 13,3 triệu giao dịch có: 13.094.522 giao dịch không gặp lỗi, 211.393 giao dịch có lỗi phát sinh. Tỷ lệ lỗi thấp chứng tỏ dữ liệu tương đối sạch và hệ thống ghi nhận ổn định, tuy nhiên nhóm giao dịch có lỗi vẫn cần được xem xét riêng biệt để phân tích nguyên nhân.
data[, nhom_gia_tri := cut(amount, breaks = c(-Inf, 0, 50, 250, 1000, Inf),
labels = c("Hoàn tiền", "Rất nhỏ (0-50)",
"Nhỏ (50-250)", "Trung bình (250-1k)",
"Lớn (>1k)"), right = FALSE)]
kable(head(data[, .(amount, nhom_gia_tri)]), caption = "Mã hóa biến nhóm giá trị",
booktabs = TRUE, col.names = c("Giá trị", "Nhóm")) %>%
kable_styling(font_size = 11, position = "center",latex_options = "hold_position")
| Giá trị | Nhóm |
|---|---|
| -77.00 | Hoàn tiền |
| 14.57 | Rất nhỏ (0-50) |
| 80.00 | Nhỏ (50-250) |
| 200.00 | Nhỏ (50-250) |
| 46.41 | Rất nhỏ (0-50) |
| 4.81 | Rất nhỏ (0-50) |
Giải thích
Đoạn mã trên thực hiện phân loại biến định lượng “amount” thành các nhóm định tính “nhom_gia_tri” nhằm thuận tiện cho việc mô tả và phân tích thống kê. Sử dụng hàm cut() để chia amount thành 5 khoảng giá trị theo tiêu chí:
Đặt tham số right = FALSE để cận trái bao gồm trong khoảng, giúp xác định ranh giới nhóm rõ ràng.
Nhận xét
Việc mã hóa này giúp biến giá trị giao dịch từ dạng liên tục sang dạng phân loại, hỗ trợ cho việc so sánh phân bố giao dịch giữa các mức giá trị khác nhau, xác định các nhóm giao dịch có tần suất cao (ví dụ: nhóm nhỏ hoặc trung bình).
data_duong <- data[amount > 0]
print(paste("Số lượng giao dịch thành công:",
scales::comma(nrow(data_duong), accuracy = 1,
big.mark = ".", decimal.mark = ",")))
## [1] "Số lượng giao dịch thành công: 12.635.227"
Giải thích
Đoạn mã trên lọc ra các giao dịch có giá trị amount lớn hơn 0, tức là các giao dịch thành công và hợp lệ về mặt tài chính. Sử dụng cú pháp của data.table: data[amount > 0] để tạo tập con data_duong. Hàm nrow() đếm tổng số giao dịch thỏa điều kiện. Hàm scales::comma() định dạng số lượng có dấu phân tách hàng nghìn, giúp kết quả dễ đọc hơn.
Nhận xét
Kết quả cho thấy có 12.635.227 giao dịch thành công, chiếm phần lớn trong tổng số 13.305.915 bản ghi của tập dữ liệu ban đầu. Điều này chứng tỏ tỷ lệ giao dịch hoàn tiền (giá trị âm) là rất nhỏ, cho thấy dữ liệu thích hợp để tiếp tục khai thác cho các bước phân tích thống kê và mô hình hóa.
data_am <- data[amount < 0]
data_abs <- copy(data_am)
data_abs[, amount := abs(amount)]
print(paste("Số lượng giao dịch hoàn tiền:",
scales::comma(nrow(data_abs),accuracy=1, big.mark = ".",
decimal.mark = ",")))
## [1] "Số lượng giao dịch hoàn tiền: 660.049"
Giải thích
Đoạn mã trên lọc ra các giao dịch có giá trị amount âm, tức là các giao dịch hoàn tiền hoặc hủy giao dịch.Lệnh data[amount < 0] tạo tập con data_am chứa các bản ghi có giá trị âm. Lệnh copy() được dùng để tránh thay đổi dữ liệu gốc khi xử lý. Trong data_abs, giá trị amount được chuyển sang trị tuyệt đối (abs(amount)) để tiện cho việc so sánh hoặc trực quan hóa sau này. Lệnh nrow() đếm tổng số giao dịch, scales::comma() định dạng hiển thị dẩu phẩy phân cách các dữ liệu số giúp dễ đọc hơn
Nhận xét
Có 660.049 giao dịch hoàn tiền, chiếm khoảng 5% tổng số giao dịch. Tỷ lệ này ở mức chấp nhận được, phản ánh rằng hệ thống thanh toán hoạt động khá ổn định, chỉ có một phần nhỏ các giao dịch bị hoàn lại — có thể do lỗi kỹ thuật, hủy đơn hàng hoặc yêu cầu hoàn trả từ khách hàng.
Việc tách nhóm này riêng giúp phân tích hành vi rủi ro hoặc hiệu suất xử lý thanh toán trong các phần sau được chính xác hơn.
pt_method <- data_duong[, .N, by = payment_type][order(-N)]
if (knitr::is_latex_output()) {
kable(pt_method,
caption = "Số lượng giao dịch thành công theo phương thức thanh toán",
format = "latex", booktabs = TRUE, col.names = c("Phương thức","Số lượng")) %>%
kable_styling(font_size = 13, position = "center",latex_options = "hold_position")
} else {
kable(pt_method, caption = "Số lượng giao dịch thành công theo phương thức thanh toán", col.names = c("Phương thức", "Số lượng")) %>%
kable_styling(bootstrap_options = "striped", full_width = F)}
| Phương thức | Số lượng |
|---|---|
| Swipe | 6571735 |
| Chip | 4512267 |
| Online | 1551225 |
Giải thích
Đoạn mã trên thực hiện việc thống kê số lượng giao dịch thành công theo từng phương thức thanh toán trong tập dữ liệu data_duong. Hàm .N của data.table được sử dụng để đếm số lượng bản ghi trong từng nhóm được xác định bởi phương thức thanh toán by = payment_type, sau đó kết quả được sắp xếp giảm dần theo tần suất xuất hiện bằng order(-N).
Nhận xét
Trong các giao dịch thành công, Swipe (quẹt thẻ) chiếm tỷ trọng lớn nhất với 6.571.735 giao dịch, theo sau là Chip (thẻ chip) với 4.512.267 giao dịch, và Online chiếm 1.551.225 giao dịch. Điều này cho thấy thanh toán trực tiếp qua thẻ vật lý vẫn chiếm ưu thế, phản ánh thói quen tiêu dùng truyền thống. Tuy nhiên, tỷ lệ giao dịch Online cũng đáng chú ý, cho thấy xu hướng chuyển dịch dần sang thanh toán điện tử.
pt_hour <- data_duong[, .N, by = khung_gio][order(-N)]
if (knitr::is_latex_output()) { kable(pt_hour,
caption = "Số lượng giao dịch thành công theo khung giờ",
format = "latex", booktabs = TRUE, col.names = c("Khung giờ", "Số lượng")) %>%
kable_styling(font_size = 13, position = "center",latex_options = "hold_position")
} else {kable(pt_hour, caption = "Số lượng giao dịch thành công theo khung giờ",
col.names = c("Khung giờ", "Số lượng")) %>%
kable_styling(bootstrap_options = "striped", full_width = F)}
| Khung giờ | Số lượng |
|---|---|
| Sáng | 4994972 |
| Chiều | 4647658 |
| Tối | 2248379 |
| Đêm | 744218 |
Giải thích
Đoạn mã trên thực hiện việc tổng hợp số lượng giao dịch thành công theo từng khung giờ trong ngày, nhằm nhận diện thời điểm có hoạt động giao dịch sôi động nhất.Tập dữ liệu data_duong được nhóm theo biến khung giờ – biến này trước đó đã được mã hóa từ biến hour với bốn khoảng thời gian: Đêm, Sáng, Chiều, và Tối. Hàm .N trong data.table được dùng để đếm số lượng giao dịch trong mỗi nhóm, sau đó sắp xếp giảm dần bằng order(-N) để xác định khung giờ có tần suất giao dịch cao nhất.
Nhận xét
Kết quả cho thấy các giao dịch thành công tập trung chủ yếu vào buổi sáng (4.994.972 giao dịch) và buổi chiều (4.647.658 giao dịch), chiếm phần lớn tổng số giao dịch. Trong khi đó, buổi tối và ban đêm có số lượng thấp hơn đáng kể. Điều này phản ánh hoạt động kinh tế và tiêu dùng diễn ra mạnh nhất trong giờ hành chính, phù hợp với thời gian mở cửa của cửa hàng, siêu thị, và trung tâm dịch vụ. Tần suất giảm dần vào buổi tối và đêm cho thấy mức độ giao dịch chịu ảnh hưởng bởi yếu tố thời gian sinh hoạt và hoạt động kinh doanh.
pt_weekday <- data_duong[, .N, by = weekday][order(factor(weekday,
levels = display_levels))]
if (knitr::is_latex_output()) {
kable(pt_weekday, caption = "Số lượng giao dịch thành công theo thứ trong tuần",
format = "latex", booktabs = TRUE, col.names = c("Thứ","Số lượng")) %>%
kable_styling(font_size = 13, position = "center",latex_options = "hold_position")
} else {
kable(pt_weekday, caption = "Số lượng giao dịch thành công theo thứ trong tuần",
col.names = c("Thứ", "Số lượng")) %>%
kable_styling(bootstrap_options = "striped", full_width = F)}
| Thứ | Số lượng |
|---|---|
| Thứ hai | 1799729 |
| Thứ ba | 1802706 |
| Thứ tư | 1799311 |
| Thứ năm | 1821962 |
| Thứ sáu | 1801176 |
| Thứ bảy | 1805616 |
| Chủ nhật | 1804727 |
Giải thích
Đoạn mã này tiến hành thống kê số lượng giao dịch thành công theo thứ trong tuần nhằm xác định xu hướng hoạt động giao dịch theo thời gian.
Dữ liệu được nhóm theo biến weekday, đại diện cho ngày trong tuần từ Thứ hai đến Chủ nhật. Để đảm bảo thứ tự hiển thị hợp lý, hàm factor() được sử dụng kèm tham số levels = display_levels, giúp sắp xếp các ngày theo đúng thứ tự tuần thay vì theo bảng chữ cái.
Nhận xét
Kết quả cho thấy số lượng giao dịch trong tuần khá đồng đều, dao động quanh mức 1,79 – 1,82 triệu giao dịch mỗi ngày.
Tuy nhiên, có thể nhận thấy thứ năm là ngày có số lượng giao dịch cao nhất (1.821.962 giao dịch), trong khi thứ tư là ngày thấp nhất (1.799.311 giao dịch). Mức chênh lệch giữa các ngày không đáng kể, cho thấy hoạt động giao dịch được duy trì ổn định trong suốt tuần. Ngoài ra, hai ngày cuối tuần (thứ bảy và chủ nhật) vẫn ghi nhận khối lượng giao dịch tương đương với ngày thường.
pt_year <- data_duong[, .N, by = .(year)][order(year)]
if (knitr::is_latex_output()) {
kable(pt_year, caption = "Số lượng giao dịch thành công theo năm",
format = "latex", booktabs = TRUE, col.names = c("Năm", "Số lượng")) %>%
kable_styling(font_size = 13, position = "center",latex_options = "hold_position")
} else {kable(pt_year, caption = "Số lượng giao dịch thành công theo năm",
col.names = c("Năm", "Số lượng")) %>%
kable_styling(bootstrap_options = "striped", full_width = F)}
| Năm | Số lượng |
|---|---|
| 2010 | 1175783 |
| 2011 | 1224214 |
| 2012 | 1254410 |
| 2013 | 1284433 |
| 2014 | 1297336 |
| 2015 | 1318939 |
| 2016 | 1322628 |
| 2017 | 1329128 |
| 2018 | 1326144 |
| 2019 | 1102212 |
Giải thích
Đoạn mã này thực hiện thống kê số lượng giao dịch thành công theo từng năm để nhận diện xu hướng biến động trong giai đoạn quan sát. Dữ liệu được nhóm theo biến year, sau đó được sắp xếp tăng dần bằng order(year) để thể hiện tiến trình thời gian.
Nhận xét
Kết quả cho thấy số lượng giao dịch thành công tăng đều đặn qua các năm từ 2010 đến 2017, từ mức 1.175.783 giao dịch năm 2010, con số này tăng liên tục và đạt đỉnh 1.329.128 giao dịch vào năm 2017. Tuy nhiên, giai đoạn 2018–2019 ghi nhận dấu hiệu chững lại và giảm nhẹ, đặc biệt năm 2019 chỉ còn 1.102.212 giao dịch, cho thấy sự bão hòa của thị trường hoặc ảnh hưởng của yếu tố kinh tế vĩ mô.
pt_value <- data_duong[, .N, by = nhom_gia_tri][order(-N)]
if (knitr::is_latex_output()) {kable(pt_value,
caption = "Số lượng giao dịch thành công theo nhóm giá trị giao dịch",
format = "latex", booktabs = TRUE, col.names = c("Nhóm giá trị",
"Số lượng")) %>%
kable_styling(font_size = 13, position = "center",latex_options = "hold_position")
} else {
kable(pt_value, caption = "Số lượng giao dịch thành công theo nhóm giá trị giao dịch",
col.names = c("Nhóm giá trị", "Số lượng")) %>%
kable_styling(bootstrap_options = "striped", full_width = F)}
| Nhóm giá trị | Số lượng |
|---|---|
| Rất nhỏ (0-50) | 8176139 |
| Nhỏ (50-250) | 4254647 |
| Trung bình (250-1k) | 195256 |
| Lớn (>1k) | 9185 |
Giải thích
Đoạn mã trên thực hiện phân nhóm và thống kê số lượng giao dịch thành công theo giá trị giao dịch, nhằm xác định cấu trúc phân bố của biến amount trong các khoảng giá trị đã được mã hóa trước đó. Cụ thể, dữ liệu được nhóm theo biến nhóm giá trị — bao gồm các nhóm như “Rất nhỏ (0–50)”, “Nhỏ (50–250)”, “Trung bình (250–1k)” và “Lớn (>1k)” — rồi sắp xếp giảm dần theo tần suất (order(-N)).
Nhận xét
Kết quả thống kê cho thấy các giao dịch:
Điều này phản ánh rằng đa phần người dùng thực hiện các giao dịch thanh toán nhỏ lẻ, phù hợp với hành vi tiêu dùng hàng ngày hoặc thanh toán tiện ích, trong khi các giao dịch giá trị lớn ít phổ biến hơn, có thể do liên quan đến hoạt động kinh doanh hoặc thanh toán đặc thù.
pt_city <- data_duong[, .N, by = merchant_city][order(-N)][1:10]
if (knitr::is_latex_output()) {
kable(pt_city,
caption = "Top 10 thành phố có số lượng giao dịch thành công cao nhất",
format = "latex", booktabs = TRUE, col.names = c("Thành phố","Số lượng")) %>%
kable_styling(font_size = 13, position = "center",latex_options = "hold_position")
} else {
kable(pt_city, caption = "Top 10 thành phố có số lượng giao dịch thành công cao nhất",
col.names = c("Thành phố", "Số lượng")) %>%
kable_styling(bootstrap_options = "striped", full_width = F)}
| Thành phố | Số lượng |
|---|---|
| ONLINE | 1554120 |
| HOUSTON | 135977 |
| MIAMI | 83797 |
| BROOKLYN | 79450 |
| LOS ANGELES | 78426 |
| CHICAGO | 69935 |
| DALLAS | 66223 |
| LOUISVILLE | 64000 |
| SAN ANTONIO | 56484 |
| PHILADELPHIA | 55478 |
Giải thích
Đoạn mã này tiến hành thống kê số lượng giao dịch thành công theo thành phố bằng cách nhóm dữ liệu theo biến merchant_city, sau đó sắp xếp giảm dần theo tần suất giao dịch (order(-N)) và chọn ra 10 thành phố có số lượng giao dịch cao nhất [1:10].
Nhận xét
Kết quả cho thấy ONLINE là khu vực có số lượng giao dịch thành công cao nhất, đạt 1.554.120 giao dịch. Theo sau là Houston với 135.977 giao dịch, Miami (83.797 giao dịch) và Brooklyn (79.450 giao dịch). Nhìn chung, các thành phố lớn như Los Angeles, Chicago hay Dallas đều ghi nhận khối lượng giao dịch cao, phản ánh mức độ hoạt động thanh toán tập trung chủ yếu ở các đô thị lớn và khu vực có nhu cầu tiêu dùng cao.
pt_state <- data_duong[, .N, by = merchant_state][order(-N)][1:10]
if (knitr::is_latex_output()) {
kable(pt_state,caption = "Top 10 bang có số lượng giao dịch thành công cao nhất",
format = "latex", booktabs = TRUE, col.names = c("Bang","Số lượng")) %>%
kable_styling(font_size = 13, position = "center",latex_options = "hold_position")
} else {
kable(pt_state, caption = "Top 10 bang có số lượng giao dịch thành công cao nhất",
col.names = c("Bang", "Số lượng")) %>%
kable_styling(bootstrap_options = "striped", full_width = F)}
| Bang | Số lượng |
|---|---|
| 1554120 | |
| CA | 1342445 |
| TX | 952843 |
| NY | 819464 |
| FL | 661908 |
| OH | 450961 |
| IL | 447930 |
| NC | 405545 |
| PA | 393916 |
| MI | 374628 |
Giải thích
Đoạn mã tiến hành phân tổ dữ liệu theo biến merchant_state nhằm xác định mức độ phân bố của các giao dịch thành công tại từng bang. Lệnh data_duong[, .N, by = merchant_state] được sử dụng để đếm số lượng giao dịch (.N) trong mỗi nhóm bang, sau đó kết quả được sắp xếp giảm dần bằng order(-N) để tìm ra các khu vực có hoạt động giao dịch nổi bật nhất. Tiếp đó, phần [1:10] giúp lấy ra 10 bang có số lượng giao dịch cao nhất
Nhận xét
Kết quả cho thấy giao dịch trực tuyến NA đứng đầu với 1.554.120 giao dịch. Trong số các bang có thông tin cụ thể, CA dẫn đầu với 1.342.445 giao dịch, tiếp theo là TX (952.843), NY (819.464) và FL (661.908) — đều là những khu vực có quy mô dân số lớn và hoạt động kinh tế sôi động
pt_weekend <- data_duong[, .N, by = cuoi_tuan]
if (knitr::is_latex_output()) {
kable(pt_weekend,caption = "Số lượng giao dịch thành công theo loại ngày",
format = "latex", booktabs = TRUE, col.names = c("Cuối tuần","Số lượng")) %>%
kable_styling(font_size = 13, position = "center",latex_options = "hold_position")
} else {kable(pt_weekend, caption = "Số lượng giao dịch thành công theo loại ngày",
col.names = c("Cuối tuần", "Số lượng")) %>%
kable_styling(bootstrap_options = "striped", full_width = F)}
| Cuối tuần | Số lượng |
|---|---|
| FALSE | 9024884 |
| TRUE | 3610343 |
Giải thích
Đoạn mã này thống kê số lượng giao dịch thành công theo loại ngày bằng cách nhóm dữ liệu theo biến cuoi_tuan, phân biệt giữa các giao dịch diễn ra trong tuần (FALSE) và cuối tuần (TRUE). Hàm .N của data.table được dùng để tính tần suất từng nhóm.
Nhận xét
Kết quả cho thấy số lượng giao dịch trong tuần chiếm 9.024.884 giao dịch, áp đảo so với cuối tuần với 3.610.343 giao dịch, phản ánh rằng hoạt động thanh toán diễn ra tập trung chủ yếu vào các ngày làm việc.
pt_year_method <- data_duong[, .N, by = .(year, payment_type)][order(year,
payment_type)]
if (knitr::is_latex_output()) {kable(head(pt_year_method, 10),
caption = "Số lượng giao dịch thành công theo năm và phương thức",
format = "latex", booktabs = TRUE, col.names = c("Năm",
"Phương thức", "Số lượng")) %>%
kable_styling(font_size = 13, position = "center",latex_options = "hold_position")
} else {
kable(head(pt_year_method, 10), caption = "Số lượng giao dịch thành công
theo năm và phương thức",col.names = c("Năm", "Phương thức", "Số lượng")) %>%
kable_styling(bootstrap_options = "striped", full_width = F)}
| Năm | Phương thức | Số lượng |
|---|---|---|
| 2010 | Online | 133781 |
| 2010 | Swipe | 1042002 |
| 2011 | Online | 138610 |
| 2011 | Swipe | 1085604 |
| 2012 | Online | 146435 |
| 2012 | Swipe | 1107975 |
| 2013 | Online | 156142 |
| 2013 | Swipe | 1128291 |
| 2014 | Online | 160512 |
| 2014 | Swipe | 1136824 |
Giải thích
Đoạn mã này tiến hành thống kê số lượng giao dịch thành công theo từng năm và từng phương thức thanh toán bằng cách nhóm dữ liệu theo hai biến year và payment_type. Hàm .N trong data.table được dùng để tính số lượng giao dịch cho mỗi nhóm kết hợp, kết quả được sắp xếp theo năm và phương thức thanh toán để dễ quan sát xu hướng. Lệnh head(pt_year_method, 10) dùng để xem trước 10 giá trị đầu tiên của mẫu.
Nhận xét
Kết quả cho thấy trong giai đoạn 2010–2014, phương thức Swipe chiếm phần lớn giao dịch thành công, ví dụ năm 2010 có 1.042.002 giao dịch Swipe so với 133.781 giao dịch Online, phản ánh xu hướng thanh toán truyền thống vẫn chiếm ưu thế. Số liệu cũng cho thấy sự tăng trưởng đều đặn cả hai phương thức qua các năm, trong đó Online tăng từ 133.781 (2010) lên 160.512 (2014), chứng tỏ xu hướng thanh toán điện tử đang dần phát triển, nhưng Swipe vẫn là phương thức phổ biến nhất trong giai đoạn này.
pt_year_hour <- data_duong[, .N, by = .(year, khung_gio)][order(year)]
if (knitr::is_latex_output()) {kable(head(pt_year_hour, 10),
caption = "Số lượng giao dịch thành công theo năm và khung giờ",
format = "latex", booktabs = TRUE, col.names = c("Năm",
"Khung giờ", "Số lượng")) %>%
kable_styling(font_size = 13, position = "center",latex_options = "hold_position")
} else {kable(head(pt_year_hour, 10), caption = "Số lượng giao dịch
thành công theo năm và khung giờ",
col.names = c("Năm", "Khung giờ", "Số lượng")) %>%
kable_styling(bootstrap_options = "striped", full_width = F)}
| Năm | Khung giờ | Số lượng |
|---|---|---|
| 2010 | Đêm | 69452 |
| 2010 | Sáng | 461696 |
| 2010 | Chiều | 433547 |
| 2010 | Tối | 211088 |
| 2011 | Đêm | 71939 |
| 2011 | Sáng | 480540 |
| 2011 | Chiều | 452658 |
| 2011 | Tối | 219077 |
| 2012 | Đêm | 73737 |
| 2012 | Sáng | 493964 |
Giải thích
Đoạn mã này thống kê số lượng giao dịch thành công theo năm và khung giờ bằng cách nhóm dữ liệu theo hai biến year và khung_gio, sử dụng .N để tính số lượng giao dịch trong mỗi nhóm. Hàm head(pt_year_hour, 10) dùng để xem trước 10 giá trị đầu tiên của mẫu.
Nhận xét
Kết quả cho thấy các giao dịch thành công phân bổ không đồng đều trong ngày, vớ sáng chiếm số lượng cao nhất (ví dụ năm 2010: 461.696 giao dịch), tiếp theo là chiều (433.547), tối (211.088) và đêm ít nhất (69.452). Xu hướng này lặp lại qua các năm, phản ánh nhịp sinh hoạt và hoạt động kinh tế của khách hàng, đồng thời cho thấy các khung giờ cao điểm (Sáng và Chiều) là thời điểm chủ yếu diễn ra các giao dịch thành công.
summary_duong_method <- data_duong[, .(
`Số lượng` = .N,
`Trung bình` = mean(amount, na.rm=TRUE),
`Độ lệch chuẩn` = sd(amount, na.rm=TRUE),
`Phương sai` = var(amount, na.rm=TRUE),
Min = min(amount, na.rm=TRUE),
Q1 = quantile(amount, 0.25, na.rm=TRUE),
`Trung vị` = median(amount, na.rm=TRUE),
Q3 = quantile(amount, 0.75, na.rm=TRUE),
Max = max(amount, na.rm=TRUE),
`Độ xiên` = skewness(amount, na.rm=TRUE),
`Độ nhọn` = kurtosis(amount, na.rm=TRUE)
), by=.(`Phương thức` = payment_type)]
cols_to_round <- c("Trung bình","Độ lệch chuẩn","Phương sai","Q1","Trung vị","Q3",
"Độ xiên","Độ nhọn")
summary_duong_method[, (cols_to_round) := round(.SD, 2), .SDcols = cols_to_round]
if (knitr::is_latex_output()) {
kable(summary_duong_method, caption="Thống kê mô tả số lượng
giao dịch theo phương thức thanh toán", format="latex", booktabs=TRUE) %>%
kable_styling(latex_options=c("scale_down","hold_position"),
font_size=9, position="center")
} else {
kable(summary_duong_method, caption="Thống kê mô tả theo phương thức thanh toán") %>%
kable_styling(bootstrap_options=c("striped","hover"), full_width=T)}
| Phương thức | Số lượng | Trung bình | Độ lệch chuẩn | Phương sai | Min | Q1 | Trung vị | Q3 | Max | Độ xiên | Độ nhọn |
|---|---|---|---|---|---|---|---|---|---|---|---|
| Swipe | 6571735 | 49.72 | 72.21 | 5214.78 | 0.01 | 9.90 | 30.88 | 67.99 | 6820.20 | 7.52 | 138.39 |
| Online | 1551225 | 58.78 | 89.20 | 7956.66 | 0.01 | 22.08 | 35.30 | 57.38 | 6613.44 | 9.82 | 249.40 |
| Chip | 4512267 | 49.07 | 70.68 | 4995.16 | 0.01 | 9.77 | 30.80 | 67.27 | 5591.73 | 7.55 | 131.64 |
Nhận xét
Đoạn mã thực hiện thống kê mô tả cho các giao dịch thành công theo từng phương thức thanh toán. Kết quả cho thấy Swipe có số lượng giao dịch nhiều nhất (6.571.735) với giá trị trung bình 49,72, Online có giá trị trung bình cao hơn (58,78) nhưng số lượng ít hơn (1.551.225).
Độ xiên cao (Online: 9,82) cho thấy sự xuất hiện ít các giao dịch lớn, kéo dài đuôi phải của phân phối, trong khi độ nhọn cao (Online: 249,40) phản ánh phân phối tập trung nhiều giao dịch nhỏ. Swipe và Chip có phân phối tương tự, lệch phải nhưng ít hơn. Điều này nhấn mạnh rằng phần lớn giao dịch thành công có giá trị nhỏ, trong khi các giao dịch lớn hiếm nhưng ảnh hưởng mạnh đến độ biến thiên, phản ánh hành vi chi tiêu thực tế của người dùng.
summary_duong_hour <- data_duong[, .(
`Số lượng` = .N,
`Trung bình` = mean(amount, na.rm=TRUE),
`Độ lệch chuẩn` = sd(amount, na.rm=TRUE),
`Phương sai` = var(amount, na.rm=TRUE),
Min = min(amount, na.rm=TRUE),
Q1 = quantile(amount, 0.25, na.rm=TRUE),
`Trung vị` = median(amount, na.rm=TRUE),
Q3 = quantile(amount, 0.75, na.rm=TRUE),
Max = max(amount, na.rm=TRUE),
`Độ xiên` = skewness(amount, na.rm=TRUE),
`Độ nhọn` = kurtosis(amount, na.rm=TRUE)
), by=.(`Khung giờ`=khung_gio)]
summary_duong_hour[, (cols_to_round) := round(.SD,2), .SDcols=cols_to_round]
if (knitr::is_latex_output()) {
kable(summary_duong_hour, caption="Thống kê mô tả số lượng
giao dịch thành công theo khung giờ giao dịch",
format="latex", booktabs=TRUE) %>%kable_styling(latex_options=c("scale_down","hold_position"),
font_size=9, position="center")
} else {
kable(summary_duong_hour, caption="Thống kê mô tả theo khung giờ giao dịch") %>%
kable_styling(bootstrap_options=c("striped","hover"), full_width=T)}
| Khung giờ | Số lượng | Trung bình | Độ lệch chuẩn | Phương sai | Min | Q1 | Trung vị | Q3 | Max | Độ xiên | Độ nhọn |
|---|---|---|---|---|---|---|---|---|---|---|---|
| Đêm | 744218 | 59.31 | 82.69 | 6836.92 | 0.05 | 9.64 | 36.22 | 81.00 | 4729.38 | 6.28 | 113.22 |
| Sáng | 4994972 | 41.96 | 66.85 | 4469.29 | 0.01 | 6.43 | 21.25 | 58.00 | 6820.20 | 8.88 | 217.03 |
| Chiều | 4647658 | 53.98 | 75.57 | 5710.74 | 0.01 | 13.99 | 36.02 | 71.00 | 6613.44 | 8.44 | 182.90 |
| Tối | 2248379 | 59.93 | 80.69 | 6511.44 | 0.01 | 20.68 | 41.61 | 71.29 | 4382.90 | 7.21 | 112.75 |
Nhận xét
Đoạn mã thực hiện thống kê mô tả các giao dịch thành công theo khung giờ. Kết quả cho thấy khung giờ Sáng chiếm số lượng giao dịch cao nhất (4.994.972) nhưng giá trị trung bình thấp nhất (41,96), phản ánh khối lượng giao dịch lớn nhưng giá trị mỗi giao dịch tương đối nhỏ. Khung giờ Tối và Đêm có giá trị trung bình cao hơn (59,93 và 59,31), cho thấy các giao dịch ít nhưng giá trị lớn hơn.
Độ xiên dương ở tất cả các khung giờ (từ 6,28 đến 8,88) chứng tỏ phân phối lệch phải, với nhiều giao dịch nhỏ và một số ít giao dịch lớn, trong khi độ nhọn cao, đặc biệt ở Sáng (217,03), cho thấy phân phối tập trung mạnh quanh các giá trị nhỏ. Điều này phản ánh hành vi chi tiêu theo khung giờ: buổi sáng giao dịch nhiều và nhỏ, buổi tối và đêm giao dịch ít nhưng giá trị lớn hơn.
summary_duong_weekday <- data_duong[, .(
`Số lượng` = .N,
`Trung bình` = mean(amount, na.rm=TRUE),
`Độ lệch chuẩn` = sd(amount, na.rm=TRUE),
`Phương sai` = var(amount, na.rm=TRUE),
Min = min(amount, na.rm=TRUE),
Q1 = quantile(amount, 0.25, na.rm=TRUE),
`Trung vị` = median(amount, na.rm=TRUE),
Q3 = quantile(amount, 0.75, na.rm=TRUE),
Max = max(amount, na.rm=TRUE),
`Độ xiên` = skewness(amount, na.rm=TRUE),
`Độ nhọn` = kurtosis(amount, na.rm=TRUE)
), by=.(Thứ=weekday)]
summary_duong_weekday[, (cols_to_round):=round(.SD,2), .SDcols=cols_to_round]
if (knitr::is_latex_output()) {
kable(summary_duong_weekday, caption="Thống kê mô tả số lượng giao dịch thành
công theo thứ trong tuần",format="latex", booktabs=TRUE) %>%
kable_styling(latex_options=c("scale_down","hold_position"), font_size=9, position="center")
} else {
kable(summary_duong_weekday, caption="Thống kê mô tả theo thứ trong tuần") %>%
kable_styling(bootstrap_options=c("striped","hover"), full_width=T)}
| Thứ | Số lượng | Trung bình | Độ lệch chuẩn | Phương sai | Min | Q1 | Trung vị | Q3 | Max | Độ xiên | Độ nhọn |
|---|---|---|---|---|---|---|---|---|---|---|---|
| Thứ sáu | 1801176 | 50.52 | 73.64 | 5422.96 | 0.01 | 11.08 | 31.93 | 66.29 | 5696.78 | 8.00 | 161.67 |
| Thứ bảy | 1805616 | 50.83 | 74.22 | 5508.76 | 0.01 | 11.11 | 32.00 | 66.69 | 4804.21 | 7.99 | 157.61 |
| Chủ nhật | 1804727 | 50.62 | 74.48 | 5546.71 | 0.01 | 11.12 | 31.95 | 66.33 | 6613.44 | 8.44 | 195.49 |
| Thứ hai | 1799729 | 50.96 | 74.32 | 5523.99 | 0.01 | 11.19 | 32.13 | 66.86 | 4685.23 | 7.61 | 129.70 |
| Thứ ba | 1802706 | 50.52 | 73.90 | 5460.48 | 0.01 | 11.06 | 31.86 | 66.29 | 5913.37 | 7.94 | 156.02 |
| Thứ tư | 1799311 | 50.48 | 73.94 | 5466.45 | 0.01 | 11.09 | 31.96 | 66.32 | 6820.20 | 8.65 | 219.00 |
| Thứ năm | 1821962 | 50.28 | 73.81 | 5447.64 | 0.01 | 10.87 | 31.58 | 66.00 | 5654.50 | 8.10 | 165.12 |
Nhận xét
Đoạn mã trên thực hiện thống kê mô tả số lượng giao dịch thành công theo từng ngày trong tuần. Kết quả cho thấy số lượng giao dịch giữa các ngày trong tuần khá đồng đều, dao động quanh 1,8 triệu giao dịch mỗi ngày, trong đó thứ năm có số lượng cao nhất (1.821.962). Giá trị trung bình giao dịch dao động từ 50,28 đến 50,96, chứng tỏ mức chi tiêu tương đối ổn định xuyên suốt tuần.
Độ xiên dương (7,61 – 8,65) và độ nhọn cao (129,70 – 219,00) ở tất cả các ngày cho thấy phân phối lệch phải mạnh, nghĩa là phần lớn các giao dịch có giá trị nhỏ, trong khi một số ít giao dịch có giá trị rất cao làm kéo dài đuôi phải của phân phối.Tổng thể, kết quả phản ánh rằng hành vi chi tiêu không thay đổi đáng kể giữa các ngày trong tuần, tuy nhiên vẫn duy trì đặc điểm điển hình của dữ liệu giao dịch – tập trung vào các giao dịch nhỏ lẻ và xuất hiện một số giao dịch lớn đột biến.
summary_duong_year <- data_duong[, .(
`Số lượng` = .N,
`Trung bình` = mean(amount, na.rm=TRUE),
`Độ lệch chuẩn` = sd(amount, na.rm=TRUE),
`Phương sai` = var(amount, na.rm=TRUE),
Min = min(amount, na.rm=TRUE),
Q1 = quantile(amount, 0.25, na.rm=TRUE),
`Trung vị` = median(amount, na.rm=TRUE),
Q3 = quantile(amount, 0.75, na.rm=TRUE),
Max = max(amount, na.rm=TRUE),
`Độ xiên` = skewness(amount, na.rm=TRUE),
`Độ nhọn` = kurtosis(amount, na.rm=TRUE)
), by=.(Năm=year)]
summary_duong_year[, (cols_to_round):=round(.SD,2), .SDcols=cols_to_round]
if (knitr::is_latex_output()) {
kable(summary_duong_year, caption="Thống kê mô tả số lượng giao dịch
thành công theo năm",format="latex", booktabs=TRUE) %>%
kable_styling(latex_options=c("scale_down","hold_position"), font_size=9, position="center")
} else {kable(summary_duong_year, caption="Thống kê mô tả theo năm") %>%
kable_styling(bootstrap_options=c("striped","hover"), full_width=T)}
| Năm | Số lượng | Trung bình | Độ lệch chuẩn | Phương sai | Min | Q1 | Trung vị | Q3 | Max | Độ xiên | Độ nhọn |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 2010 | 1175783 | 51.73 | 77.36 | 5984.74 | 0.01 | 11.26 | 32.63 | 68.03 | 6820.20 | 9.56 | 261.48 |
| 2011 | 1224214 | 51.02 | 73.87 | 5456.99 | 0.01 | 11.14 | 32.24 | 67.46 | 4984.24 | 7.71 | 145.67 |
| 2012 | 1254410 | 50.68 | 73.66 | 5426.38 | 0.01 | 11.07 | 31.99 | 66.86 | 5913.37 | 7.72 | 146.57 |
| 2013 | 1284433 | 50.75 | 73.97 | 5471.23 | 0.01 | 11.09 | 32.01 | 66.71 | 5813.78 | 7.61 | 136.12 |
| 2014 | 1297336 | 50.45 | 73.76 | 5439.83 | 0.01 | 11.05 | 31.85 | 66.18 | 5696.78 | 8.22 | 184.04 |
| 2015 | 1318939 | 50.42 | 74.44 | 5541.63 | 0.01 | 11.07 | 31.74 | 65.84 | 4804.21 | 8.34 | 169.90 |
| 2016 | 1322628 | 50.53 | 74.04 | 5481.74 | 0.01 | 11.05 | 31.81 | 66.00 | 4077.18 | 7.60 | 124.42 |
| 2017 | 1329128 | 50.17 | 73.14 | 5349.23 | 0.01 | 10.99 | 31.68 | 65.89 | 5155.36 | 8.12 | 172.15 |
| 2018 | 1326144 | 50.16 | 73.37 | 5382.53 | 0.01 | 11.01 | 31.61 | 65.69 | 5682.22 | 7.83 | 146.31 |
| 2019 | 1102212 | 50.19 | 72.87 | 5310.47 | 0.01 | 11.03 | 31.75 | 65.97 | 6613.44 | 8.23 | 200.42 |
Nhận xét
Đoạn mã này thực hiện thống kê mô tả số lượng giao dịch thành công theo từng năm. Kết quả thống kê cho thấy số lượng giao dịch tăng dần qua các năm từ 1.175.783 giao dịch năm 2010 lên đến đỉnh 1.329.128 giao dịch năm 2017, sau đó giảm nhẹ còn 1.102.212 vào năm 2019. Giá trị trung bình các giao dịch khá ổn định quanh mức 50–52, phản ánh sự nhất quán trong hành vi chi tiêu của khách hàng qua các năm.
Độ xiên và độ nhọn đều duy trì ở mức rất cao (độ xiên khoảng 7,6–9,6; độ nhọn từ 120 đến hơn 260), chứng tỏ phân phối của giá trị giao dịch lệch phải mạnh và có đuôi dài, nghĩa là tồn tại nhiều giao dịch nhỏ xen lẫn một số ít giao dịch có giá trị rất lớn.
Tổng thể, kết quả này phản ánh xu hướng mở rộng hoạt động giao dịch trong giai đoạn đầu, đạt cực đại vào năm 2017, sau đó có dấu hiệu chững lại, trong khi cấu trúc giá trị giao dịch vẫn giữ nguyên tính chất đặc trưng – tập trung chủ yếu vào các giao dịch nhỏ lẻ, phản ánh hành vi tiêu dùng phổ biến trong thanh toán điện tử.
summary_duong_value <- data_duong[, .(
`Số lượng` = .N,
`Trung bình` = mean(amount, na.rm=TRUE),
`Độ lệch chuẩn` = sd(amount, na.rm=TRUE),
`Phương sai` = var(amount, na.rm=TRUE),
Min = min(amount, na.rm=TRUE),
Q1 = quantile(amount, 0.25, na.rm=TRUE),
`Trung vị` = median(amount, na.rm=TRUE),
Q3 = quantile(amount, 0.75, na.rm=TRUE),
Max = max(amount, na.rm=TRUE),
`Độ xiên` = skewness(amount, na.rm=TRUE),
`Độ nhọn` = kurtosis(amount, na.rm=TRUE)
), by=.(`Nhóm giá trị`=nhom_gia_tri)]
summary_duong_value[, (cols_to_round):=round(.SD,2), .SDcols=cols_to_round]
if (knitr::is_latex_output()) {
kable(summary_duong_value, caption="Thống kê mô tả số lượng giao dịch
thành công theo nhóm giá trị giao dịch",format="latex", booktabs=TRUE) %>%
kable_styling(latex_options=c("scale_down","hold_position"), font_size=9, position="center")
} else {
kable(summary_duong_value, caption="Thống kê mô tả theo nhóm giá trị giao dịch") %>%
kable_styling(bootstrap_options=c("striped","hover"), full_width=T)}
| Nhóm giá trị | Số lượng | Trung bình | Độ lệch chuẩn | Phương sai | Min | Q1 | Trung vị | Q3 | Max | Độ xiên | Độ nhọn |
|---|---|---|---|---|---|---|---|---|---|---|---|
| Rất nhỏ (0-50) | 8176139 | 18.83 | 14.36 | 206.28 | 0.01 | 6.09 | 15.52 | 30.29 | 49.99 | 0.51 | 2.04 |
| Nhỏ (50-250) | 4254647 | 92.69 | 39.72 | 1577.41 | 50.00 | 62.62 | 80.00 | 108.22 | 249.99 | 1.46 | 4.94 |
| Trung bình (250-1k) | 195256 | 405.13 | 159.48 | 25433.69 | 250.00 | 290.00 | 354.00 | 458.45 | 999.97 | 1.64 | 5.35 |
| Lớn (>1k) | 9185 | 1296.27 | 379.78 | 144229.45 | 1000.02 | 1080.99 | 1195.22 | 1381.71 | 6820.20 | 4.84 | 42.47 |
Nhận xét
Đoạn mã này tiến hành thống kê mô tả số lượng giao dịch thành công theo nhóm giá trị giao dịch. Kết quả cho thấy số lượng giao dịch tập trung chủ yếu ở hai nhóm giá trị rất nhỏ (8.176.139 giao dịch) và nhỏ (4.254.647 giao dịch), trong khi nhóm trung bình và lớn chiếm tỷ trọng rất thấp (195.256 và 9.185 giao dịch). Điều này phản ánh rằng phần lớn hoạt động thanh toán diễn ra ở mức chi tiêu nhỏ, đặc trưng của giao dịch tiêu dùng hàng ngày.
Các nhóm đều có độ xiên dương (dao động từ 0,51 đến 4,84) và độ nhọn lớn hơn 3 (từ 2,04 đến 42,47), cho thấy phân phối lệch phải và nhọn hơn chuẩn, tức tồn tại một số giao dịch có giá trị cao vượt trội kéo trung bình tăng lên. Xu hướng này phù hợp với đặc điểm chung của dữ liệu giao dịch tài chính – phần lớn giá trị nhỏ và chỉ một số ít có giá trị rất lớn.
summary_duong_city <- data_duong[, .(
`Số lượng` = .N,
`Trung bình` = mean(amount, na.rm=TRUE),
`Độ lệch chuẩn` = sd(amount, na.rm=TRUE),
`Phương sai` = var(amount, na.rm=TRUE),
Min = min(amount, na.rm=TRUE),
Q1 = quantile(amount, 0.25, na.rm=TRUE),
`Trung vị` = median(amount, na.rm=TRUE),
Q3 = quantile(amount, 0.75, na.rm=TRUE),
Max = max(amount, na.rm=TRUE),
`Độ xiên` = skewness(amount, na.rm=TRUE),
`Độ nhọn` = kurtosis(amount, na.rm=TRUE)
), by=.(`Thành phố`=merchant_city)][order(-`Số lượng`)][1:10]
summary_duong_city[, (cols_to_round[1:3]):=round(.SD,2), .SDcols=cols_to_round[1:3]]
if (knitr::is_latex_output()) {
kable(summary_duong_city, caption="Thống kê mô tả top 10 thành phố có
lượng giao dịch cao nhất",format="latex", booktabs=TRUE) %>%
kable_styling(latex_options=c("scale_down","hold_position"), font_size=9, position="center")
} else {kable(summary_duong_city, caption="Thống kê mô tả Top 10 thành phố") %>%
kable_styling(bootstrap_options=c("striped","hover"), full_width=T)}
| Thành phố | Số lượng | Trung bình | Độ lệch chuẩn | Phương sai | Min | Q1 | Trung vị | Q3 | Max | Độ xiên | Độ nhọn |
|---|---|---|---|---|---|---|---|---|---|---|---|
| ONLINE | 1554120 | 59.04 | 89.42 | 7996.65 | 0.01 | 22.10 | 35.36 | 57.5900 | 6613.44 | 9.743650 | 246.41945 |
| HOUSTON | 135977 | 55.31 | 80.13 | 6420.16 | 0.06 | 12.89 | 33.25 | 76.6000 | 2345.04 | 8.502767 | 135.17590 |
| MIAMI | 83797 | 46.45 | 69.08 | 4771.72 | 0.04 | 9.08 | 27.12 | 64.9200 | 3010.22 | 8.183935 | 142.25443 |
| BROOKLYN | 79450 | 39.03 | 60.67 | 3681.12 | 0.11 | 4.17 | 22.22 | 58.2200 | 3215.36 | 12.708598 | 365.60655 |
| LOS ANGELES | 78426 | 42.84 | 80.11 | 6418.11 | 0.04 | 6.43 | 27.13 | 58.7800 | 2090.28 | 10.429253 | 166.03257 |
| CHICAGO | 69935 | 41.21 | 84.79 | 7189.32 | 0.05 | 8.03 | 17.55 | 47.7950 | 3062.19 | 8.887227 | 128.76700 |
| DALLAS | 66223 | 47.54 | 60.81 | 3697.72 | 0.05 | 11.79 | 39.50 | 67.0000 | 1714.99 | 7.764062 | 116.83898 |
| LOUISVILLE | 64000 | 52.86 | 86.49 | 7479.77 | 0.07 | 8.32 | 29.80 | 78.0425 | 3516.73 | 9.735607 | 201.32374 |
| SAN ANTONIO | 56484 | 47.49 | 61.83 | 3822.55 | 0.04 | 12.02 | 30.36 | 64.0000 | 1411.00 | 5.200063 | 54.80188 |
| PHILADELPHIA | 55478 | 53.58 | 74.38 | 5532.86 | 0.01 | 10.35 | 40.00 | 69.1000 | 3424.51 | 9.578237 | 247.91219 |
Nhận xét
Kết quả cho thấy giao dịch trực tuyến (ONLINE) chiếm ưu thế rõ rệt với 1.554.120 giao dịch, giá trị trung bình 59,04 và độ lệch chuẩn 89,42, thể hiện mức độ biến động rất lớn. Tiếp theo là Houston (135.977 giao dịch, trung bình 55,31) và Miami (83.797 giao dịch, trung bình 46,45) – đều duy trì giá trị trung bình ở mức khá nhưng có độ phân tán cao.
Các thành phố như Los Angeles (78.426 giao dịch, trung bình 42,84) và Chicago (69.935 giao dịch, trung bình 41,21) tập trung chủ yếu ở các giao dịch nhỏ đến trung bình. Trong khi đó, Louisville (64.000 giao dịch, trung bình 52,86) và Philadelphia (55.478 giao dịch, trung bình 53,58) có giá trị trung bình cao hơn, thể hiện quy mô giao dịch lớn hơn đôi chút. Đa số các thành phố có độ xiên cao (trên 7) và độ nhọn lớn (trên 100), cho thấy phân phối lệch phải mạnh, nghĩa là phần lớn giao dịch có giá trị nhỏ nhưng tồn tại một số giao dịch rất lớn.
summary_duong_state <- data_duong[, .(
`Số lượng` = .N,
`Trung bình` = mean(amount, na.rm=TRUE),
`Độ lệch chuẩn` = sd(amount, na.rm=TRUE),
`Phương sai` = var(amount, na.rm=TRUE),
Min = min(amount, na.rm=TRUE),
Q1 = quantile(amount, 0.25, na.rm=TRUE),
`Trung vị` = median(amount, na.rm=TRUE),
Q3 = quantile(amount, 0.75, na.rm=TRUE),
Max = max(amount, na.rm=TRUE),
`Độ xiên` = skewness(amount, na.rm=TRUE),
`Độ nhọn` = kurtosis(amount, na.rm=TRUE)
), by=.(Bang=merchant_state)][order(-`Số lượng`)][1:10]
summary_duong_state[, (cols_to_round[1:3]):=round(.SD,2), .SDcols=cols_to_round[1:3]]
if (knitr::is_latex_output()) {
kable(summary_duong_state, caption="Thống kê mô tả top 10 bang có
lượng giao dịch cao nhất",format="latex", booktabs=TRUE) %>%
kable_styling(latex_options=c("scale_down","hold_position"), font_size=9, position="center")
} else {kable(summary_duong_state, caption="Thống kê mô tả Top 10 bang có
lượng giao dịch cao nhất") %>%
kable_styling(bootstrap_options=c("striped","hover"), full_width=T)}
| Bang | Số lượng | Trung bình | Độ lệch chuẩn | Phương sai | Min | Q1 | Trung vị | Q3 | Max | Độ xiên | Độ nhọn |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 1554120 | 59.04 | 89.42 | 7996.65 | 0.01 | 22.10 | 35.36 | 57.59 | 6613.44 | 9.743650 | 246.41945 | |
| CA | 1342445 | 50.39 | 75.94 | 5767.20 | 0.01 | 9.68 | 29.75 | 68.03 | 4685.23 | 7.879764 | 138.51902 |
| TX | 952843 | 50.04 | 68.24 | 4656.54 | 0.01 | 10.86 | 33.19 | 69.33 | 2345.04 | 7.522829 | 115.75867 |
| NY | 819464 | 52.69 | 78.80 | 6209.66 | 0.03 | 9.08 | 32.06 | 73.45 | 6820.20 | 8.962919 | 227.53533 |
| FL | 661908 | 48.73 | 63.34 | 4011.64 | 0.01 | 10.57 | 32.12 | 70.00 | 3010.22 | 6.376862 | 91.43437 |
| OH | 450961 | 43.10 | 63.29 | 4005.40 | 0.01 | 9.03 | 23.99 | 60.00 | 2259.66 | 6.826525 | 90.66514 |
| IL | 447930 | 48.08 | 67.77 | 4593.09 | 0.03 | 9.72 | 30.19 | 65.51 | 3062.19 | 6.781934 | 97.83597 |
| NC | 405545 | 45.84 | 63.65 | 4051.31 | 0.01 | 8.69 | 31.59 | 62.18 | 1994.84 | 7.022449 | 95.10773 |
| PA | 393916 | 51.93 | 69.93 | 4889.73 | 0.01 | 10.74 | 35.91 | 72.00 | 3925.74 | 9.000144 | 240.54469 |
| MI | 374628 | 48.15 | 72.82 | 5303.02 | 0.03 | 7.77 | 25.43 | 61.51 | 1906.77 | 5.134219 | 50.45041 |
Nhận xét
Đoạn mã này thực hiện thống kê mô tả các giao dịch thành công theo từng bang. Kết quả cho thấy NA (các giao dịch trực tuyến) chiếm tỷ trọng lớn nhất với 1.554.120 giao dịch, giá trị trung bình 59,04 và độ lệch chuẩn 89,42, phản ánh mức biến động mạnh tương tự xu hướng ở phần “thành phố”. Các bang có quy mô giao dịch lớn tiếp theo gồm California (CA) với 1.342.445 giao dịch, trung bình 50,39, Texas (TX) với 952.843 giao dịch, trung bình 50,04, và New York (NY) với 819.464 giao dịch, trung bình 52,69.
Các bang như Florida (FL) và Illinois (IL) duy trì mức trung bình khoảng 48–49, trong khi Ohio (OH) và North Carolina (NC) thấp hơn đôi chút (khoảng 43–46). Về đặc điểm phân phối, hầu hết các bang có độ xiên cao (trên 6) và độ nhọn lớn (trên 90), cho thấy phân phối lệch phải mạnh.
summary_duong_weekend <- data_duong[, .(
`Số lượng` = .N,
`Trung bình` = mean(amount, na.rm=TRUE),
`Độ lệch chuẩn` = sd(amount, na.rm=TRUE),
`Phương sai` = var(amount, na.rm=TRUE),
Min = min(amount, na.rm=TRUE),
Q1 = quantile(amount, 0.25, na.rm=TRUE),
`Trung vị` = median(amount, na.rm=TRUE),
Q3 = quantile(amount, 0.75, na.rm=TRUE),
Max = max(amount, na.rm=TRUE),
`Độ xiên` = skewness(amount, na.rm=TRUE),
`Độ nhọn` = kurtosis(amount, na.rm=TRUE)
), by=.(`Cuối tuần`=cuoi_tuan)]
summary_duong_weekend[, (cols_to_round):=round(.SD,2), .SDcols=cols_to_round]
if (knitr::is_latex_output()) {
kable(summary_duong_weekend, caption="Thống kê mô tả số lượng giao dịch
thành công theo loại ngày",format="latex", booktabs=TRUE) %>%
kable_styling(latex_options=c("scale_down","hold_position"), font_size=9, position="center")
} else {kable(summary_duong_weekend, caption="Thống kê mô tả theo loại ngày
(cuối tuần/ngày thường)") %>%
kable_styling(bootstrap_options=c("striped","hover"), full_width=T)}
| Cuối tuần | Số lượng | Trung bình | Độ lệch chuẩn | Phương sai | Min | Q1 | Trung vị | Q3 | Max | Độ xiên | Độ nhọn |
|---|---|---|---|---|---|---|---|---|---|---|---|
| FALSE | 9024884 | 50.55 | 73.92 | 5464.30 | 0.01 | 11.06 | 31.89 | 66.35 | 6820.20 | 8.06 | 166.16 |
| TRUE | 3610343 | 50.73 | 74.35 | 5527.74 | 0.01 | 11.11 | 31.98 | 66.51 | 6613.44 | 8.22 | 176.68 |
Nhận xét
Kết quả cho thấy tổng thể ngày thường chiếm ưu thế với 9.024.884 giao dịch, trong khi cuối tuần có 3.610.343 giao dịch, phản ánh xu hướng hoạt động thanh toán diễn ra mạnh mẽ hơn vào các ngày làm việc. Tuy nhiên, giá trị trung bình của giao dịch cuối tuần (50,73) cao hơn nhẹ so với ngày thường (50,55), cho thấy các giao dịch cuối tuần tuy ít hơn nhưng thường có quy mô lớn hơn một chút.
Độ lệch chuẩn và phương sai ở cả hai nhóm đều tương đương (khoảng 74 và 5.500), cùng với độ xiên trên 8 và độ nhọn trên 160, cho thấy phân phối dữ liệu lệch phải rõ rệt, tức tồn tại nhiều giao dịch nhỏ xen lẫn một số giao dịch có giá trị rất cao (tối đa lên tới 6.820,20).Nhìn chung, khối lượng giao dịch tập trung vào ngày thường, nhưng các giao dịch cuối tuần có xu hướng giá trị cao hơn
summary_duong_year_method <- data_duong[, .(
`Số lượng` = .N,
`Trung bình` = mean(amount, na.rm=TRUE),
`Độ lệch chuẩn` = sd(amount, na.rm=TRUE),
`Phương sai` = var(amount, na.rm=TRUE),
Min = min(amount, na.rm=TRUE),
Q1 = quantile(amount, 0.25, na.rm=TRUE),
`Trung vị` = median(amount, na.rm=TRUE),
Q3 = quantile(amount, 0.75, na.rm=TRUE),
Max = max(amount, na.rm=TRUE),
`Độ xiên` = skewness(amount, na.rm=TRUE),
`Độ nhọn` = kurtosis(amount, na.rm=TRUE)
), by=.(Năm=year, `Phương thức`=payment_type)][order(Năm,`Phương thức`)]
summary_duong_year_method[, (cols_to_round[1:3]):=round(.SD,2),
.SDcols=cols_to_round[1:3]]
if (knitr::is_latex_output()) {
kable(head(summary_duong_year_method,10), caption="Thống kê mô tả số lượng
giao dịch thành công theo năm và phương thức",format="latex", booktabs=TRUE) %>%
kable_styling(latex_options=c("scale_down","hold_position"), font_size=9, position="center")
} else {kable(head(summary_duong_year_method,10), caption="Thống kê mô tả
theo năm và phương thức") %>%
kable_styling(bootstrap_options=c("striped","hover"), full_width=T)}
| Năm | Phương thức | Số lượng | Trung bình | Độ lệch chuẩn | Phương sai | Min | Q1 | Trung vị | Q3 | Max | Độ xiên | Độ nhọn |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 2010 | Online | 133781 | 61.78 | 102.97 | 10602.62 | 0.01 | 21.67 | 35.08 | 59.090 | 5694.44 | 12.314180 | 342.18194 |
| 2010 | Swipe | 1042002 | 50.44 | 73.33 | 5377.22 | 0.01 | 10.16 | 31.94 | 69.000 | 6820.20 | 8.238376 | 192.83882 |
| 2011 | Online | 138610 | 59.30 | 89.65 | 8036.58 | 0.01 | 21.72 | 34.91 | 58.050 | 4984.24 | 9.291588 | 219.92803 |
| 2011 | Swipe | 1085604 | 49.97 | 71.54 | 5117.77 | 0.01 | 10.04 | 31.47 | 68.580 | 4685.23 | 7.211055 | 116.85799 |
| 2012 | Online | 146435 | 59.01 | 87.53 | 7661.33 | 0.02 | 21.66 | 34.84 | 57.525 | 2719.23 | 7.112213 | 93.89253 |
| 2012 | Swipe | 1107975 | 49.58 | 71.56 | 5120.63 | 0.01 | 9.94 | 31.18 | 68.000 | 5913.37 | 7.789568 | 158.23555 |
| 2013 | Online | 156142 | 59.27 | 89.25 | 7965.10 | 0.01 | 22.04 | 35.27 | 57.680 | 5813.78 | 8.962307 | 214.36226 |
| 2013 | Swipe | 1128291 | 49.57 | 71.52 | 5114.68 | 0.01 | 9.91 | 30.99 | 67.930 | 3103.32 | 7.141926 | 104.67417 |
| 2014 | Online | 160512 | 58.71 | 87.75 | 7699.58 | 0.01 | 22.08 | 35.35 | 57.500 | 5696.78 | 9.532053 | 247.19356 |
| 2014 | Swipe | 1136824 | 49.28 | 71.48 | 5109.79 | 0.01 | 9.80 | 30.70 | 67.420 | 5654.50 | 7.793594 | 158.12583 |
Nhận xét
Bảng kết quả cho thấy sự khác biệt rõ rệt giữa hai phương thức thanh toán Online và Swipe. Về số lượng, giao dịch qua Swipe chiếm ưu thế áp đảo, thường gấp nhiều lần so với Online, thể hiện đây vẫn là hình thức thanh toán phổ biến hơn trong tổng thể dữ liệu. Tuy nhiên, giá trị trung bình của giao dịch Online cao hơn đáng kể (khoảng 58–62 so với 49–50 ở nhóm Swipe), phản ánh rằng giao dịch trực tuyến tuy ít nhưng có quy mô lớn hơn.
Độ lệch chuẩn và phương sai của nhóm Online cũng cao hơn, cho thấy mức độ biến động mạnh hơn trong giá trị giao dịch, dễ xuất hiện các giao dịch giá trị rất cao. Bên cạnh đó, các chỉ số độ xiên và độ nhọn đều lớn (độ xiên > 7, độ nhọn > 100), chứng tỏ phân phối dữ liệu bị lệch phải mạnh và có nhiều giá trị ngoại lai. Tổng thể, kết quả cho thấy thanh toán trực tiếp vẫn chiếm ưu thế về số lượng, trong khi thanh toán trực tuyến đóng góp đáng kể về giá trị, thể hiện sự khác biệt về hành vi giao dịch giữa hai hình thức trong bộ dữ liệu nghiên cứu.
summary_duong_year_hour <- data_duong[, .(
`Số lượng` = .N,
`Trung bình` = mean(amount, na.rm=TRUE),
`Độ lệch chuẩn` = sd(amount, na.rm=TRUE),
`Phương sai` = var(amount, na.rm=TRUE),
Min = min(amount, na.rm=TRUE),
Q1 = quantile(amount, 0.25, na.rm=TRUE),
`Trung vị` = median(amount, na.rm=TRUE),
Q3 = quantile(amount, 0.75, na.rm=TRUE),
Max = max(amount, na.rm=TRUE),
`Độ xiên` = skewness(amount, na.rm=TRUE),
`Độ nhọn` = kurtosis(amount, na.rm=TRUE)
), by=.(Năm=year, `Khung giờ`=khung_gio)][order(Năm,`Khung giờ`)]
summary_duong_year_hour[, (cols_to_round[1:3]):=round(.SD,2),
.SDcols=cols_to_round[1:3]]
if (knitr::is_latex_output()) {
kable(head(summary_duong_year_hour,10), caption="Thống kê mô tả số lượng
giao dịch thành công theo năm và khung giờ",format="latex", booktabs=TRUE) %>%
kable_styling(latex_options=c("scale_down","hold_position"), font_size=9, position="center")
} else {kable(head(summary_duong_year_hour,10), caption="Thống kê mô tả theo
năm và khung giờ") %>%
kable_styling(bootstrap_options=c("striped","hover"), full_width=T)}
| Năm | Khung giờ | Số lượng | Trung bình | Độ lệch chuẩn | Phương sai | Min | Q1 | Trung vị | Q3 | Max | Độ xiên | Độ nhọn |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 2010 | Đêm | 69452 | 61.20 | 89.67 | 8040.57 | 0.09 | 9.67 | 37.345 | 83.6425 | 4266.21 | 8.887343 | 240.11789 |
| 2010 | Sáng | 461696 | 43.16 | 72.16 | 5206.38 | 0.01 | 6.68 | 21.890 | 59.5200 | 6820.20 | 12.420622 | 485.86921 |
| 2010 | Chiều | 433547 | 55.04 | 77.31 | 5976.82 | 0.01 | 14.09 | 36.920 | 72.9100 | 4004.73 | 8.387772 | 157.43237 |
| 2010 | Tối | 211088 | 60.55 | 82.08 | 6736.74 | 0.04 | 20.66 | 42.050 | 72.1400 | 4199.33 | 7.776521 | 147.74572 |
| 2011 | Đêm | 71939 | 59.44 | 80.74 | 6518.31 | 0.07 | 9.51 | 36.690 | 82.1150 | 1836.38 | 4.825018 | 49.60527 |
| 2011 | Sáng | 480540 | 42.34 | 66.78 | 4460.20 | 0.01 | 6.51 | 21.410 | 58.9300 | 2632.11 | 7.827752 | 125.75349 |
| 2011 | Chiều | 452658 | 54.53 | 75.38 | 5681.46 | 0.01 | 14.02 | 36.560 | 72.0200 | 4984.24 | 8.363112 | 186.36929 |
| 2011 | Tối | 219077 | 60.06 | 80.84 | 6535.44 | 0.03 | 20.48 | 41.750 | 71.9100 | 4382.90 | 7.412871 | 129.39162 |
| 2012 | Đêm | 73737 | 59.50 | 83.49 | 6971.35 | 0.05 | 9.43 | 36.430 | 82.0000 | 3081.81 | 6.144015 | 94.92908 |
| 2012 | Sáng | 493964 | 42.03 | 66.61 | 4437.37 | 0.01 | 6.44 | 21.260 | 58.2900 | 5913.37 | 8.902463 | 240.64061 |
Nhận xét
Kết quả phản ánh sự khác biệt đáng kể giữa các khung giờ giao dịch: khối lượng giao dịch buổi sáng luôn chiếm tỷ trọng lớn nhất, ví dụ năm 2010 có khoảng 461.696 giao dịch, vượt xa so với các khung giờ khác như chiều (433.547), tối (211.088) hay đêm (69.452).
Về giá trị trung bình, các giao dịch vào buổi tối và đêm thường cao hơn (khoảng 59–61 đơn vị) so với ban ngày (42–55 đơn vị). Đây là dấu hiệu cho thấy giao dịch đêm ít hơn nhưng có quy mô lớn hơn, có thể đến từ nhóm khách hàng cá nhân mua sắm trực tuyến hoặc thanh toán vào cuối ngày.
Ngoài ra, độ lệch chuẩn và độ xiên cao (trên 7) cho thấy phân phối dữ liệu bị lệch phải mạnh, nghĩa là tồn tại nhiều giao dịch giá trị nhỏ và một số ít giao dịch có giá trị rất lớn. Độ nhọn cao (thường vượt 100) khẳng định sự xuất hiện của nhiều ngoại lệ trong dữ liệu giao dịch.
ggplot(pt_method, aes(x = reorder(payment_type, -N), y = N, fill = payment_type)) +
geom_col(width = 0.65, alpha = 0.9, show.legend = FALSE) +
geom_text(aes(label = scales::comma(N, big.mark = ".", decimal.mark = ",")),
vjust = -0.4, size = 3.2, fontface = "bold") +
geom_hline(yintercept = mean(pt_method$N), color = "red", linetype = "dashed",
size = 1) + annotate("text",x = Inf, y = mean(pt_method$N) * 1.05,
label = paste("Trung bình:", scales::comma(mean(
pt_method$N), accuracy = 1, , big.mark = ".", decimal.mark = ",")),
color = "red", fontface = "italic", size = 3.5, hjust = 1.05, vjust = -0.5) +
scale_fill_brewer(palette = "Set2") +
scale_y_continuous(labels = scales::comma_format(big.mark = ".",
decimal.mark = ",")) +
labs(title = "Số lượng giao dịch thành công theo phương thức thanh toán",
x = "Phương thức", y = "Số lượng giao dịch") + theme_minimal(base_size = 12) +
theme(plot.title = element_text(face = "bold", hjust = 0.5),
plot.subtitle = element_text(hjust = 0.5))
Giải thích
Biểu đồ trên được xây dựng bằng hàm ggplot() để thể hiện số lượng giao dịch thành công theo từng phương thức thanh toán. Hàm geom_col() dùng để tạo biểu đồ cột, trong đó các cột được sắp xếp giảm dần theo số lượng (reorder(payment_type, -N)), còn geom_text() chèn nhãn hiển thị giá trị cụ thể trên mỗi cột. Đường nét đứt màu đỏ được thêm bằng geom_hline() nhằm biểu thị giá trị trung bình của toàn bộ phương thức, kèm chú thích bằng annotate() giúp trực quan hóa dễ dàng nhận diện mức trung bình tổng thể. Hàm scale_fill_brewer() và theme_minimal() được sử dụng để tăng tính thẩm mỹ và dễ đọc của biểu đồ.
Nhận xét
Kết quả cho thấy phương thức “Swipe” có số lượng giao dịch thành công cao nhất, đạt 6.571.735 giao dịch, vượt xa mức trung bình chung (4.211.742 giao dịch) và chiếm tỷ trọng nổi bật so với các phương thức khác. Phương thức “Chip” đứng thứ hai với 4.512.267 giao dịch, xấp xỉ mức trung bình, phản ánh đây là hình thức thanh toán ổn định và phổ biến. Trong khi đó, “Online” có số lượng giao dịch thấp nhất (1.551.225 giao dịch), chỉ bằng khoảng một phần ba so với trung bình.
Điều này cho thấy các giao dịch trực tiếp (Swipe và Chip) vẫn là hình thức thanh toán chủ đạo, trong khi giao dịch trực tuyến (Online) tuy đang phát triển nhưng vẫn còn chiếm tỷ trọng nhỏ — phản ánh sự chênh lệch rõ rệt giữa hành vi thanh toán truyền thống và hiện đại trong tập dữ liệu.
pt_method <- data_duong[, .N, by = payment_type][order(-N)]
pt_method <- pt_method %>%
mutate(perc = N / sum(N) * 100,
payment_type = factor(payment_type,levels = c("Swipe", "Chip", "Online"),
labels = c("Swipe (Quẹt thẻ từ)","Chip (Thẻ gắn chip)",
"Online (Thanh toán trực tuyến)")))
ggplot(pt_method, aes(x = 2, y = perc, fill = payment_type)) +
geom_bar(stat = "identity", width = 1, color = "white") +
coord_polar(theta = "y", start = 0) +
geom_text(aes(label = paste0(round(perc, 1), "%")),
position = position_stack(vjust = 0.5),
size = 3.5, color = "white", fontface = "bold") +
scale_fill_brewer(palette = "Pastel1", name = "Phương thức thanh toán") +
xlim(0.5, 2.5) + labs( title = "Tỷ trọng giao dịch thành công theo phương thức") +
theme_void(base_size = 12) +
theme(legend.position = "right", legend.title = element_text(face = "bold"),
plot.title = element_text(face = "bold", hjust = 0.5, size = 14),
plot.subtitle = element_text(face = "italic", hjust = 0.5, size = 10))
Giải thích
Biểu đồ được xây dựng bằng hàm ggplot() kết hợp với hệ toạ độ cực coord_polar() để tạo dạng biểu đồ tròn thể hiện tỷ trọng giao dịch thành công theo từng phương thức thanh toán. Hàm geom_bar(stat=“identity”) giúp vẽ các phần tương ứng với tỷ lệ phần trăm, còn geom_text() hiển thị tỷ lệ phần trăm trên từng lát biểu đồ, giúp dễ so sánh trực quan. Lớp scale_fill_brewer(palette=“Pastel1”) tạo gam màu nhẹ, và phần xlim(0.5, 2.5) mở rộng trục X để tạo khoảng trống trung tâm, biến biểu đồ tròn thành dạng donut. Hàm theme_void() loại bỏ các chi tiết dư thừa, giúp biểu đồ gọn gàng và tập trung vào thông tin chính.
Nhận xét
Kết quả thể hiện phương thức “Swipe (Quẹt thẻ từ)” chiếm tỷ trọng cao nhất – 52% tổng số giao dịch thành công, cho thấy đây là hình thức thanh toán phổ biến nhất. Phương thức “Chip (Thẻ gắn chip)” đứng thứ hai với 35,7%, phản ánh xu hướng chuyển dần sang công nghệ thẻ bảo mật cao hơn. Trong khi đó, “Online (Thanh toán trực tuyến)” chỉ chiếm 12,3%, cho thấy giao dịch trực tuyến vẫn còn hạn chế trong tập dữ liệu này.
ggplot(data_duong, aes(x = khung_gio, fill = khung_gio)) +
geom_bar(width = 0.7, alpha = 0.8, color = "white") +
scale_fill_brewer(palette = "Pastel1") +
labs(title = "Phân bố giao dịch thành công theo khung giờ trong ngày",
x = "Khung giờ", y = "Số lượng giao dịch") +
geom_text(stat = "count", aes(
label = scales::comma_format(big.mark = ".",decimal.mark =",")(after_stat(count))),
vjust = -0.5, size = 3.2) + theme_minimal() +
theme(plot.title = element_text(face = "bold", size = 14, hjust = 0.5),
plot.subtitle = element_text(face = "italic", size = 10, hjust = 0.5),
legend.position = "none")
Giải thích
Biểu đồ được xây dựng bằng ggplot2 với geom_bar() để biểu diễn số lượng giao dịch thành công theo khung giờ trong ngày. Lệnh aes(x = khung_gio, fill = khung_gio) quy định trục hoành là khung giờ, đồng thời mỗi cột mang màu khác nhau tương ứng từng khung. Tham số stat = “count” giúp R tự động đếm số lượng giao dịch, còn geom_text() được dùng để hiển thị giá trị cụ thể trên đỉnh cộ. Phong cách màu Pastel từ scale_fill_brewer(palette = “Pastel1”) cùng theme_minimal() làm nổi bật dữ liệu chính mà không gây rối mắt.
Nhận xét
Kết quả cho thấy hoạt động giao dịch tập trung mạnh nhất vào buổi sáng với 4.994.972 giao dịch, chiếm tỷ trọng lớn nhất trong ngày. Buổi chiều cũng duy trì mức cao với 4.647.658 giao dịch, phản ánh nhu cầu thanh toán tiếp tục sôi động sau giờ làm việc buổi sáng. Trong khi đó, buổi tối chỉ đạt 2.248.379 giao dịch, và ban đêm là thấp nhất – 744.218 giao dịch, cho thấy tần suất sử dụng dịch vụ giảm rõ rệt khi người dùng nghỉ ngơi. Từ đó có thể thấy, giao dịch tập trung chủ yếu vào khung giờ hành chính (sáng – chiều), phù hợp với đặc điểm hoạt động của phần lớn doanh nghiệp và người tiêu dùng trong ngày.
pt_year_method <- data_duong[, .N, by = .(year, payment_type)]
ggplot(pt_year_method, aes(x = factor(year), y = N, fill = payment_type)) +
geom_col(position = "stack", alpha = 0.9) +
geom_text(aes(label = scales::comma(N, big.mark = ".", decimal.mark = ",")),
position = position_stack(vjust = 0.5), color = "black", size = 2.5) +
scale_fill_brewer(palette = "Set3", name = "Phương thức") +
labs(title = "So sánh khối lượng giao dịch thành công theo năm và phương thức",
x = "Năm", y = "Số lượng giao dịch") +
theme_minimal() + theme(plot.title = element_text(face = "bold", hjust = 0.5),
plot.subtitle = element_text(hjust = 0.5, face = "italic"),
axis.title = element_text(face = "bold"))
Giải thích
Biểu đồ trên được xây dựng bằng ggplot2 với hàm geom_col() ở dạng stacked bar để thể hiện tổng số lượng giao dịch thành công theo từng năm và phương thức thanh toán. Dữ liệu được nhóm bởi năm và phương thức thông qua data.table. Các giá trị được hiển thị trực tiếp lên cột bằng geom_text(), giúp dễ dàng so sánh quy mô giữa các nhóm. Màu sắc được phối bằng bảng “Set3” trong scale_fill_brewer(), tạo nên sự phân biệt giữa ba phương thức: Chip, Online và Swipe. Biểu đồ được làm gọn gàng với theme_minimal() và tiêu đề in đậm ở giữa để tăng tính thẩm mỹ.
Nhận xét
Từ năm 2010–2014, phương thức Swipe chiếm ưu thế tuyệt đối, duy trì mức trên 1,04–1,13 triệu giao dịch/năm. Tuy nhiên, từ 2015 trở đi, sự xuất hiện của Chip đã đạt 927.618 giao dịch, trở thành phương thức chủ đạo và duy trì mức ổn định quanh 930 nghìn giao dịch/năm giai đoạn 2016–2018. Trong khi đó, Online dù tăng nhẹ giai đoạn 2010–2014 (từ 133.781 lên 160.512), nhưng sau 2015 chỉ dao động quanh 168–170 nghìn giao dịch/năm, cho thấy mức tăng trưởng chậm hơn so với Chip. Đến năm 2019, tất cả các phương thức đều giảm nhẹ, trong đó Chip còn 776.843 giao dịch, Online 139.704, và Swipe 185.665, phản ánh xu hướng bão hòa và có thể ảnh hưởng từ các yếu tố thị trường hoặc chuyển dịch sang nền tảng thanh toán mới. Tổng thể, sự chuyển dịch từ Swipe sang Chip thể hiện xu hướng công nghệ thẻ hiện đại hóa, còn Online tuy duy trì ổn định nhưng chưa bứt phá, cho thấy tiềm năng mở rộng trong tương lai nếu hạ tầng và trải nghiệm người dùng được cải thiện.
pt_year <- data_duong[, .N, by = year][order(year)]
nudge_y_values <- ifelse(
pt_year$year %in% c(2013, 2014, 2015, 2016, 2017, 2018), 30000,-30000)
nudge_x_values <- ifelse( pt_year$year == 2010, 0.2,
ifelse(pt_year$year == 2019, -0.2, 0))
ggplot(pt_year, aes(x = year, y = N)) +
geom_line(aes(color = "Số lượng thực tế"), size = 1.2) +
geom_point(size = 2.5, color = "#E41A1C") +
geom_smooth(aes(color = "Xu hướng"), method = "lm", se = FALSE,
linetype = "dashed", size = 1) +
ggrepel::geom_label_repel( aes(label = scales::comma(N, accuracy = 1,
big.mark = ".",
decimal.mark = ",")),
size = 3.2,
color = "black", fill = "white", nudge_y = nudge_y_values,
nudge_x = nudge_x_values,
segment.linetype = "dashed",segment.size = 0.3, segment.color = "gray50",
box.padding = 0.1) +
scale_color_manual( name = "Chú thích:",
values = c("Số lượng thực tế" = "#0072B2", "Xu hướng" = "gray40")
) +
scale_y_continuous(labels = scales::comma_format(big.mark = ".", decimal.mark = ","),
expand = expansion(mult = c(0.1, 0.15))) +
scale_x_continuous(breaks = sort(unique(pt_year$year))) +
labs(title = "Xu hướng tăng trưởng giao dịch thành công theo năm",
x = "Năm", y = "Số lượng giao dịch") + theme_minimal(base_size = 12) +
theme(plot.title = element_text(hjust = 0.5, face = "bold"),
plot.subtitle = element_text(hjust = 0.5, face = "italic"),
legend.position = "bottom")
Giải thích
Biểu đồ được xây dựng bằng ggplot2, hiển thị xu hướng tăng trưởng số lượng giao dịch thành công theo năm. Dữ liệu được tổng hợp bằng data.table với số lượng giao dịch (.N) cho từng năm. Đường màu xanh thể hiện số lượng thực tế, còn đường gạch xám là đường xu hướng tuyến tính được vẽ bằng geom_smooth(method = “lm”). Các điểm dữ liệu được đánh dấu bằng geom_point(), kết hợp với geom_label_repel() để hiển thị giá trị cụ thể của từng năm mà không bị chồng lấn, giúp biểu đồ rõ ràng và trực quan.
Nhận xét
Giai đoạn 2010–2018, tổng số lượng giao dịch thành công tăng đều đặn từ 1.175.783 lên mức đỉnh 1.326.144 giao dịch, thể hiện xu hướng mở rộng ổn định của hệ thống thanh toán. Mức tăng trung bình mỗi năm khoảng 2–3%, cho thấy thị trường giao dịch điện tử phát triển bền vững. Tuy nhiên, đến năm 2019, số lượng giao dịch giảm mạnh xuống 1.102.212, tương đương giảm hơn 16,9% so với năm trước. Sự sụt giảm này có thể do sự thay đổi trong hành vi người dùng, điều chỉnh kỹ thuật hệ thống, hoặc chuyển dịch sang các kênh thanh toán khác.
amount_trim <- data_duong[amount <= quantile(amount, 0.99, na.rm = TRUE)]$amount
mean_val <- mean(amount_trim, na.rm = TRUE)
ggplot(data.frame(amount = amount_trim), aes(x = amount)) +
geom_histogram(aes(y = ..density..),
binwidth = 5, fill = "#56B4E9", color = "white", alpha = 0.7) +
geom_density(color = "red", size = 1.2) +
geom_vline(xintercept = mean_val, color = "black", linetype = "dashed", size = 1.1) +
annotate("text", x = mean_val + 10, y = 0.015,
label = paste0("Trung bình = ", round(mean_val, 1)),
color = "black", hjust = 0) +
labs(title = "Phân phối giá trị giao dịch thành công",
x = "Giá trị giao dịch ($)", y = "Mật độ") +
theme_minimal(base_size = 12) +
theme(plot.title = element_text(face = "bold", hjust = 0.5),
plot.subtitle = element_text(hjust = 0.5))
Giải thích
Lệnh amount_trim <- data_duong[amount <= quantile(amount, 0.99, na.rm = TRUE)]$amount loại bỏ 1% giá trị giao dịch cao nhất để tránh ảnh hưởng của các ngoại lệ, giúp phân phối hiển thị rõ ràng hơn. Sau đó, mean_val <- mean(amount_trim, na.rm = TRUE) tính giá trị trung bình của các giao dịch còn lại.
Trong phần trực quan, geom_histogram() được dùng để vẽ biểu đồ tần suất (chuẩn hóa theo mật độ) với binwidth = 5, thể hiện sự phân bố của giá trị giao dịch theo từng khoảng 5 USD. Lệnh geom_density() vẽ đường mật độ màu đỏ chồng lên histogram để mô tả xu hướng phân phối tổng thể. Đường trung bình được thêm bằng geom_vline() với nét đứt màu đen, còn annotate() hiển thị nhãn “Trung bình = …” ngay cạnh để làm rõ vị trí này. Và biểu đồ được định dạng bằng theme_minimal() và tiêu đề in đậm, giúp bố cục gọn và dễ đọc.
Nhận xét
Kết quả biểu đồ cho thấy phân bố giá trị giao dịch thành công có dạng lệch phải rõ rệt. Đường mật độ minh họa một đỉnh cao tập trung ở các giao dịch có giá trị rất nhỏ (khoảng 0–10 USD), sau đó giảm nhanh và kéo dài về phía các giá trị lớn hơn. Điều này phản ánh thực tế rằng phần lớn các giao dịch thẻ có giá trị thấp, chủ yếu phục vụ chi tiêu hằng ngày như mua sắm tạp hóa, đồ uống hoặc xăng dầu. Giá trị trung bình đạt 45.6 USD, cao hơn đáng kể so với khu vực tập trung của dữ liệu, cho thấy ảnh hưởng của các giao dịch giá trị cao kéo lệch phân phối về bên phải. Ngoài ra, các đỉnh nhỏ lặp lại quanh mức 50, 75 và 100 USD có thể biểu hiện cho những mức giá phổ biến hoặc các khoản thanh toán định kỳ trong tập dữ liệu.
pt_weekday_perc <- pt_weekday %>%
mutate( perc = N / sum(N), label_text = paste0(scales::percent(
perc, accuracy = 0.1)))
ggplot(pt_weekday_perc, aes(x = "", y = perc, fill = weekday)) +
geom_col(color = "white", width = 1) +
coord_polar(theta = "y", start = 0) +
ggrepel::geom_text_repel(aes(label = label_text),
position = position_stack(vjust = 0.5),
size = 3.5, color = "black", fontface = "bold",
segment.color = "gray50", show.legend = FALSE) +
scale_fill_brewer(palette = "Pastel2", name = "Thứ:") + labs(
title = "Tỷ trọng số lượng giao dịch thành công theo thứ trong tuần") +
theme_void(base_size = 12) +
theme( plot.title = element_text(hjust = 0.5, face = "bold"),
legend.position = "right")
Giải thích
Đoạn mã trên sử dụng ggplot2 kết hợp với ggrepel để trực quan hóa tỷ trọng số lượng giao dịch thành công theo thứ trong tuần dưới dạng biểu đồ tròn. Đầu tiên, biến perc được tính bằng cách chia số lượng giao dịch từng ngày (N) cho tổng số giao dịch trong tuần, sau đó được định dạng hiển thị dưới dạng phần trăm (label_text). Trong bước vẽ, hàm geom_col() dựng các cột tỷ lệ, sau đó coord_polar() chuyển chúng thành biểu đồ tròn. Các nhãn phần trăm được chèn bằng geom_text_repel() — giúp nhãn không bị chồng lên nhau, đảm bảo tính trực quan. Hàm theme_void() loại bỏ trục – phù hợp với biểu đồ tỷ trọng.
Nhận xét
Biểu đồ tròn thể hiện tỷ trọng số lượng giao dịch thành công trong 7 ngày của tuần, cho thấy sự phân bố gần như hoàn toàn đồng đều. Mỗi ngày chiếm khoảng 14,3% tổng số giao dịch, với mức dao động rất nhỏ (14,2%–14,4%). Về mặt kinh tế, điều này cho thấy hành vi thanh toán của người dùng ít chịu tác động bởi yếu tố “thứ trong tuần”. Không xuất hiện xu hướng gia tăng giao dịch vào cuối tuần hay giảm vào đầu tuần, qua đó hàm ý rằng phần lớn các giao dịch trong tập dữ liệu là những khoản chi tiêu thường nhật, thiết yếu và duy trì ổn định trong suốt cả tuần.
ggplot(pt_city, aes(area = N, fill = merchant_city, label = paste0(merchant_city, "\n", scales::comma(N, big.mark = ".", decimal.mark = ",")))) +
geom_treemap(color = "white", size = 1) +
geom_treemap_text( color = "black", place = "centre", grow = TRUE, reflow = TRUE,
min.size = 6, size = 14 ) +
scale_fill_brewer(palette = "Paired") +
labs(title = "Top 10 thành phố có số lượng giao dịch thành công cao nhất")+
theme_void() +
theme( plot.title = element_text(hjust = 0.5, face = "bold", size = 16),
plot.subtitle = element_text(hjust = 0.5, face = "italic"),
legend.position = "none")
Giải thích
Đoạn mã này sử dụng biểu đồ cây (treemap) để trực quan hoá top 10 thành phố có số lượng giao dịch thành công cao nhất.
Nhận xét
Biểu đồ Treemap cho thấy sự phân bổ rõ rệt và mất cân đối mạnh trong số lượng giao dịch giữa các địa điểm. Nổi bật nhất là “ONLINE”, chiếm 1,55 triệu giao dịch, vượt trội hoàn toàn so với các thành phố còn lại, là danh mục đại diện cho giao dịch thương mại điện tử, phản ánh xu hướng tiêu dùng kỹ thuật số ngày càng chiếm ưu thế.
Ở nhóm thành phố vật lý, Houston (135.977 giao dịch) dẫn đầu, tiếp theo là Miami (83.797) và Brooklyn (79.450). Mặc dù các thành phố này có khối lượng giao dịch đáng kể, song khoảng cách giữa “ONLINE” và nhóm đô thị lớn vẫn rất lớn, cho thấy kênh trực tuyến đã trở thành trụ cột chính trong hệ thống thanh toán hiện đại.
pt_state <- data_duong[, .N, by = merchant_state][order(-N)][1:10]
ggplot(pt_state, aes(x = reorder(merchant_state, N), y = N, fill = merchant_state)) +
geom_col(alpha = 0.9, color = "black") +
coord_flip() +
geom_text(aes(label = scales::comma(N, big.mark = ".", decimal.mark = ",")),
hjust = 1.1, color = "black", size = 4) +
scale_y_continuous(labels = scales::comma_format(big.mark = ".",
decimal.mark = ",")) +
scale_fill_manual(values = c("#A6CEE3", "#B2DF8A", "#FDBF6F", "#CAB2D6",
"#FB9A99", "#8DD3C7", "#FFFFB3", "#BEBADA",
"#80B1D3", "#FFED6F")) +
labs(title = "So sánh 10 bang có số lượng giao dịch thành công cao nhất",
x = "Bang", y = "Số lượng giao dịch") +
theme_minimal() + theme(legend.position = "none",
plot.title = element_text(hjust = 0.5, face = "bold"),
plot.subtitle = element_text(hjust = 0.5, face = "italic"))
Giải thích
Câu lệnh pt_state <- data_duong[, .N, by = merchant_state][order(-N)][1:10] sử dụng cú pháp data.table để đếm số lượng giao dịch (.N) theo từng bang, sau đó sắp xếp giảm dần và chọn 10 bang đứng đầu. Hàm ggplot() được dùng để vẽ biểu đồ cột ngang so sánh giữa các bang. Hàm reorder() giúp sắp xếp cột theo giá trị, coord_flip() xoay biểu đồ để hiển thị tên bang rõ hơn, và geom_text() chèn nhãn số liệu cụ thể. Hàm scale_fill_manual() tùy chỉnh màu cho từng bang, còn theme_minimal() làm biểu đồ gọn gàng và chuyên nghiệp hơn.
Nhận xét Biểu đồ cho thấy sự phân bố không đồng đều về số lượng giao dịch thành công giữa các bang. Nhóm dẫn đầu gồm giao dịch trực tuyến (NA), California và Texas, trong đó “NA” chiếm hơn 1,55 triệu giao dịch. California và Texas tiếp theo với hơn 1,3 và 0,9 triệu giao dịch, phù hợp với đặc điểm là các trung tâm kinh tế – tài chính lớn. Các bang còn lại như New York, Florida hay Ohio có khối lượng giao dịch thấp hơn, thể hiện sự tập trung hoạt động thanh toán tại các vùng có hạ tầng kinh tế phát triển mạnh. Kết quả này cho thấy xu hướng mở rộng thanh toán điện tử và mức độ tiêu dùng cao ở các khu vực đô thị, đồng thời khẳng định vai trò ngày càng lớn của các giao dịch không phụ thuộc địa lý trong nền kinh tế số.
pt_weekend <- data_duong[, .N, by = cuoi_tuan]
pt_weekend[, perc := round(N / sum(N) * 100, 1)]
ggplot(pt_weekend,
aes(x = factor(cuoi_tuan, labels = c("Ngày thường", "Cuối tuần")),
y = N, fill = cuoi_tuan)) +
geom_col(width = 0.6, alpha = 0.9, color = "gray20", show.legend = FALSE) +
geom_text(aes(label = scales::comma(N)),
vjust = -1.2, size = 3.5, color = "gray20", fontface = "bold") +
geom_text(aes(y = N / 2, label = paste0(perc, "%")),
vjust = 0.5, size = 3.8, color = "white", fontface = "bold") +
scale_fill_manual(values = c("#80cbc4", "#4db6ac")) +
labs(title = "So sánh giao dịch thành công: Ngày thường và Cuối tuần",
x = "Loại ngày", y = "Số lượng giao dịch") +
scale_y_continuous(labels = scales::comma_format(
big.mark = ".", decimal.mark = ","), expand = expansion(mult = c(0, 0.1))) +
theme_minimal(base_size = 12) +
theme(
plot.title = element_text(face = "bold", hjust = 0.5, size = 14),
plot.subtitle = element_text(hjust = 0.5, size = 11, color = "gray30"),
axis.title.x = element_text(face = "bold"),
axis.title.y = element_text(face = "bold"),
axis.text = element_text(size = 10))
Giải thích
Đoạn mã sử dụng data.table để tổng hợp dữ liệu và ggplot2 để trực quan hóa. Hàm data_duong[, .N, by = cuoi_tuan] đếm số lượng giao dịch theo biến cuối tuần, sau đó biến perc được tính bằng công thức round(N / sum(N) 100, 1) để xác định tỷ lệ phần trăm của mỗi nhóm. Biểu đồ cột được vẽ bằng geom_col(), trong đó hai lớp geom_text() lần lượt hiển thị số lượng và tỷ lệ phần trăm giao dịch. Thang màu được tùy chỉnh với scale_fill_manual(), các nhãn được định dạng bằng labs(), và bố cục được tinh gọn nhờ theme_minimal().
Nhận xét
Biểu đồ cho thấy phần lớn giao dịch thành công diễn ra vào ngày thường, chiếm 71,4%, trong khi cuối tuần chỉ chiếm 28,6%. Kết quả này phản ánh thói quen tiêu dùng gắn với các hoạt động kinh tế – thương mại trong tuần, khi nhu cầu thanh toán và giao dịch cao hơn. Tỷ lệ giảm vào cuối tuần cho thấy xu hướng chi tiêu chuyển sang các hoạt động nghỉ ngơi hoặc giải trí, ít phụ thuộc vào hệ thống thanh toán chính thức. Điều này hàm ý rằng cường độ giao dịch điện tử vẫn tuân theo nhịp hoạt động lao động – kinh doanh của nền kinh tế.
avg_year <- data_duong[, .(mean_amount = mean(amount, na.rm = TRUE)), by = year][order(year)]
nudge_y_values <- ifelse(avg_year$year %in% c(2010, 2013, 2016, 2018, 2019),
0.18, -0.18)
nudge_x_values <- ifelse( avg_year$year == 2010, 0.2,
ifelse(avg_year$year == 2019, -0.2, 0))
ggplot(avg_year, aes(x = year, y = mean_amount)) +
geom_line(aes(color = "Giá trị Trung bình"), size = 1.2) +
geom_point(color = "#0d47a1", size = 3) +
geom_smooth(aes(color = "Xu hướng"), method = "lm", se = FALSE,
linetype = "dashed", size = 1) +
ggrepel::geom_label_repel( aes(label = round(mean_amount, 1)), size = 3.2,
color = "black",
fill = "white", nudge_y = nudge_y_values,
nudge_x = nudge_x_values, segment.linetype = "dashed",
segment.size = 0.3, segment.color = "gray50",
box.padding = 0.1) +
scale_color_manual( name = "Chú thích:",
values = c("Giá trị Trung bình" = "#1e88e5", "Xu hướng" = "gray40")) +
scale_x_continuous(breaks = seq(min(avg_year$year), max(avg_year$year), by = 1),
labels = function(x) format(round(x), scientific = FALSE)) +
scale_y_continuous(expand = expansion(mult = c(0.15, 0.15))) +
labs(title = "Xu hướng giá trị trung bình giao dịch thành công theo năm",
x = "Năm", y = "Giá trị trung bình ($)") +
theme_minimal(base_size = 12) +
theme( plot.title = element_text(hjust = 0.5, face = "bold"),
plot.subtitle = element_text(hjust = 0.5),
legend.position = "bottom")
Giải thích
Đoạn mã tính giá trị trung bình của biến amount theo từng năm và ggplot2 để thể hiện xu hướng biến động đó. Biến “avg_year” được tạo bằng cách nhóm dữ liệu data_duong theo năm với hàm mean() tính trung bình có loại trừ giá trị thiếu (na.rm = TRUE). Sau khi sắp xếp theo thứ tự thời gian, biểu đồ được xây dựng bằng geom_line() để thể hiện đường biến động giá trị trung bình, kết hợp geom_point() để đánh dấu từng năm. Đường xu hướng tuyến tính được thêm qua geom_smooth(method = “lm”) với dạng nét đứt, giúp minh họa khuynh hướng dài hạn. Các giá trị trung bình được hiển thị bằng geom_label_repel() nhằm tránh chồng nhãn, đồng thời cải thiện tính trực quan. Hàm scale_color_manual() và theme_minimal() được dùng để tùy chỉnh màu sắc, chú thích và giao diện biểu đồ.
Nhận xét
Biểu đồ cho thấy giá trị trung bình của các giao dịch thành công có xu hướng giảm nhẹ theo thời gian trong giai đoạn 2010–2019. Mức trung bình giảm từ khoảng 51,7 USD năm 2010 xuống còn 50,2 USD năm 2019, thể hiện sự thu hẹp dần về quy mô giá trị mỗi giao dịch. Diễn biến này có thể phản ánh sự mở rộng của thanh toán điện tử đối với các giao dịch giá trị nhỏ, chẳng hạn như chi tiêu hàng ngày hoặc dịch vụ trực tuyến, thay vì tập trung vào các khoản mua sắm lớn. Về mặt kinh tế, xu hướng giảm nhẹ này hàm ý rằng thói quen tiêu dùng điện tử ngày càng phổ biến, với tần suất giao dịch tăng lên nhưng giá trị trung bình mỗi giao dịch thấp hơn, phù hợp với sự phát triển của nền kinh tế số và thương mại điện tử trong giai đoạn nghiên cứu.
pt_hour_method <- data_duong[, .N, by = .(khung_gio, payment_type)]
pt_hour_method[, Total_N := sum(N), by = .(khung_gio)]
pt_hour_method[, Percentage := N / Total_N]
ggplot(pt_hour_method, aes(x = khung_gio, y = Percentage, fill = payment_type)) +
geom_col(position = "dodge", color = "gray30") +
geom_text(aes(label = scales::percent(Percentage, accuracy = 0.1)),
position = position_dodge(width = 0.9),
vjust = -0.5, size = 3) +
scale_fill_brewer(palette = "Pastel1") +
labs(title = "Cơ cấu phần trăm giao dịch thành công theo khung giờ và phương thức",
x = "Khung giờ", y = "Tỷ lệ giao dịch (%)", fill = "Phương thức") +
theme_minimal(base_size = 12) +
theme(plot.title = element_text(hjust = 0.5, face = "bold"),
plot.subtitle = element_text(hjust = 0.5))
Giải thích
Đoạn mã tính tỷ lệ phần trăm giao dịch thành công theo từng khung giờ và phương thức thanh toán, sau đó trực quan hóa bằng ggplot2. Biến pt_hour_method được tạo bằng cú pháp data_duong[, .N, by = .(khung_gio, payment_type)] nhằm đếm số lượng giao dịch cho mỗi cặp khung giờ – phương thức. Tổng số giao dịch trong từng khung giờ được tính bằng sum(N) và tỷ lệ phần trăm được xác định qua N / Total_N. Biểu đồ cột được tạo với geom_col(position = “dodge”) để các nhóm hiển thị song song, kết hợp geom_text() để hiển thị giá trị phần trăm ngay trên cột
Nhận xét Biểu đồ cho thấy phương thức thanh toán “Swipe” chiếm ưu thế trong mọi khung giờ, với tỷ lệ dao động quanh 50% tổng số giao dịch thành công. Phương thức “Chip” đứng thứ hai, chiếm khoảng 33–36%, trong khi “Online” có tỷ lệ thấp nhất, chỉ từ 11–17% tùy thời điểm. Tỷ trọng cao của giao dịch “Swipe” và “Chip” phản ánh việc thanh toán trực tiếp qua thẻ vật lý vẫn là hình thức chủ đạo, đặc biệt trong các khung giờ hoạt động thương mại truyền thống như sáng và chiều. Ngược lại, tỷ lệ “Online” thấp hơn cho thấy giao dịch điện tử tuy đã phổ biến nhưng chưa chiếm ưu thế tuyệt đối. Nhìn chung, kết quả này cho thấy sự song song giữa hành vi tiêu dùng truyền thống và xu hướng số hóa.
pt_state_day <- data_duong[, .(mean_amount = mean(amount, na.rm = TRUE)),
by = .(merchant_state, cuoi_tuan)]
top_states <- pt_state_day[, .(total = sum(mean_amount)), by = merchant_state][order(-total)][1:10]$merchant_state
pt_state_day <- pt_state_day[merchant_state %in% top_states]
ggplot(pt_state_day, aes(x = reorder(merchant_state, mean_amount),
y = mean_amount, fill = factor(cuoi_tuan, labels = c("Ngày thường", "Cuối tuần")))) +
geom_col(position = position_dodge(width = 0.8), color = "white", alpha = 0.9) +
geom_text(aes(label = round(mean_amount, 1)), position = position_dodge(width = 0.8),
hjust = 1.1, vjust = 0.5, size = 2.8) +
coord_flip() +
scale_fill_brewer(palette = "Pastel1", name = "Loại ngày") +
labs(title = "Giá trị trung bình giao dịch thành công theo bang và loại ngày",
x = "Bang", y = "Giá trị trung bình ($)") +
theme_minimal() +
theme(plot.title = element_text(hjust = 0.5, face = "bold"))
Giải thích
Hàm mean(amount, na.rm = TRUE) được tính cho từng bang và loại ngày nhằm xác định giá trị trung bình giao dịch. Sau đó, nhóm 10 bang có giá trị trung bình cao nhất được chọn bằng cách tính tổng (sum(mean_amount)), sắp xếp giảm dần và lọc lại bằng toán tử %in%. Trong biểu đồ, geom_col(position = position_dodge()) vẽ cột song song thể hiện hai loại ngày, còn geom_text() hiển thị nhãn giá trị trên từng cột. Hàm coord_flip() xoay biểu đồ theo chiều ngang giúp so sánh dễ hơn giữa các bang. Cuối cùng, scale_fill_brewer() và theme_minimal() được dùng để tùy chỉnh màu sắc và bố cục.
Nhận xét
Biểu đồ phản ánh sự khác biệt trong hành vi chi tiêu giữa ngày thường và cuối tuần, cho thấy mô hình chi tiêu không đồng nhất mà thay đổi theo từng bang. Ở một số bang như Zimbabwe và Slovenia, giá trị giao dịch trung bình cuối tuần cao hơn đáng kể, cho thấy xu hướng chi tiêu lớn cho các hoạt động giải trí hoặc mua sắm cao cấp. Ngược lại, phần lớn các bang khác như Brunei, Equatorial Guinea hay Vietnam lại có giá trị trung bình cao hơn vào ngày thường, hàm ý rằng các giao dịch quy mô lớn chủ yếu diễn ra trong khung thời gian làm việc. Sự khác biệt này cho thấy hành vi tiêu dùng không chỉ chịu ảnh hưởng của yếu tố thời gian mà còn phản ánh đặc điểm kinh tế – văn hóa riêng của từng khu vực, nơi cấu trúc chi tiêu gắn chặt với nhịp độ lao động và mức độ phát triển thị trường.
pt_heat <- data_duong[, .N, by = .(weekday, khung_gio)]
ggplot(pt_heat, aes(x = khung_gio, y = weekday, fill = N)) +
geom_tile(color = "white") +
geom_text(aes(label = scales::comma(N, big.mark = ".", decimal.mark = ",")),
color = "black", size = 3, ) +
scale_fill_gradient(low = "#cfe2f3", high = "#084594", name = "Số lượng") +
labs(title = "Số lượng giao dịch thành công theo khung giờ và thứ trong tuần",
x = "Khung giờ", y = "Thứ") +
theme_minimal(base_size = 12) +
theme(plot.title = element_text(hjust = 0.5, face = "bold"))
Giải thích
Đoạn mã sử dụng data.table để tổng hợp dữ liệu và ggplot2 để trực quan hóa dưới dạng biểu đồ nhiệt. Lệnh data_duong[, .N, by = .(weekday, khung_gio)] đếm số lượng giao dịch thành công cho từng tổ hợp giữa thứ trong tuần và khung giờ, tạo cơ sở cho ma trận tần suất. Trong phần vẽ, hàm geom_tile() tạo các ô vuông có màu thể hiện cường độ giao dịch, được tô bằng thang màu liên tục xác định qua scale_fill_gradient(low, high).
Nhận xét
Biểu đồ nhiệt thể hiện rõ sự phân bố giao dịch theo cả thời điểm trong ngày và các ngày trong tuần, cho thấy một mô thức hoạt động rất ổn định. Về khung giờ, “sáng” là giai đoạn sôi động nhất với khoảng 710–725 nghìn giao dịch, tiếp theo là “chiều”, trong khi “đêm” có mức thấp nhất (~106 nghìn). Đáng chú ý, mô hình này được lặp lại gần như đồng nhất ở mọi ngày, kể cả cuối tuần.
Điều này củng cố kết quả từ biểu đồ tỷ trọng theo thứ, cho thấy tổng khối lượng giao dịch giữa các ngày không khác biệt đáng kể. Về ý nghĩa kinh tế, mô thức ổn định này phản ánh rằng phần lớn giao dịch mang tính thiết yếu và thường nhật (như mua thực phẩm, xăng dầu, thanh toán dịch vụ), thay vì các hoạt động tiêu dùng tùy hứng chỉ xuất hiện vào cuối tuần.
pt_city_year <- data_duong[, .N, by = .(merchant_city, year)][order(-N)][1:50]
pt_city_year[, year := as.integer(year)]
ggplot(pt_city_year, aes(x = factor(year), y = reorder(merchant_city, N),
size = N, color = year)) +
geom_point(alpha = 0.75) +
scale_size_continuous(range = c(3, 10), name = "Số lượng") +
scale_color_viridis_c(option = "plasma", name = "Năm", breaks = c(2010, 2013, 2016, 2019)) +
labs(title = "Tần suất giao dịch thành công theo thành phố và năm",
subtitle = "Mỗi điểm biểu thị tần suất giao dịch thành công tại một thành phố
trong năm tương ứng", x = "Năm", y = "Thành phố") +
theme_minimal(base_size = 12) +
theme(plot.title = element_text(hjust = 0.5, face = "bold", size = 14),
plot.subtitle = element_text(hjust = 0.5, size = 10, color = "gray40"),
axis.title = element_text(face = "bold"),
panel.grid.minor = element_blank(),
legend.position = "right",
legend.title = element_text(face = "bold"))
Giải thích
Đoạn mã này thống kê số lượng giao dịch thành công (.N) theo thành phố và năm, sau đó chọn ra 50 thành phố có tần suất giao dịch cao nhất. Cột “year” được ép về kiểu số nguyên để hiển thị chính xác trên biểu đồ.
Biểu đồ được xây dựng bằng “ggplot2”, trong đó trục tung thể hiện thành phố, trục hoành là năm, còn kích thước điểm biểu thị số lượng giao dịch. Thang màu Viridis (option = “plasma”) giúp phân biệt các năm, đồng thời đảm bảo độ tương phản tốt. Các tuỳ chỉnh trong theme_minimal() giúp biểu đồ trực quan, gọn gàng và dễ đọc.
Nhận xét
Biểu đồ bong bóng cho thấy sự chuyển dịch mạnh mẽ trong cấu trúc hoạt động giao dịch toàn hệ thống. Kênh “ONLINE” thể hiện sự thống trị tuyệt đối và tăng trưởng nhanh qua các năm. Trong khi đó, các trung tâm kinh tế truyền thống như Houston và Miami duy trì mức độ giao dịch ổn định, phản ánh vai trò bền vững của khu vực bán lẻ và dịch vụ trực tiếp. Ngược lại, một số thành phố khác biến động rõ rệt như Chicago hay Dallas, cho thấy sự tái cấu trúc không gian kinh tế, nơi các trung tâm mới nổi dần thay thế những khu vực suy giảm.
set.seed(123)
data_sample <- data_duong[sample(.N, 50000)]
data_sample[, weekday_type := ifelse(cuoi_tuan, 1, 0)]
data_day <- data_sample[, .(`Giá trị trung bình` = mean(amount, na.rm = TRUE),
`Số lượng giao dịch` = .N,`Loại ngày` = first(weekday_type),
`Tháng` = first(month)), by = date]
data_corr_subset <- data_day[, .(`Giá trị trung bình`, `Số lượng giao dịch`,
`Loại ngày`, `Tháng`)]
corr_matrix <- cor(data_corr_subset, use = "pairwise.complete.obs")
melted_corr <- melt(as.data.table(corr_matrix, keep.rownames = "Var1"),
variable.name = "Var2",value.name = "correlation")
ggplot(melted_corr, aes(x = Var1, y = Var2, fill = correlation)) +
geom_tile(color = "white") +
geom_text(aes(label = round(correlation, 2)), color = "black", size = 4) +
scale_fill_gradient2(low = "#E8D5DB", high = "#E85D5D", mid = "#609384",
midpoint = 0, limit = c(-1,1), name = "Hệ số tương quan") +
theme_minimal(base_size = 12) +
labs(title = "Ma trận tương quan: Giá trị trung bình, số lượng giao dịch,
tháng và loại ngày", x = "", y = "") +
theme(plot.title = element_text(hjust = 0.5, face = "bold"),
axis.text.x = element_text(angle = 45, vjust = 1, hjust = 1),
panel.grid = element_blank())
Giải thích
Đoạn mã trên thực hiện phân tích mối quan hệ giữa giá trị trung bình giao dịch, số lượng giao dịch, loại ngày và tháng. Dữ liệu được lấy mẫu 50.000 giao dịch để giảm khối lượng tính toán và tổng hợp theo ngày để tạo bảng cơ sở. Hàm cor() tính ma trận tương quan giữa các biến định lượng, với use = “pairwise.complete.obs” loại bỏ giá trị thiếu theo cặp. Ma trận sau đó được chuyển sang dạng dài bằng melt() để trực quan hóa. Cuối cùng, ggplot2 vẽ bản đồ nhiệt, thể hiện mức độ tương quan giữa các biến, giúp nhận diện nhanh các mối quan hệ trong dữ liệu.
Nhận xét
Biểu đồ thể hiện mức độ tương quan gần như bằng 0 giữa giá trị trung bình, số lượng giao dịch, tháng và loại ngày, cho thấy các biến này hầu như không đồng biến cùng nhau theo ngày. Giá trị trung bình và tháng hay số lượng giao dịch và loại ngày cũng không có mối liên hệ đáng kể, phản ánh rằng biến thời gian hoặc loại ngày không trực tiếp ảnh hưởng đến khối lượng và giá trị trung bình của giao dịch trong mẫu này. Điều này cho thấy xu hướng giao dịch ổn định qua thời gian và không bị chi phối mạnh bởi ngày trong tuần hay tháng. Tổng thể, ma trận tương quan này cho thấy không có mối quan hệ đơn giản giữa các biến số lượng/giá trị và các yếu tố thời gian trong dữ liệu hiện tại.
global_mean_amount <- mean(data_duong$amount, na.rm = TRUE)
year_summary <- data_duong[, .(mean_amount = mean(amount, na.rm = TRUE)),
by = year][order(year)]
p1 <- ggplot(year_summary, aes(x = factor(year), y = mean_amount)) +
geom_line(group = 1, color = "steelblue", linewidth = 1.2) +
geom_point(color = "steelblue", size = 2) +
geom_hline(yintercept = global_mean_amount, linetype = "dashed",
color = "red", linewidth = 0.6) +
annotate("text", x = Inf, y = global_mean_amount, label = "Trung bình",
color = "red", hjust = 2, vjust = -0.4, fontface = "italic", size = 2.8) +
labs(title = "Theo Năm", x = "Năm", y = "Giá trị trung bình") +
theme_minimal(base_size = 10) + theme(plot.title = element_text(hjust = 0.5))
month_summary <- data_duong[, .(mean_amount = mean(amount, na.rm = TRUE)),
by = month][order(month)]
p2 <- ggplot(month_summary, aes(x = factor(month), y = mean_amount)) +
geom_line(group = 1, color = "#FF9E6D", linewidth = 1.2) +
geom_point(color = "#FF9E6D", size = 2) +
geom_hline(yintercept = global_mean_amount, linetype = "dashed",
color = "red", linewidth = 0.6) +
annotate("text", x = Inf, y = global_mean_amount, label = "Trung bình",
color = "red", hjust = 1.05, vjust = -0.4, fontface = "italic", size = 2.8) +
labs(title = "Theo Tháng", x = "Tháng", y = NULL) + theme_minimal(base_size = 10) +
theme(plot.title = element_text(hjust = 0.5))
weekday_summary <- data_duong[, .(mean_amount = mean(amount, na.rm = TRUE)),
by = weekday][
order(factor(weekday, levels = c("Thứ hai", "Thứ ba", "Thứ tư", "Thứ năm",
"Thứ sáu", "Thứ bảy", "Chủ nhật")))]
p3 <- ggplot(weekday_summary, aes(x = weekday, y = mean_amount)) +
geom_line(group = 1, color = "#FFD166", linewidth = 1.2) +
geom_point(color = "#FFD166", size = 2) +
geom_hline(yintercept = global_mean_amount, linetype = "dashed",
color = "red", linewidth = 0.6) +
annotate("text", x = Inf, y = global_mean_amount, label = "Trung bình",
color = "red", hjust = 1.0, vjust = -0.4, fontface = "italic", size = 2.8) +
labs(title = "Theo Thứ", x = "Thứ", y = NULL) + theme_minimal(base_size = 10) +
theme(plot.title = element_text(hjust = 0.5), axis.text.x = element_text(
angle = 30, hjust = 1))
hour_summary <- data_duong[, .(mean_amount = mean(amount, na.rm = TRUE)),
by = hour][order(hour)]
p4 <- ggplot(hour_summary, aes(x = factor(hour), y = mean_amount)) +
geom_line(group = 1, color = "#6D597A", linewidth = 1.2) +
geom_point(color = "#6D597A", size = 1.5) +
geom_hline(yintercept = global_mean_amount, linetype = "dashed",
color = "red", linewidth = 0.6) +
annotate("text", x = Inf, y = global_mean_amount, label = "Trung bình",
color = "red", hjust = 1.05, vjust = -0.4, fontface = "italic", size = 2.8) +
scale_x_discrete(breaks = c("0","4","8","12","16","20","23")) +
labs(title = "Theo Giờ", x = "Giờ", y = NULL) + theme_minimal(base_size = 10) +
theme(plot.title = element_text(hjust = 0.5))
final_plot <- (p1 + p2) / (p3 + p4) + plot_annotation(
title = "Giá trị trung bình giao dịch theo các yếu tố thời gian",
theme = theme(plot.title = element_text(hjust = 0.5, face = "bold", size = 14)))
final_plot
Giải thích
Đoạn code tổng hợp dữ liệu theo năm, tháng, thứ và giờ, tính giá trị trung bình (mean_amount) và sắp xếp thứ tự logic; mỗi biểu đồ dùng geom_line và geom_point thể hiện xu hướng, geom_hline và annotate minh họa giá trị trung bình chung. Trục x giờ được chỉnh bằng scale_x_discrete để hiển thị các mốc quan trọng, tránh xung đột với factor, trong khi theme_minimal và theme tối ưu hiển thị và căn tiêu đề. Cuối cùng, các biểu đồ được ghép bằng patchwork với plot_annotation tạo tiêu đề tổng quan, trình bày đầy đủ xu hướng giao dịch theo thời gian.
Nhận xét
Biểu đồ cho thấy giá trị trung bình giao dịch có sự biến động theo các yếu tố thời gian. Theo năm, giá trị trung bình giảm dần từ 2010 đến 2019, phản ánh xu hướng bình quân giao dịch có thể ổn định hoặc giảm do sự trưởng thành của thị trường. Theo tháng, giao dịch đạt đỉnh vào tháng 6–7, gợi ý mùa vụ hoặc các dịp kích cầu tiêu dùng tác động đến giá trị giao dịch. Theo thứ, mức trung bình thấp nhất rơi vào thứ năm và thứ sáu, trong khi cuối tuần cao hơn, chỉ ra hành vi khách hàng gia tăng vào cuối tuần. Theo giờ, giao dịch thấp vào buổi sáng sớm, tăng mạnh vào các giờ cao điểm (chiều tối), phản ánh nhịp sinh hoạt kinh tế và thói quen sử dụng dịch vụ của khách hàng.
Bộ dữ liệu được sử dụng trong phần này là báo cáo tài chính hợp nhất của Công ty Cổ phần Camimex Group (mã chứng khoán: CMX), cụ thể là trong bảng cân đối kế toán của doanh nghiệp được thu thập theo quý trong giai đoạn từ năm 2015 đến năm 2024.
Nguồn dữ liệu được trích xuất từ báo cáo tài chính công bố công khai của doanh nghiệp trên Sở giao dịch chứng khoán TP. Hồ Chí Minh (HOSE). Bộ dữ liệu bao gồm thông tin về cơ cấu tài sản và nguồn vốn của công ty, với trọng tâm là hai nhóm chính:
Bộ dữ liệu này là cơ sở cho việc phân tích biến động cơ cấu tài sản và nguồn vốn, cũng như đánh giá hiệu quả hoạt động tài chính của doanh nghiệp theo từng quý.
library(readxl)
library(dplyr)
library(ggplot2)
library(tibble)
library(moments)
library(tidyr)
library(ggrepel)
library(scales)
library(corrplot)
library(patchwork)
library(gridExtra)
library(reshape2)
library(ggdist)
library(knitr)
library(kableExtra)
Ở đoạn lệnh này, tác giả đã sử dụng hàm library với cú pháp như sau library(package) với package là một gói được nạp vào Rstudio để thực hiện các mục đích liên quan đến tải dữ liệu, phân tích, thống kê, trực quan hóa…
Ngoài ra, tác giả còn tải các gói cần thiết phục vụ cho nội dung phân tích bên dưới. Cụ thể như sau:
data <- read_excel(file.choose())
Ở dòng lệnh này, tác giả dùng lệnh read_excel để tải file dữ liệu vào R. Cú pháp như sau read_excel(path) với path là tham số chính dùng để chọn file cần tải vào R. Ngoài ra tác giả còn sử dụng lệnh file.choose() để chọn được file CMX11.xlsx - file dữ liệu mà tác giả mong muốn.
Sau khi đã tải được file CMX11.xlsx vào R, tác giả gán bộ dữ liệu này vào biến data để thuận thiện phân tích.
class(data)
## [1] "tbl_df" "tbl" "data.frame"
Ở dòng lệnh này, tác giả dùng hàm class để kiểm tra bộ dữ liệu có kiểu dữ liệu là gì. Cú pháp như sau: class(x) với x là một dữ liệu bất kỳ.
Kết quả cho thấy bộ dữ liệu mà nhóm tác giả đang nghiên cứu là dataframe.
dim(data)
## [1] 119 41
Ở dòng lệnh này, tác giả dùng lệnh dim để xác định số biến và số quan sát. Cú pháp sau như sau dim(x), với x là matrix, array hoặc là data frame. Kết quả cho thấy bộ dữ liệu có 119 dòng và 41 cột.
colnames(data)
## [1] "Tiêu chí" "2015_Q1" "2015_Q2" "2015_Q3" "2015_Q4"
## [6] "2016_Q1" "2016_Q2" "2016_Q3" "2016_Q4" "2017_Q1"
## [11] "2017_Q2" "2017_Q3" "2017_Q4" "2018_Q1" "2018_Q2"
## [16] "2018_Q3" "2018_Q4" "2019_Q1" "2019_Q2" "2019_Q3"
## [21] "2019_Q4" "2020_Q1" "2020_Q2" "2020_Q3" "2020_Q4"
## [26] "2021_Q1" "2021_Q2" "2021_Q3" "2021_Q4" "2022_Q1"
## [31] "2022_Q2" "2022_Q3" "2022_Q4" "2023_Q1" "2023_Q2"
## [36] "2023_Q3" "2023_Q4" "2024_Q1" "2024_Q2" "2024_Q3"
## [41] "2024_Q4"
Ở dòng lệnh này, tác giả dùng lệnh colnames để xác định các biến của từng cột. Cú pháp như sau colnames(x) với x là dataframe, matrix hoặc array. Kết quả cho thấy có 41 biến là “Tiêu chí” và các quý từ quý 1 năm 2015 đến quý 4 năm 2024 (gồm 40 biến)
Tuy nhiên từ đây mà khẳng định dựa trên số cột khẳng định số biến và số quan sát từ số cột thì chưa chính xác. Bởi vì dữ liệu của tác giả được trình bày theo bảng cân đối kế toán, nên các hàng là giá trị của các biến như tài sản, tài sản ngắn hạn, tài sản dài hạn, nguồn vốn… còn ở các cột là các năm.
kable(data[1:6, 1:6], booktabs = FALSE,linesep = "",align = "c",escape = FALSE) %>%
kable_styling(latex_options = "striped",font_size = 8)
| Tiêu chí | 2015_Q1 | 2015_Q2 | 2015_Q3 | 2015_Q4 | 2016_Q1 |
|---|---|---|---|---|---|
| TÀI SẢN | NA | NA | NA | NA | NA |
| A- TÀI SẢN NGẮN HẠN | 457530508816 | 455477073220 | 496604609235 | 505064327772 | 531545294814 |
| I. Tiền và các khoản tương đương tiền | 5402139458 | 7363850792 | 8178935850 | 4319012577 | 9260939793 |
|
5402139458 | 7363850792 | 8178935850 | 4319012577 | 9260939793 |
|
0 | NA | NA | NA | NA |
|
0 | NA | NA | NA | NA |
Ở dòng lệnh này, tác giả sử dụng lệnh kable kết hợp với hàm kable_styling để kiểm tra cấu trúc của 6 dòng đầu và 6 cột đầu của bộ dữ liệu.
any(is.na(data))
## [1] TRUE
Ở câu lệnh này, tác giả dùng hàm any kết hợp với hàm is.na để xác định số dữ liệu bị thiếu của từng cột. Cú pháp như sau:
Kết quả cho thấy bộ dữ liệu có ít nhất một giá trị bị thiếu.
colSums(is.na(data))
## Tiêu chí 2015_Q1 2015_Q2 2015_Q3 2015_Q4 2016_Q1
## 0 60 63 66 69 66
## 2016_Q2 2016_Q3 2016_Q4 2017_Q1 2017_Q2 2017_Q3
## 65 67 67 66 66 66
## 2017_Q4 2018_Q1 2018_Q2 2018_Q3 2018_Q4 2019_Q1
## 66 65 64 66 66 63
## 2019_Q2 2019_Q3 2019_Q4 2020_Q1 2020_Q2 2020_Q3
## 57 55 54 54 53 52
## 2020_Q4 2021_Q1 2021_Q2 2021_Q3 2021_Q4 2022_Q1
## 53 53 54 56 55 56
## 2022_Q2 2022_Q3 2022_Q4 2023_Q1 2023_Q2 2023_Q3
## 54 53 53 54 54 56
## 2023_Q4 2024_Q1 2024_Q2 2024_Q3 2024_Q4
## 57 57 56 54 55
Ở câu lệnh này, tác giả dùng hàm colSums kết hợp với hàm is.na để xác định số dữ liệu bị thiếu của từng cột. Cú pháp như sau:
Kết quả cho thấy, ở các cột theo quý số lượng giá trị bị thiếu dao động từ 52 - 69. Điều này được lý giải vì có nhiều hàng bị bỏ trống (giá trị tại đó bằng 0), nhưng trong quá trình thu thập dữ liệu dẫn đến sai sót này. Ở vấn đề này tác giả sẽ khắc phục ở phần 2:Xử lý dữ liệu thô, mã hóa dữ liệu.
head(unique(data$`Tiêu chí`), n =10)
## [1] "TÀI SẢN"
## [2] "A- TÀI SẢN NGẮN HẠN"
## [3] "I. Tiền và các khoản tương đương tiền"
## [4] "1. Tiền"
## [5] "2. Các khoản tương đương tiền"
## [6] "II. Các khoản đầu tư tài chính ngắn hạn"
## [7] "1. Chứng khoán kinh doanh"
## [8] "2. Dự phòng giảm giá chứng khoán kinh doanh"
## [9] "3. Đầu tư nắm giữ đến ngày đáo hạn"
## [10] "III. Các khoản phải thu ngắn hạn"
Ở dòng lệnh này, tác giả dùng hàm head kết hợp hàm unique để xem thông tin 10 biến của cột Tiêu chí, chỉ xuất hiện những phần tử duy nhất, không hiển thị phần tử trùng lặp. Cú pháp như sau unique(x) với x là vecto, matrix, dataframe.
any(duplicated(data))
## [1] FALSE
Ở câu lệnh này, tác giả dùng hàm any kết hợp với hàm duplicated để xác định bộ dữ liệu có dòng nào bị trùng lặp theo hàng hay không. Cú pháp như sau:
Kết quả cho thấy không có hàng nào bị trùng lắp hoàn toàn. Bởi vì dữ liệu của tác giả được trình bày như Bảng cân đối kế toán, do đó trùng lặp theo hàng là không thể xảy ra.
Bộ dữ liệu được sử dụng trong phần này là báo cáo tài chính hợp nhất của Công ty Cổ phần Camimex Group (mã chứng khoán: CMX), cụ thể là trong bảng cân đối kế toán của doanh nghiệp được thu thập theo quý trong giai đoạn từ năm 2015 đến năm 2024.
Nguồn dữ liệu được trích xuất từ báo cáo tài chính công bố công khai của doanh nghiệp trên Sở giao dịch chứng khoán TP. Hồ Chí Minh (HOSE). Bộ dữ liệu bao gồm thông tin về cơ cấu tài sản và nguồn vốn của công ty, với trọng tâm là hai nhóm chính:
Bộ dữ liệu này là cơ sở cho việc phân tích biến động cơ cấu tài sản và nguồn vốn, cũng như đánh giá hiệu quả hoạt động tài chính của doanh nghiệp theo từng quý.
library(readxl)
library(dplyr)
library(ggplot2)
library(tibble)
library(moments)
library(tidyr)
library(ggrepel)
library(scales)
library(corrplot)
library(patchwork)
library(gridExtra)
library(reshape2)
library(ggdist)
library(knitr)
library(kableExtra)
Ở đoạn lệnh này, tác giả đã sử dụng hàm library với cú pháp như sau library(package) với package là một gói được nạp vào Rstudio để thực hiện các mục đích liên quan đến tải dữ liệu, phân tích, thống kê, trực quan hóa…
Ngoài ra, tác giả còn tải các gói cần thiết phục vụ cho nội dung phân tích bên dưới. Cụ thể như sau:
data <- read_excel(file.choose())
Ở dòng lệnh này, tác giả dùng lệnh read_excel để tải file dữ liệu vào R. Cú pháp như sau read_excel(path) với path là tham số chính dùng để chọn file cần tải vào R. Ngoài ra tác giả còn sử dụng lệnh file.choose() để chọn được file CMX11.xlsx - file dữ liệu mà tác giả mong muốn.
Sau khi đã tải được file CMX11.xlsx vào R, tác giả gán bộ dữ liệu này vào biến data để thuận thiện phân tích.
class(data)
## [1] "tbl_df" "tbl" "data.frame"
Ở dòng lệnh này, tác giả dùng hàm class để kiểm tra bộ dữ liệu có kiểu dữ liệu là gì. Cú pháp như sau: class(x) với x là một dữ liệu bất kỳ.
Kết quả cho thấy bộ dữ liệu mà nhóm tác giả đang nghiên cứu là dataframe.
dim(data)
## [1] 119 41
Ở dòng lệnh này, tác giả dùng lệnh dim để xác định số biến và số quan sát. Cú pháp sau như sau dim(x), với x là matrix, array hoặc là data frame. Kết quả cho thấy bộ dữ liệu có 119 dòng và 41 cột.
colnames(data)
## [1] "Tiêu chí" "2015_Q1" "2015_Q2" "2015_Q3" "2015_Q4"
## [6] "2016_Q1" "2016_Q2" "2016_Q3" "2016_Q4" "2017_Q1"
## [11] "2017_Q2" "2017_Q3" "2017_Q4" "2018_Q1" "2018_Q2"
## [16] "2018_Q3" "2018_Q4" "2019_Q1" "2019_Q2" "2019_Q3"
## [21] "2019_Q4" "2020_Q1" "2020_Q2" "2020_Q3" "2020_Q4"
## [26] "2021_Q1" "2021_Q2" "2021_Q3" "2021_Q4" "2022_Q1"
## [31] "2022_Q2" "2022_Q3" "2022_Q4" "2023_Q1" "2023_Q2"
## [36] "2023_Q3" "2023_Q4" "2024_Q1" "2024_Q2" "2024_Q3"
## [41] "2024_Q4"
Ở dòng lệnh này, tác giả dùng lệnh colnames để xác định các biến của từng cột. Cú pháp như sau colnames(x) với x là dataframe, matrix hoặc array. Kết quả cho thấy có 41 biến là “Tiêu chí” và các quý từ quý 1 năm 2015 đến quý 4 năm 2024 (gồm 40 biến)
Tuy nhiên từ đây mà khẳng định dựa trên số cột khẳng định số biến và số quan sát từ số cột thì chưa chính xác. Bởi vì dữ liệu của tác giả được trình bày theo bảng cân đối kế toán, nên các hàng là giá trị của các biến như tài sản, tài sản ngắn hạn, tài sản dài hạn, nguồn vốn… còn ở các cột là các quý.
data %>% head(5) %>% select(1:5) %>%
mutate(across(where(is.numeric),
~ scales::comma(., accuracy = 1, big.mark = ".", decimal.mark = ","))) %>%
kable(caption = "5 dòng và 5 cột đầu tiên của bộ dữ liệu.",
booktabs = TRUE,
format = "latex") %>%
kable_styling(latex_options = c("striped", "scale_down", "hold_position"),
font_size = 9, full_width = FALSE, position = "center")
Ở dòng lệnh này, hàm head(5) giới hạn dữ liệu ở 5 dòng đầu tiên, còn select(1:5) lựa chọn 5 cột đầu tiên theo thứ tự xuất hiện trong bộ dữ liệu.
Hàm mutate(across(where(is.numeric), ~ scales::comma(…))) được dùng để định dạng các biến số học bằng cách chèn dấu phân tách hàng nghìn (“.”) và dấu thập phân (“,”) nhằm tăng tính trực quan. Hàm kable() tạo bảng hiển thị dưới định dạng LaTeX với chú thích, và kable_styling() điều chỉnh phong cách bảng
data %>%
tail(5) %>% select(1:5) %>% mutate(across(where(is.numeric),
~ scales::comma(., accuracy = 1, big.mark = ".", decimal.mark = ","))) %>%
kable(caption = "5 dòng và 5 cột cuối cùng của bộ dữ liệu.",format = "latex",
booktabs = TRUE) %>%
kable_styling(latex_options = c("striped", "scale_down", "hold_position"),
font_size = 9,
position = "center")
Ở dòng lệnh này, hàm tail(5) giới hạn dữ liệu ở 5 dòng cuối cùng, còn select(1:5) lựa chọn 5 cột cuối cùng theo thứ tự trong bộ dữ liệu.
str(data)
## tibble [119 × 41] (S3: tbl_df/tbl/data.frame)
## $ Tiêu chí: chr [1:119] "TÀI SẢN" "A- TÀI SẢN NGẮN HẠN" "I. Tiền và các khoản tương đương tiền" "1. Tiền" ...
## $ 2015_Q1 : num [1:119] NA 4.58e+11 5.40e+09 5.40e+09 0.00 ...
## $ 2015_Q2 : num [1:119] NA 4.55e+11 7.36e+09 7.36e+09 NA ...
## $ 2015_Q3 : num [1:119] NA 4.97e+11 8.18e+09 8.18e+09 NA ...
## $ 2015_Q4 : num [1:119] NA 5.05e+11 4.32e+09 4.32e+09 NA ...
## $ 2016_Q1 : num [1:119] NA 5.32e+11 9.26e+09 9.26e+09 NA ...
## $ 2016_Q2 : num [1:119] NA 5.18e+11 6.28e+09 6.28e+09 NA ...
## $ 2016_Q3 : num [1:119] NA 4.96e+11 6.15e+09 6.15e+09 NA ...
## $ 2016_Q4 : num [1:119] NA 4.36e+11 1.00e+10 1.00e+10 NA ...
## $ 2017_Q1 : num [1:119] NA 4.65e+11 6.33e+09 6.33e+09 NA ...
## $ 2017_Q2 : num [1:119] NA 4.51e+11 6.16e+09 6.16e+09 NA ...
## $ 2017_Q3 : num [1:119] NA 4.53e+11 3.69e+09 3.69e+09 NA ...
## $ 2017_Q4 : num [1:119] NA 4.85e+11 8.22e+09 8.22e+09 NA ...
## $ 2018_Q1 : num [1:119] NA 5.1e+11 4.4e+09 4.4e+09 NA ...
## $ 2018_Q2 : num [1:119] NA 5.16e+11 5.61e+09 5.61e+09 NA ...
## $ 2018_Q3 : num [1:119] NA 5.92e+11 6.47e+09 6.47e+09 NA ...
## $ 2018_Q4 : num [1:119] NA 6.02e+11 3.61e+09 3.61e+09 NA ...
## $ 2019_Q1 : num [1:119] NA 7.25e+11 1.00e+10 1.00e+10 NA ...
## $ 2019_Q2 : num [1:119] NA 8.37e+11 1.54e+10 1.54e+10 NA ...
## $ 2019_Q3 : num [1:119] NA 8.63e+11 2.99e+10 2.99e+10 NA ...
## $ 2019_Q4 : num [1:119] NA 9.73e+11 3.97e+10 2.81e+10 1.15e+10 ...
## $ 2020_Q1 : num [1:119] NA 1.13e+12 1.56e+10 4.63e+09 1.10e+10 ...
## $ 2020_Q2 : num [1:119] NA 9.21e+11 3.38e+10 3.38e+10 NA ...
## $ 2020_Q3 : num [1:119] NA 1.01e+12 3.53e+10 3.49e+10 4.70e+08 ...
## $ 2020_Q4 : num [1:119] NA 8.88e+11 6.28e+09 6.28e+09 NA ...
## $ 2021_Q1 : num [1:119] NA 1.06e+12 1.56e+10 5.62e+09 1.00e+10 ...
## $ 2021_Q2 : num [1:119] NA 1.03e+12 2.32e+09 2.32e+09 NA ...
## $ 2021_Q3 : num [1:119] NA 1.28e+12 1.40e+10 1.40e+10 NA ...
## $ 2021_Q4 : num [1:119] NA 1.53e+12 2.68e+10 2.68e+10 NA ...
## $ 2022_Q1 : num [1:119] NA 1.63e+12 1.60e+10 1.60e+10 NA ...
## $ 2022_Q2 : num [1:119] NA 2.14e+12 3.36e+10 3.36e+10 NA ...
## $ 2022_Q3 : num [1:119] NA 2.20e+12 5.11e+10 5.11e+10 NA ...
## $ 2022_Q4 : num [1:119] NA 2.16e+12 2.67e+10 2.67e+10 NA ...
## $ 2023_Q1 : num [1:119] NA 2.14e+12 2.68e+10 2.68e+10 NA ...
## $ 2023_Q2 : num [1:119] NA 2.27e+12 2.83e+10 2.83e+10 NA ...
## $ 2023_Q3 : num [1:119] NA 2.54e+12 2.74e+10 2.74e+10 NA ...
## $ 2023_Q4 : num [1:119] NA 2.36e+12 3.21e+10 3.21e+10 NA ...
## $ 2024_Q1 : num [1:119] NA 2.18e+12 2.92e+10 2.92e+10 NA ...
## $ 2024_Q2 : num [1:119] NA 2.24e+12 2.89e+10 2.89e+10 NA ...
## $ 2024_Q3 : num [1:119] NA 2.29e+12 2.57e+10 2.57e+10 NA ...
## $ 2024_Q4 : num [1:119] NA 2.82e+12 1.66e+11 1.66e+11 NA ...
Hàm str(data) trong R được dùng để mô tả cấu trúc tổng thể của đối tượng dữ liệu, giúp nắm nhanh loại dữ liệu, số lượng quan sát, số biến, cũng như kiểu dữ liệu của từng biến. Trong trường hợp này, có 119 hàng và 41 cột
Cột đầu tiên, “Tiêu chí” thuộc kiểu chuỗi ký tự (character), chứa tên hoặc mô tả các hạng mục tài chính. Các cột còn lại thuộc kiểu số thực (numeric) và thể hiện các giá trị định lượng theo quý từ năm 2015 đến 2024. Ký hiệu NA biểu thị giá trị bị thiếu (missing value)
head(unique(data$`Tiêu chí`), n =10)
## [1] "TÀI SẢN"
## [2] "A- TÀI SẢN NGẮN HẠN"
## [3] "I. Tiền và các khoản tương đương tiền"
## [4] "1. Tiền"
## [5] "2. Các khoản tương đương tiền"
## [6] "II. Các khoản đầu tư tài chính ngắn hạn"
## [7] "1. Chứng khoán kinh doanh"
## [8] "2. Dự phòng giảm giá chứng khoán kinh doanh"
## [9] "3. Đầu tư nắm giữ đến ngày đáo hạn"
## [10] "III. Các khoản phải thu ngắn hạn"
Hàm head kết hợp hàm unique để xem thông tin 10 biến của cột Tiêu chí, chỉ xuất hiện những phần tử duy nhất, không hiển thị phần tử trùng lặp. Cú pháp như sau unique(x) với x là vecto, matrix, dataframe.
any(duplicated(data))
## [1] FALSE
Hàm any kết hợp với hàm duplicated để xác định bộ dữ liệu có dòng nào bị trùng lặp theo hàng hay không. Cú pháp như sau:
Kết quả cho thấy không có hàng nào bị trùng lắp hoàn toàn. Bởi vì dữ liệu của tác giả được trình bày như bảng cân đối kế toán, do đó trùng lặp theo hàng là không thể xảy ra.
Như nãy tác giả đã đề cập, dữ liệu của tác giả được trình bày theo bảng cân đối kế toán, nên các hàng là giá trị của các biến còn ở các cột là các quý. Do đó để thuận lợi phân tích, tác giả sẽ chuyển hàng thành cột.
data1 <- as.matrix(data)
data1 <- t(data1)
data <- as.data.frame(data1)
colnames(data) <- data[1,]
data <- data[-1,]
data[] <- lapply(data, function(x) as.numeric(x))
Ở câu lệnh này tác giả sử dụng hàm as.matrix ,t ,colnames , function, as.numeric và lapply để chuyển hàng thành cột, đồng thời dữ liệu về dạng số. Cú pháp như sau:
Với tính chất chuyển vị của ma trận, tác giả đã chuyển đổi hàng thành cột. Sau đó chuyển matrix thành dataframe. Tuy nhiên sau khi chuyển đổi thì tên các cột lại để trống nên tác giả đã chuyển dòng 1 của dữ liệu thành tên các cột, đồng thời xóa dòng 1 này để được định dạng dữ liệu như tác giả mong muốn.
Mặt khác khi tác giả cố đưa data về dạng ma trận thì tác giả đã vô tình đưa kiểu dữ liệu của bộ dữ liệu về dạng character. Vì vậy tác giả dùng hàm lappy kết hợp hàm as.numeric để đưa dữ liệu trong biến data về dạng số, thuận tiện để phân tích.
d1 <- colSums(is.na(data))
head(d1)
## TÀI SẢN
## 40
## A- TÀI SẢN NGẮN HẠN
## 0
## I. Tiền và các khoản tương đương tiền
## 0
## 1. Tiền
## 0
## 2. Các khoản tương đương tiền
## 35
## II. Các khoản đầu tư tài chính ngắn hạn
## 23
Hàm colSums kết hợp với hàm is.na để xác định số dữ liệu bị thiếu của từng cột. Cú pháp như sau:
Kết quả cho thấy có các cột sau có số lượng giá trị bị thiếu nhiều như bằng 40 (tương ứng với toàn bộ giá trị theo quý của biến này đều bỏ trống) hoặc lớn hơn 20, ví dụ như TÀI SẢN, Chứng khoán kinh doanh …
data2 <- data[,d1 < 21]
Ở dòng lệnh này gán biến data2, tác giả đã lọc, chỉ giữ lại các biến có số lượng giá trị bị thiếu nhỏ hơn 9, nghĩa các biến có các giá trị bị thiếu từ 9 đến 10 thì tác giả sẽ bỏ đi.
Đối với các biến này, tác giả quyết định sẽ lấy giá trị trung bình của các giá trị còn lại và thay vào vị trí các giá trị bị thiếu.
data3 <- data2
for (i in names(data3)) {
data3[[i]][is.na(data3[[i]])] <- mean(data3[[i]], na.rm = TRUE)}
Ở dòng lệnh này tác giả sử dụng hàm for kết hợp hàm names, is.na và hàm mean. Cú pháp như sau:
Vòng lặp này có ý nghĩa rằng lấy từng cột của data3, kiểm tra xem trong từng cột này có giá trị nào bị thiếu hay không, nếu không có thì bỏ qua, nếu có thì sẽ lấy trung bình của cột đó (tính luôn vị trí các giá trị bị thiếu) sau đó thay vào vị trí các giá trị bị thiếu này.
Trước khi tiến hành các bước xử lý và phân tích dữ liệu, tác giả cần thực hiện thao tác chuẩn hóa cấu trúc bảng dữ liệu. Trong tập dữ liệu ban đầu, các thời điểm quan sát (theo quý và năm) được lưu dưới dạng tên hàng thay vì là một cột thông thường
Điều này gây khó khăn cho việc trích lọc, tổng hợp và trực quan hóa dữ liệu theo thời gian. Do đó, tác giả tiến hành chuyển phần tên hàng thành một cột mới có tên là Thoi_gian, sau đó tách riêng phần năm và phần quý để thuận tiện cho việc thống kê, so sánh và biểu diễn xu hướng theo từng giai đoạn.
data3 <- data3 %>%
rownames_to_column(var = "Thoi_gian") %>%
mutate(Nam = sub("_Q\\d", "", Thoi_gian),Quy = sub(".*_Q", "", Thoi_gian))
Hàm rownames_to_column() từ gói tibble để chuyển phần tên hàng của bảng dữ liệu “data3” thành một cột dữ liệu mới có tên là Thoi_gian sau đó tác giả dùng hàm mutate tạo một dataframe kết hợp hàm sub để tạo thêm hai cột mới Quy và Nam từ cột Thoi_gian.
Kết quả ở cột Nam là giá trị phần năm, ví dụ 2019_Q3 được chuyển thành 2019 và kết quả ở cột Quy thu được là phần quý, ví dụ 2019_Q3 được chuyển thành 3.
Sau khi đã xử lý các giá trị bị thiếu, tác giả sẽ tiến hành mã hóa dữ liệu theo khả năng thanh toán hiện thời (CR), khả năng thanh toán tức thời (QR), tỷ lệ nợ (DR), tỷ lệ nợ trên vốn chủ sở hữu (D/E), tỷ lệ vốn chủ sở hữu (ER), tỷ lệ nợ dài hạn (LDR), vốn lưu động ròng (NWC), tỷ lệ tài sản cố định (FAR), tỷ lệ hàng tồn kho (IR) và tỷ suất sinh lời trên tài sản (ROA).
data3 <- data3 %>%
mutate( WCR = `A- TÀI SẢN NGẮN HẠN` / `II.Tài sản cố định`,
LM = (`A- TÀI SẢN NGẮN HẠN` - `I. Nợ ngắn hạn`) / `TỔNG CỘNG TÀI SẢN`,
LDE = `II. Nợ dài hạn` / `D.VỐN CHỦ SỞ HỮU`,
ETL = `D.VỐN CHỦ SỞ HỮU` / `C. NỢ PHẢI TRẢ`,
CDC = `11. Lợi nhuận sau thuế chưa phân phối` / `I. Nợ ngắn hạn`,
LDTA = `II. Nợ dài hạn` / `TỔNG CỘNG TÀI SẢN`,
NCAR = (`A- TÀI SẢN NGẮN HẠN` - `I. Nợ ngắn hạn`) / `D.VỐN CHỦ SỞ HỮU`,
FAE = `II.Tài sản cố định` / `D.VỐN CHỦ SỞ HỮU`,
IRE = `IV. Hàng tồn kho` / `D.VỐN CHỦ SỞ HỮU`,
ROEA = `11. Lợi nhuận sau thuế chưa phân phối` / (`TỔNG CỘNG TÀI SẢN`
+ `D.VỐN CHỦ SỞ HỮU`)
)
Ở các dòng lệnh trên, tác giả đã tạo các cột mới liên quan đến các biến mà tác giả muốn phân tích ở phần sau. Sau đó thức hiện công thức tính toán như sau:
Trong quá trình phân tổ dữ liệu, tác giả sử dụng hàm ifelse() trong R để phân loại các chỉ số tài chính thành các mức đánh giá cụ thể. Cú pháp chung như sau: data3$ “Tên cột mức độ” <- ifelse(Điều kiện, Giá trị 1, Giá trị 0)
Phương pháp này được áp dụng đồng nhất cho tất cả các phân tổ thanh toán, mỗi phân tổ được xác định dựa trên ngưỡng cụ thể nhằm thể hiện mức độ an toàn hoặc rủi ro trong thanh toán.
data3$Muc_do_WCR <- ifelse (data3$WCR >= 0.5,1,0)
Trong phân tích cấu trúc tài sản, chỉ số tỷ lệ vốn lưu động (WCR) được sử dụng để đánh giá mức độ linh hoạt của tài sản ngắn hạn so với tài sản cố định:
data3$Muc_do_LM <- ifelse(data3$LM >= 0.1, 1, 0)
Trong phân tích thanh khoản tổng thể, chỉ số biên thanh khoản (LM) phản ánh khả năng dư thừa thanh khoản trên tổng tài sản:
data3$Muc_do_LDE <- ifelse(data3$LDE <= 1.5, 1, 0)
Chỉ số tỷ lệ nợ dài hạn trên vốn chủ sở hữu (LDE) phản ánh mức độ rủi ro tài chính dài hạn:
data3$Muc_do_ETL <- ifelse(data3$ETL >= 0.4, 1, 0)
Chỉ số vốn chủ sở hữu trên tổng nợ (ETL) dùng để đánh giá mức độ bảo vệ của vốn chủ sở hữu:
data3$Muc_do_CDC <- ifelse(data3$CDC >= 0.2, 1, 0)
Chỉ số khả năng trả nợ ngắn hạn từ lợi nhuận (CDC) phản ánh khả năng sử dụng lợi nhuận để thanh toán nợ ngắn hạn:
data3$Muc_do_LDTA <- ifelse(data3$LDTA <= 0.4, 1, 0)
Chỉ số tỷ lệ nợ dài hạn trên tổng tài sản (LDTA) đánh giá mức độ phụ thuộc vào nợ dài hạn:
data3$Muc_do_NCAR <- ifelse(data3$NCAR >= 0.3, 1, 0)
Chỉ số tỷ lệ vốn lưu động ròng trên vốn chủ sở hữu (NCAR) phản ánh khả năng tự tài trợ từ vốn chủ sở hữu:
data3$Muc_do_FAE <- ifelse(data3$FAE <= 2, 1, 0)
Chỉ số tỷ lệ tài sản cố định trên vốn chủ sở hữu (FAE) phản ánh mức độ tài trợ của vốn chủ sở hữu cho tài sản cố định:
data3$Muc_do_IRE <- ifelse(data3$IRE <= 0.5, 1, 0)
Chỉ số tỷ lệ hàng tồn kho trên vốn chủ sở hữu (IRE) đánh giá mức độ phụ thuộc vốn chủ sở hữu vào hàng tồn kho:
data3$Muc_do_ROEA <- ifelse(data3$ROEA >= 0.05, 1, 0)
Chỉ số tỷ suất sinh lời trên tài sản và vốn chủ sở hữu (ROEA) phản ánh hiệu quả sử dụng vốn tổng hợp:
Để tiến hành phân tổ các chỉ tiêu kinh tế theo quý, tác giả sử dụng cú pháp lọc và trích chọn dữ liệu trong R nhằm tách riêng từng nhóm giá trị theo mức độ (0 hoặc 1) của các biến đã được phân loại trước đó. Cụ thể:
Nhờ cấu trúc này, mỗi chỉ tiêu được chia thành hai nhóm dữ liệu con tương ứng với mức độ thấp và cao theo quý, qua đó hỗ trợ việc quan sát, so sánh và đánh giá xu hướng biến động của từng chỉ tiêu trong các giai đoạn khác nhau.
data_WCR0 <- data3 %>% filter(Muc_do_WCR == 0) %>% select(Muc_do_WCR, `Quy`)
data_WCR1 <- data3 %>% filter(Muc_do_WCR == 1) %>% select(Muc_do_WCR, `Quy`)
Bảng data_WCR0 cho thấy các doanh nghiệp có vốn lưu động thấp, phản ánh mức độ linh hoạt tài chính hạn chế theo từng quý, trong khi bảng data_WCR1 hiển thị các doanh nghiệp duy trì vốn lưu động hợp lý, thuận lợi cho hoạt động thanh toán và quản trị ngắn hạn.
data_LM0 <- data3 %>% filter(Muc_do_LM == 0) %>% select(Muc_do_LM, `Quy`)
data_LM1 <- data3 %>% filter(Muc_do_LM == 1) %>% select(Muc_do_LM, `Quy`)
data_LM0 cho thấy doanh nghiệp gặp hạn chế trong khả năng dư thừa thanh khoản, còn data_LM1 thể hiện doanh nghiệp có thanh khoản dư thừa, giúp đánh giá khả năng chống chịu và lập kế hoạch tài chính theo quý.
data_LDE0 <- data3 %>% filter(Muc_do_LDE == 0) %>% select(Muc_do_LDE, `Quy`)
data_LDE1 <- data3 %>% filter(Muc_do_LDE == 1) %>% select(Muc_do_LDE, `Quy`)
data_LDE0 phản ánh doanh nghiệp có nợ dài hạn cao, tiềm ẩn rủi ro tài chính theo quý, còn data_LDE1 cho thấy doanh nghiệp duy trì cơ cấu nợ an toàn.
data_ETL0 <- data3 %>% filter(Muc_do_ETL == 0) %>% select(Muc_do_ETL, `Quy`)
data_ETL1 <- data3 %>% filter(Muc_do_ETL == 1) %>% select(Muc_do_ETL, `Quy`)
data_ETL0 cho thấy quý mà doanh nghiệp có vốn bảo vệ nợ thấp, tiềm ẩn rủi ro, còn data_ETL1 cho thấy doanh nghiệp duy trì vốn chủ sở hữu hợp lý, thuận lợi cho ổn định tài chính.
data_CDC0 <- data3 %>% filter(Muc_do_CDC == 0) %>% select(Muc_do_CDC, `Quy`)
data_CDC1 <- data3 %>% filter(Muc_do_CDC == 1) %>% select(Muc_do_CDC, `Quy`)
data_CDC0 phản ánh quý mà doanh nghiệp khó dùng lợi nhuận thanh toán nợ ngắn hạn, còn data_CDC1 cho thấy doanh nghiệp có khả năng trả nợ từ lợi nhuận hiệu quả.
data_LDTA0 <- data3 %>% filter(Muc_do_LDTA == 0) %>% select(Muc_do_LDTA, `Quy`)
data_LDTA1 <- data3 %>% filter(Muc_do_LDTA == 1) %>% select(Muc_do_LDTA, `Quy`)
data_LDTA0 cho thấy quý doanh nghiệp chịu áp lực nợ dài hạn, data_LDTA1 cho thấy cơ cấu nợ dài hạn hợp lý, giúp đánh giá xu hướng quản trị nợ.
data_NCAR0 <- data3 %>% filter(Muc_do_NCAR == 0) %>% select(Muc_do_NCAR, `Quy`)
data_NCAR1 <- data3 %>% filter(Muc_do_NCAR == 1) %>% select(Muc_do_NCAR, `Quy`)
data_NCAR0 phản ánh quý doanh nghiệp khó tự tài trợ, data_NCAR1 thể hiện khả năng tự tài trợ tốt, thuận lợi cho quản lý tài chính ngắn hạn.
data_FAE0 <- data3 %>% filter(Muc_do_FAE == 0) %>% select(Muc_do_FAE, `Quy`)
data_FAE1 <- data3 %>% filter(Muc_do_FAE == 1) %>% select(Muc_do_FAE, `Quy`)
data_FAE0 cho thấy quý mà tài sản cố định lớn so với vốn chủ sở hữu, tiềm ẩn rủi ro, data_FAE1 cho thấy cơ cấu tài sản cố định hợp lý, hỗ trợ quản trị vốn hiệu quả.
data_IRE0 <- data3 %>% filter(Muc_do_IRE == 0) %>% select(Muc_do_IRE, `Quy`)
data_IRE1 <- data3 %>% filter(Muc_do_IRE == 1) %>% select(Muc_do_IRE, `Quy`)
data_IRE0 phản ánh quý doanh nghiệp phụ thuộc nhiều vào hàng tồn kho, tiềm ẩn rủi ro thanh khoản, data_IRE1 cho thấy khả năng quản lý tồn kho hiệu quả.
data_ROEA0 <- data3 %>% filter(Muc_do_ROEA == 0) %>% select(Muc_do_ROEA, `Quy`)
data_ROEA1 <- data3 %>% filter(Muc_do_ROEA == 1) %>% select(Muc_do_ROEA, `Quy`)
data_ROEA0 cho thấy quý doanh nghiệp đạt hiệu quả sinh lời thấp, còn data_ROEA1 cho thấy doanh nghiệp đạt hiệu quả sinh lời tốt, từ đó dễ dàng đánh giá xu hướng sinh lợi theo quý và hỗ trợ quyết định đầu tư.
Trong bước này, tác giả tiến hành tính toán các thống kê mô tả cơ bản cho từng biến tài chính bằng ngôn ngữ R, nhằm đánh giá đặc trưng trung tâm và mức độ phân tán của dữ liệu. Quá trình được thực hiện thông qua hàm summarise() trong gói dplyr, kết hợp với các hàm thống kê cơ bản như:
Các kết quả này được hiển thị thông qua kable() và kable_styling() nhằm trình bày bảng thống kê một cách trực quan.
thongke_WCR <- data3 %>%
summarise( `Trung bình` = mean(WCR, na.rm = TRUE),
`Độ lệch chuẩn` = sd(WCR, na.rm = TRUE),
`Phương sai` = var(WCR, na.rm = TRUE),
`Q1` = quantile(WCR, 0.25, na.rm = TRUE),
`Trung vị` = median(WCR, na.rm = TRUE),
`Q3` = quantile(WCR, 0.75, na.rm = TRUE))
kable(thongke_WCR, caption = "Thống kê mô tả biến WCR", format = "latex",
booktabs = TRUE) %>%
kable_styling(full_width = F, position = "center", latex_options = "hold_position")
Nhận xét
Dựa trên thống kê mô tả, trung bình WCR là 8.3853, cho thấy tài sản ngắn hạn chiếm ưu thế so với tài sản cố định. Độ lệch chuẩn và phương sai cao (24.79 và 614.55) phản ánh sự phân hóa lớn giữa các doanh nghiệp. Tứ phân vị Q1 = 3.86, trung vị = 4.36, Q3 = 4.95 chỉ ra phần lớn doanh nghiệp tập trung quanh mức 4–5. Giá trị trung bình cao hơn trung vị do một số quan sát ngoại lai. Nhìn chung, mức WCR biến động mạnh nhưng vẫn phản ánh khả năng linh hoạt tài chính.
thongke_LM <- data3 %>%
summarise(`Trung bình` = mean(LM, na.rm = TRUE),
`Độ lệch chuẩn` = sd(LM, na.rm = TRUE),
`Phương sai` = var(LM, na.rm = TRUE),
`Q1` = quantile(LM, 0.25, na.rm = TRUE),
`Trung vị` = median(LM, na.rm = TRUE),
`Q3` = quantile(LM, 0.75, na.rm = TRUE))
kable(thongke_LM, caption = "Thống kê mô tả biến LM", format = "latex",
booktabs = TRUE) %>%
kable_styling(full_width = F, position = "center", latex_options = "hold_position")
Nhận xét
Trung bình LM gần bằng 0 (-0.0084), cho thấy về tổng thể, vốn lưu động ròng chiếm tỷ trọng rất nhỏ hoặc cân bằng với nợ ngắn hạn. Độ lệch chuẩn 0.2148 và phương sai 0.0461 phản ánh mức biến động vừa phải giữa các doanh nghiệp. Tứ phân vị Q1 = -0.2139, trung vị = -0.0218, Q3 = 0.2167 cho thấy phần lớn doanh nghiệp có LM dao động quanh giá trị 0, với một số doanh nghiệp âm hoặc dương nhẹ. Nhìn chung, LM thể hiện khả năng quản lý vốn lưu động khá cân bằng, không quá dư thừa hay thiếu hụt.
thongke_LDE <- data3 %>%
summarise(`Trung bình` = mean(LDE, na.rm = TRUE),
`Độ lệch chuẩn` = sd(LDE, na.rm = TRUE),
`Phương sai` = var(LDE, na.rm = TRUE),
`Q1` = quantile(LDE, 0.25, na.rm = TRUE),
`Trung vị` = median(LDE, na.rm = TRUE),
`Q3` = quantile(LDE, 0.75, na.rm = TRUE))
kable(thongke_LDE, caption = "Thống kê mô tả biến LDE", format = "latex",
booktabs = TRUE) %>%
kable_styling(full_width = F, position = "center", latex_options = "hold_position")
Nhận xét
Dựa trên thống kê mô tả, trung bình LDE là 0.1906, cho thấy nợ dài hạn chiếm khoảng 19% so với vốn chủ sở hữu, phản ánh mức đòn bẩy tài chính vừa phải. Độ lệch chuẩn 0.1979 và phương sai 0.0392 cho thấy sự phân hóa vừa phải giữa các doanh nghiệp. Tứ phân vị Q1 = 0.0713, trung vị = 0.1296, Q3 = 0.2524 chỉ ra phần lớn doanh nghiệp duy trì tỷ lệ nợ dài hạn thấp đến vừa phải. Nhìn chung, LDE cho thấy mức nợ dài hạn tương đối kiểm soát được, không quá rủi ro về thanh khoản hay tài chính.
thongke_ETL <- data3 %>%
summarise( `Trung bình` = mean(ETL, na.rm = TRUE),
`Độ lệch chuẩn` = sd(ETL, na.rm = TRUE),
`Phương sai` = var(ETL, na.rm = TRUE),
`Q1` = quantile(ETL, 0.25, na.rm = TRUE),
`Trung vị` = median(ETL, na.rm = TRUE),
`Q3` = quantile(ETL, 0.75, na.rm = TRUE))
kable(thongke_ETL, caption = "Thống kê mô tả biến ETL", format = "latex",
booktabs = TRUE) %>%
kable_styling(full_width = F, position = "center", latex_options = "hold_position")
Nhận xét
Quan sát thống kê mô tả biến ETL, trung bình là 0.4598, cho thấy vốn chủ sở hữu chiếm khoảng 46% so với nợ phải trả, phản ánh cấu trúc vốn cân đối vừa phải. Độ lệch chuẩn 0.3544 và phương sai 0.1256 cho thấy mức biến động khá lớn giữa các doanh nghiệp. Tứ phân vị Q1 = 0.1059, trung vị = 0.3967, Q3 = 0.7734 chỉ ra phần lớn doanh nghiệp có tỷ lệ vốn chủ sở hữu dao động từ mức thấp đến vừa phải. Nhìn chung, ETL phản ánh sự đa dạng trong cơ cấu vốn và khả năng tự tài trợ của các doanh nghiệp.
thongke_CDC <- data3 %>%
summarise( `Trung bình` = mean(CDC, na.rm = TRUE),
`Độ lệch chuẩn` = sd(CDC, na.rm = TRUE),
`Phương sai` = var(CDC, na.rm = TRUE),
`Q1` = quantile(CDC, 0.25, na.rm = TRUE),
`Trung vị` = median(CDC, na.rm = TRUE),
`Q3` = quantile(CDC, 0.75, na.rm = TRUE))
kable(thongke_CDC, caption = "Thống kê mô tả biến CDC", format = "latex",
booktabs = TRUE) %>%
kable_styling(full_width = F, position = "center", latex_options = "hold_position")
Nhận xét
Nhìn vào thống kê mô tả biến CDC, trung bình là 0.0104, cho thấy lợi nhuận sau thuế chưa phân phối chỉ chiếm tỷ trọng rất nhỏ so với nợ ngắn hạn, phản ánh khả năng tự tài trợ hạn chế. Độ lệch chuẩn 0.1023 và phương sai 0.0105 cho thấy sự biến động vừa phải giữa các doanh nghiệp. Tứ phân vị Q1 = -0.0902, trung vị = 0.0559, Q3 = 0.0944 chỉ ra phần lớn doanh nghiệp có giá trị CDC dao động quanh mức 0, với một số âm hoặc dương nhẹ. Nhìn chung, CDC phản ánh khả năng tích lũy lợi nhuận để thanh toán nợ ngắn hạn còn hạn chế và phân hóa giữa các doanh nghiệp.
thongke_LDTA <- data3 %>%
summarise(`Trung bình` = mean(LDTA, na.rm = TRUE),
`Độ lệch chuẩn` = sd(LDTA, na.rm = TRUE),
`Phương sai` = var(LDTA, na.rm = TRUE),
`Q1` = quantile(LDTA, 0.25, na.rm = TRUE),
`Trung vị` = median(LDTA, na.rm = TRUE),
`Q3` = quantile(LDTA, 0.75, na.rm = TRUE))
kable(thongke_LDTA, caption = "Thống kê mô tả biến LTDA", format = "latex",
booktabs = TRUE) %>%
kable_styling(full_width = F, position = "center", latex_options = "hold_position")
Nhận xét
Nhìn vào thống kê mô tả biến LDTA, trung bình là 0.0438, cho thấy nợ dài hạn chiếm khoảng 4.38% tổng nguồn vốn, phản ánh mức sử dụng nợ dài hạn vừa phải trong tổng cơ cấu vốn của các doanh nghiệp. Độ lệch chuẩn 0.0509 và phương sai 0.0026 chỉ ra mức biến động tương đối nhỏ, tức các doanh nghiệp có tỷ lệ nợ dài hạn khá đồng đều. Tứ phân vị Q1 = 0.0125, trung vị = 0.0238, Q3 = 0.0385 cho thấy phần lớn doanh nghiệp giữ tỷ lệ nợ dài hạn ở mức thấp, với một số ít doanh nghiệp có tỷ lệ cao hơn. Tổng thể, biến LDTA phản ánh cấu trúc nguồn vốn cân bằng, nợ dài hạn không chiếm tỷ trọng lớn và phân hóa vừa phải giữa các doanh nghiệp.
thongke_NCAR <- data3 %>%
summarise( `Trung bình` = mean(NCAR, na.rm = TRUE),
`Độ lệch chuẩn` = sd(NCAR, na.rm = TRUE),
`Phương sai` = var(NCAR, na.rm = TRUE),
`Q1` = quantile(NCAR, 0.25, na.rm = TRUE),
`Trung vị` = median(NCAR, na.rm = TRUE),
`Q3` = quantile(NCAR, 0.75, na.rm = TRUE))
kable(thongke_NCAR, caption = "Thống kê mô tả biến NCAR", format = "latex",
booktabs = TRUE) %>%
kable_styling(full_width = F, position = "center", latex_options = "hold_position")
Nhận xét
Xem xét thống kê mô tả biến NCAR, trung bình là -1.2136, cho thấy vốn lưu động ròng so với vốn chủ sở hữu nhìn chung âm, phản ánh một số doanh nghiệp sử dụng nhiều nợ ngắn hạn hơn so với tài sản ngắn hạn và vốn chủ sở hữu. Độ lệch chuẩn 2.6587 và phương sai 7.0688 chỉ ra mức biến động rất lớn giữa các doanh nghiệp, với sự phân hóa rõ rệt. Tứ phân vị Q1 = -2.2807, trung vị = -0.0811, Q3 = 0.4689 cho thấy phần lớn doanh nghiệp nằm gần 0, nhưng vẫn có một số doanh nghiệp âm sâu, kéo trung bình xuống thấp. Nhìn chung, NCAR phản ánh sự khác biệt đáng kể trong khả năng quản lý vốn lưu động so với vốn chủ sở hữu.
thongke_FAE <- data3 %>%
summarise( `Trung bình` = mean(FAE, na.rm = TRUE),
`Độ lệch chuẩn` = sd(FAE, na.rm = TRUE),
`Phương sai` = var(FAE, na.rm = TRUE),
`Q1` = quantile(FAE, 0.25, na.rm = TRUE),
`Trung vị` = median(FAE, na.rm = TRUE),
`Q3` = quantile(FAE, 0.75, na.rm = TRUE))
kable(thongke_FAE, caption = "Thống kê mô tả biến FAE", format = "latex",
booktabs = TRUE) %>%
kable_styling(full_width = F, position = "center", latex_options = "hold_position")
Nhận xét
Từ kết quả có thể thấy trung bình là 1.1524, cho thấy tài sản cố định chiếm khoảng 115% so với vốn chủ sở hữu, phản ánh mức độ sử dụng vốn chủ sở hữu để đầu tư vào tài sản cố định cao. Độ lệch chuẩn 1.3543 và phương sai 1.8342 chỉ ra sự phân hóa đáng kể giữa các doanh nghiệp. Tứ phân vị Q1 = 0.3067, trung vị = 0.5676, Q3 = 1.6962 cho thấy phần lớn doanh nghiệp có FAE dao động từ thấp đến vừa phải, nhưng tồn tại một số doanh nghiệp sử dụng vốn chủ sở hữu vượt trội cho tài sản cố định, kéo trung bình lên cao. Nhìn chung, FAE phản ánh sự đa dạng trong chiến lược đầu tư tài sản cố định và mức độ sử dụng vốn chủ sở hữu.
thongke_IRE <- data3 %>%
summarise( `Trung bình` = mean(IRE, na.rm = TRUE),
`Độ lệch chuẩn` = sd(IRE, na.rm = TRUE),
`Phương sai` = var(IRE, na.rm = TRUE),
`Q1` = quantile(IRE, 0.25, na.rm = TRUE),
`Trung vị` = median(IRE, na.rm = TRUE),
`Q3` = quantile(IRE, 0.75, na.rm = TRUE))
kable(thongke_IRE, caption = "Thống kê mô tả biến IRE", format = "latex",
booktabs = TRUE) %>%
kable_styling(full_width = F, position = "center", latex_options = "hold_position")
Nhận xét
Quan sát thống kê mô tả biến IRE, trung bình là 3.2825, cho thấy hàng tồn kho chiếm tỷ trọng khá lớn so với vốn chủ sở hữu, phản ánh mức độ đầu tư vào tồn kho cao ở nhiều doanh nghiệp. Độ lệch chuẩn 3.7842 và phương sai 14.3203 cho thấy mức biến động rất lớn giữa các doanh nghiệp, với sự phân hóa rõ rệt. Tứ phân vị Q1 = 0.9501, trung vị = 1.5330, Q3 = 4.9276 chỉ ra rằng phần lớn doanh nghiệp có IRE tập trung quanh giá trị vừa phải, trong khi một số doanh nghiệp đầu tư tồn kho vượt trội, kéo giá trị trung bình lên cao. Nhìn chung, IRE phản ánh sự khác biệt đáng kể trong chiến lược quản lý hàng tồn kho và mức độ sử dụng vốn chủ sở hữu.
thongke_ROEA <- data3 %>%
summarise( `Trung bình` = mean(ROEA, na.rm = TRUE),
`Độ lệch chuẩn` = sd(ROEA, na.rm = TRUE),
`Phương sai` = var(ROEA, na.rm = TRUE),
`Q1` = quantile(ROEA, 0.25, na.rm = TRUE),
`Trung vị` = median(ROEA, na.rm = TRUE),
`Q3` = quantile(ROEA, 0.75, na.rm = TRUE))
kable(thongke_ROEA, caption = "Thống kê mô tả biến ROEA", format = "latex",
booktabs = TRUE) %>%
kable_styling(full_width = F, position = "center", latex_options = "hold_position")
Nhận xét
Từ kết quả thống kê mô tả của ROEA ta thấy trung bình là -0.0138, cho thấy lợi nhuận sau thuế chưa phân phối so với tổng nguồn vốn và vốn chủ sở hữu nhìn chung âm nhẹ, phản ánh hiệu quả sử dụng vốn chưa cao ở một số doanh nghiệp. Độ lệch chuẩn 0.0664 và phương sai 0.0044 chỉ ra mức biến động vừa phải giữa các doanh nghiệp. Tứ phân vị Q1 = -0.0731, trung vị = 0.0259, Q3 = 0.0340 cho thấy phần lớn doanh nghiệp dao động quanh mức gần 0, với một số âm và một số dương nhẹ. Nhìn chung, ROEA phản ánh khả năng sinh lời trên tổng nguồn vốn và vốn chủ sở hữu còn hạn chế và phân hóa giữa các doanh nghiệp.
wcr_year <- data3 %>%
group_by(Nam) %>% summarise(WCR = mean(WCR, na.rm = TRUE)) %>%
mutate(Nam = as.numeric(Nam),
nudge_x = case_when(Nam %in% 2015:2016 ~ -0.3, Nam == 2017 ~ 0.1, TRUE ~ 0),
nudge_y = case_when(Nam %in% 2015:2016 ~ 5, Nam == 2017 ~ -0.5,TRUE ~ 0))
mean_wcr <- mean(data3$WCR, na.rm = TRUE)
p_wcr <- ggplot(wcr_year, aes(Nam, WCR)) +
geom_line(color = "#1f77b4", linewidth = 1) +
geom_point(size = 1.5, color = "#1f77b4") +
geom_text(aes(label = round(WCR, 3), nudge_x = nudge_x, nudge_y = nudge_y),
vjust = 1.4, size = 3, fontface = "bold", check_overlap = TRUE) +
geom_smooth(se = FALSE, color = "#2ca02c", linetype = "dashed", linewidth = 0.7) +
geom_hline(yintercept = mean_wcr, color = "red", linetype = "dashed",
linewidth = 0.8) +
annotate("text", x = max(wcr_year$Nam) - 0.5, y = mean_wcr + 1.8,
label = paste0("WCR trung bình = ", round(mean_wcr, 3)),
color = "red", fontface = "bold", size = 2.5, hjust = 1.05) +
geom_rug(alpha = 0.2) +
labs(title = "Tỷ lệ thanh toán hiện thời trung bình", x = "Năm", y = "WCR") +
scale_x_continuous(breaks = unique(wcr_year$Nam)) + theme_minimal() +
theme(plot.title = element_text(hjust = 0.5, face = "bold", size = 14),
axis.ticks.y = element_blank(), panel.grid.major.y = element_blank(),
panel.grid.minor.y = element_blank())
lm_year <- data3 %>% group_by(Nam) %>% summarise(LM = mean(LM, na.rm = TRUE)) %>%
mutate(Nam = as.numeric(Nam), nudge_x = 0, nudge_y = 0)
mean_lm <- mean(data3$LM, na.rm = TRUE)
p_lm <- ggplot(lm_year, aes(Nam, LM)) +
geom_line(color = "#1f77b4", linewidth = 1) +
geom_point(size = 1.5, color = "#1f77b4") +
geom_text(aes(label = round(LM, 3), nudge_x = nudge_x, nudge_y = nudge_y),
vjust = 1.4, size = 3, fontface = "bold", check_overlap = TRUE) +
geom_smooth(se = FALSE, color = "#2ca02c", linetype = "dashed", linewidth = 0.7) +
geom_hline(yintercept = mean_lm, color = "red", linetype = "dashed",
linewidth = 0.8) +
annotate("text", x = max(lm_year$Nam) - 0.5, y = mean_lm + 0.02,
label = paste0("LM trung bình = ", round(mean_lm, 3)),
color = "red", fontface = "bold", size = 2.5, hjust = 0.7) +
geom_rug(alpha = 0.2) +
labs(title = "Vốn lưu động ròng trung bình", x = "Năm", y = "LM") +
scale_x_continuous(breaks = unique(lm_year$Nam)) +
theme_minimal() +
theme(plot.title = element_text(hjust = 0.5, face = "bold", size = 14),
axis.ticks.y = element_blank(),
panel.grid.major.y = element_blank(),
panel.grid.minor.y = element_blank())
p_combined <- p_wcr + p_lm +
plot_layout(ncol = 2) +
plot_annotation(title = "Biểu đồ các chỉ số thanh khoản trung bình theo năm",
theme = theme(plot.title = element_text(hjust = 0.5,
face = "bold", size = 14)))
p_combined
Giải thích
Đoạn code này trước hết chuẩn bị dữ liệu theo từng năm, nhóm (group_by) và tính giá trị trung bình (summarise) của WCR và LM, đồng thời dùng mutate tạo các biến nudge_x, nudge_y để điều chỉnh vị trí nhãn số liệu trên biểu đồ, tránh trùng lắp. Hàm mean(…, na.rm = TRUE) tính giá trị trung bình toàn bộ dữ liệu, loại bỏ các giá trị thiếu.
Ở phần trực quan hóa, ggplot kết hợp geom_line và geom_point thể hiện xu hướng theo năm, geom_text gắn nhãn số liệu với khả năng điều chỉnh vị trí, geom_smooth vẽ đường trend dạng đứt để làm nổi bật xu hướng, geom_hline tạo đường ngang biểu thị mức trung bình toàn bộ dữ liệu, còn annotate dùng để gắn nhãn giải thích mức trung bình. Cuối cùng, plot_layout và plot_annotation kết hợp hai biểu đồ song song và đặt tiêu đề tổng thể, giúp so sánh trực quan WCR và LM theo năm.
Nhận xét
Biểu đồ xu hướng cho thấy WCR có sự biến động mạnh trong năm 2015–2016 với giá trị cực cao, sau đó ổn định quanh mức trung bình 8,385 từ năm 2017 trở đi, phản ánh sự cân bằng dần giữa tài sản ngắn hạn và tài sản cố định. LM dao động quanh giá trị âm trong giai đoạn 2015–2017, sau đó tăng lên dương từ năm 2018, cho thấy vốn lưu động ròng cải thiện, khả năng thanh toán ngắn hạn tăng. Cả hai chỉ số đều có xu hướng ổn định trong giai đoạn gần đây, thể hiện quản trị tài chính hiệu quả hơn và cân đối hơn giữa nguồn vốn ngắn hạn và dài hạn.
p1 <- ggplot(data3, aes(x = LDE)) + geom_histogram(
aes(y = ..density..), fill = "lightblue", color = "black", bins = 7, alpha = 0.6) +
geom_density(color = "red", linewidth = 1.2) +
labs(title = "LDE", x = "Giá trị LDE", y = "Mật độ") +theme_minimal(base_size = 11) +
theme(plot.title = element_text(hjust = 0.5, face = "bold", size = 11))
p2 <- ggplot(data3, aes(x = ETL)) + geom_histogram(
aes(y = ..density..), fill = "lightblue", color = "black", bins = 7, alpha = 0.6) +
geom_density(color = "red", linewidth = 1.2) +
labs(title = "ETL", x = "Giá trị ETL", y = "Mật độ") +theme_minimal(base_size = 11) +
theme(plot.title = element_text(hjust = 0.5, face = "bold", size = 11))
p3 <- ggplot(data3, aes(x = CDC)) + geom_histogram(
aes(y = ..density..), fill = "lightblue", color = "black", bins = 7, alpha = 0.6) +
geom_density(color = "red", linewidth = 1.2) +
labs(title = "CDC", x = "Giá trị CDC", y = "Mật độ") +theme_minimal(base_size = 11) +
theme(plot.title = element_text(hjust = 0.5, face = "bold", size = 11))
p4 <- ggplot(data3, aes(x = LDTA)) + geom_histogram(
aes(y = ..density..), fill = "lightblue", color = "black", bins = 7, alpha = 0.6) +
geom_density(color = "red", linewidth = 1.2) +
labs(title = "LDTA", x = "Giá trị LDTA", y = "Mật độ") +
theme_minimal(base_size = 11) +
theme(plot.title = element_text(hjust = 0.5, face = "bold", size = 11))
p_his <- (p1 + p2) / (p3 + p4) + plot_annotation(
title = "Histogram và đường mật độ của LDE, ETL, CDC và LDTA",
theme = theme(plot.title = element_text(hjust = 0.5, face = "bold", size = 14)))
p_his
Giải thích
Đoạn code này trước hết trực quan hóa phân bố dữ liệu của các biến tài chính quan trọng bằng histogram kết hợp đường mật độ. Mỗi biểu đồ sử dụng geom_histogram với aes(y = ..density..) để chuẩn hóa tần suất thành mật độ, giúp so sánh hình dạng phân bố giữa các biến khác nhau, đồng thời geom_density vẽ đường mật độ mượt mà, nổi bật xu hướng phân bố thực tế của dữ liệu. Tham số bins xác định số cột histogram, alpha điều chỉnh độ trong suốt của cột, còn linewidth điều chỉnh độ dày của đường mật độ, đảm bảo trực quan rõ ràng.
Các thành phần khác như labs và theme_minimal cung cấp nhãn trục và tiêu đề gọn gàng, còn theme(plot.title = …) căn giữa và làm nổi bật tiêu đề từng biểu đồ. Cuối cùng, plot_annotation kết hợp các biểu đồ con thành bố cục 2x2 và gắn tiêu đề tổng thể, cho phép so sánh trực quan đồng thời phân bố các biến tài chính như LDE, ETL, CDC, LDTA, giúp nhận diện các đặc điểm phân bố, mức độ tập trung hay biến động dữ liệu một cách trực quan.
Nhận xét
Bốn biểu đồ phân tích phân bố của các chỉ số tài chính LDE, ETL, CDC và LDTA, cho thấy mẫu dữ liệu không đồng nhất với các nhóm doanh nghiệp khác biệt về cấu trúc vốn và sức khỏe tài chính. LDE và LDTA lệch phải, chủ yếu tập trung gần 0, phản ánh hầu hết doanh nghiệp sử dụng ít hoặc không dùng nợ dài hạn, nhưng tồn tại nhóm nhỏ có đòn bẩy cao kéo trung bình lên. ETL và CDC có phân bố đa đỉnh, thể hiện rõ sự chia tách giữa nhóm doanh nghiệp bảo thủ (ETL cao, CDC dương) và nhóm mạo hiểm hoặc gặp khó khăn tài chính (ETL thấp, CDC âm). Nhìn chung, mẫu gồm ít nhất hai nhóm doanh nghiệp: nhóm lớn ổn định, ít nợ và nhóm nhỏ sử dụng đòn bẩy cao hoặc dòng tiền âm.
plot_violin_box <- function(var_name) { df <- data3[, var_name, drop = FALSE]
colnames(df) <- "value"
ggplot(df, aes(x = "", y = value)) + geom_violin(
fill = "lightblue", alpha = 0.7, color = "lightblue", linewidth = 0.02) +
geom_boxplot(width = 0.3, fill = "#ECEFF1", color = "black",
outlier.color = "red", outlier.size = 1.5) +
labs(title = var_name, x = NULL, y = "Giá trị") + theme_minimal(base_size = 10) +
theme(plot.title = element_text(hjust = 0.5, face = "bold", size = 11),
axis.text = element_text(size = 9), axis.title.y = element_text(size = 10),
axis.ticks.x = element_blank(), panel.grid.major.x = element_blank())}
p_LDE <- plot_violin_box("LDE")
p_ETL <- plot_violin_box("ETL")
p_CDC <- plot_violin_box("CDC")
p_LDTA <- plot_violin_box("LDTA")
final_plot <- (p_LDE + p_ETL) / (p_CDC + p_LDTA) + plot_annotation(
title = "Phân phối các chỉ số tài chính: Violin plot kết hợp Box plot",
theme = theme(plot.title = element_text(hjust = 0.5, face = "bold", size = 13)))
final_plot
Giải thích
Đoạn code này trước hết định nghĩa một hàm plot_violin_box để trực quan hóa phân bố của từng biến tài chính bằng violin plot kết hợp boxplot. Dữ liệu được trích riêng theo biến cần vẽ, đổi tên thành “value” để vẽ biểu đồ trong ggplot. Hàm geom_violin mô tả mật độ giá trị, giúp nhận diện hình dạng phân bố, trong khi boxplot geom_boxplot thể hiện các thông số thống kê cơ bản như trung vị, tứ phân vị và giá trị ngoại lai. Các tham số fill, alpha, color, linewidth và outlier.color được sử dụng để tăng tính trực quan và phân biệt rõ ràng giữa phần mật độ và phần box.
Các biểu đồ của từng biến được kết hợp thành bố cục 2x2 bằng toán tử / và +, đồng thời plot_annotation thêm tiêu đề tổng thể, cho phép so sánh trực quan đồng thời hình dạng phân bố, mức trung vị và các giá trị ngoại lai giữa các chỉ số tài chính.
Nhận xét
Các biểu đồ violin kết hợp boxplot cung cấp cái nhìn chi tiết về phân bố bốn chỉ số tài chính LDE, LDTA, ETL và CDC, xác nhận mẫu dữ liệu gồm các nhóm doanh nghiệp khác biệt về cấu trúc vốn và sức khỏe tài chính. LDE và LDTA lệch phải mạnh với phần lớn doanh nghiệp tập trung gần 0, chỉ một số ít sử dụng đòn bẩy cao, phản ánh xu hướng tránh nợ dài hạn. ETL trải rộng, hộp boxplot lớn, cho thấy chiến lược vốn đa dạng, từ bảo thủ (ETL cao) đến mạo hiểm (ETL thấp). CDC phân bố âm-dương, minh họa sự phân hóa sức khỏe tài chính: nhóm CDC dương dòng tiền ổn định, nhóm CDC âm gặp khó khăn tài chính. Nhìn chung, mẫu gồm ít nhất hai nhóm doanh nghiệp: ổn định ít nợ và mạo hiểm hoặc dòng tiền âm.
data3$Quy <- as.numeric(data3$Quy)
muc_do_cols <- c("Muc_do_NCAR", "Muc_do_FAE", "Muc_do_IRE", "Muc_do_ETL")
plot_pie_for_col <- function(col_name) {
df <- data3 %>% filter(!is.na(!!sym(col_name)), !!sym(col_name) == 1) %>%
count(Quy, .drop = FALSE) %>%
mutate(
Quy = factor(Quy, levels = 1:4, labels = c("Quý 1", "Quý 2", "Quý 3", "Quý 4")),
pct = n / sum(n) * 100, label = sprintf("%.1f%%", pct))
all_quy <- data.frame( Quy = factor(c("Quý 1", "Quý 2", "Quý 3", "Quý 4"),
levels = c("Quý 1", "Quý 2", "Quý 3", "Quý 4")))
df <- all_quy %>% left_join(df, by = "Quy") %>%
mutate(n = replace_na(n, 0), pct = replace_na(pct, 0),
label = ifelse(n == 0, "0.0%", sprintf("%.1f%%", pct)))
ggplot(df, aes(x = "", y = n, fill = Quy)) + geom_col(width = 1, color = "white") +
geom_text(aes(label = label), position = position_stack(vjust = 0.5), size = 2.5) +
coord_polar("y", start = 0) + scale_fill_manual(
values = c("Quý 1" = "#FF9999", "Quý 2" = "#66B2FF",
"Quý 3" = "#99FF99", "Quý 4" = "#FFCC99"),name = "Quý") +
labs(title = gsub("Muc_do_", "", col_name)) + theme_void() +
theme(plot.title = element_text(hjust = 0.5, face = "bold", size = 10),
legend.position = "none")}
p_NCAR <- plot_pie_for_col("Muc_do_NCAR")
p_FAE <- plot_pie_for_col("Muc_do_FAE")
p_ETL <- plot_pie_for_col("Muc_do_ETL")
p_ROEA <- plot_pie_for_col("Muc_do_ROEA")
final_plot <- (p_NCAR + p_FAE) / (p_ETL + p_ROEA) + plot_layout(guides = "collect") &
theme(legend.position = "right", legend.text = element_text(size = 8),
legend.title = element_text(size = 9))
final_plot + plot_annotation(
title = "Tỷ lệ doanh nghiệp đạt ngưỡng an toàn theo quý",
theme = theme(plot.title = element_text(hjust = 0.5, face = "bold", size = 12)))
Giải thích
Đoạn code này chuẩn bị và trực quan hóa tỷ lệ doanh nghiệp đạt ngưỡng an toàn cho từng chỉ số tài chính theo quý bằng biểu đồ tròn.Hàm plot_pie_for_col lọc dữ liệu theo biến nhị phân 0/1, nhóm theo quý (count(Quy)), tính tỷ lệ phần trăm (pct) và chuẩn bị nhãn hiển thị. left_join với tất cả các quý đảm bảo hiển thị đủ 4 quý, ngay cả khi không có doanh nghiệp đạt ngưỡng trong một quý nào đó.
Trong phần trực quan hóa, geom_col tạo cột tròn cho từng quý, coord_polar(“y”) biến cột thành biểu đồ tròn, geom_text gắn nhãn phần trăm vào giữa các lát.Hàm scale_fill_manual tùy chỉnh màu sắc cho từng quý, còn theme_void loại bỏ trục và lưới để biểu đồ gọn gàng. Các biểu đồ con của từng chỉ số được kết hợp thành bố cục 2x2 bằng toán tử / và +, và plot_annotation thêm tiêu đề tổng thể, giúp so sánh trực quan tỷ lệ đạt ngưỡng an toàn giữa các chỉ số và quý.
Nhận xét
Biểu đồ tỷ lệ doanh nghiệp đạt ngưỡng an toàn theo quý cho thấy tính ổn định và tính mùa vụ khác nhau giữa các chỉ số tài chính. Các chỉ số cấu trúc như NCAR và FAE duy trì ổn định qua cả bốn quý, phản ánh tính dài hạn của vốn và cơ cấu tài sản, không bị ảnh hưởng nhiều bởi biến động kinh doanh ngắn hạn. Chỉ số ETL cũng phân bố đồng đều, chứng tỏ ngưỡng an toàn về đòn bẩy phụ thuộc vào chiến lược tài chính dài hạn, không mang tính mùa vụ. Ngược lại, chỉ số hiệu suất lợi nhuận ROEA chịu ảnh hưởng mạnh của chu kỳ kinh doanh, với tất cả doanh nghiệp đạt ngưỡng chỉ rơi vào Quý 3 và Quý 4, cho thấy lợi nhuận tích lũy theo năm và các quý đầu thường chưa đạt mức an toàn.
chi_tieu_list <- c("WCR", "LM", "LDE", "IRE")
data_corr <- data3[, chi_tieu_list]
M <- cor(data_corr, use = "complete.obs")
M_long <- melt(M)
ggplot(M_long, aes(x = Var1, y = Var2, fill = value)) +
geom_tile() + scale_fill_gradient2(low = "#E74C3C",
high = "#1B9E77", mid = "white", midpoint = 0,
limits = c(-1, 1), space = "Lab", name = "Hệ số\ntương quan") +
geom_text(aes(label = round(value, 2)), size = 4, color = "black") +
theme_minimal() +
theme(axis.text.x = element_text(angle = 45, hjust = 1, size = 11),
axis.text.y = element_text(size = 11),
plot.title = element_text(hjust = 0.5, face = "bold", size = 13),
panel.grid = element_blank(), axis.ticks = element_blank(),
legend.title = element_text(size = 11),legend.text = element_text(size = 10)) +
labs(x = NULL, y = NULL, title = "Ma trận tương quan giữa các chỉ tiêu tài chính") +
coord_fixed()
Giải thích
Đoạn code này thực hiện phân tích mối quan hệ giữa các chỉ số tài chính bằng ma trận tương quan và trực quan hóa dưới dạng biểu đồ nhiệt. Trước hết, hàm cor(…, use = “complete.obs”) tính hệ số tương quan giữa các biến, loại bỏ các giá trị NA để kết quả chính xác. Ma trận tương quan sau đó được chuyển sang dạng dài bằng melt() để ggplot có thể xử lý dễ dàng.
Trong phần trực quan hóa, geom_tile() vẽ từng ô màu biểu thị mức độ tương quan, với scale_fill_gradient2 dùng gradient từ đỏ (âm) qua trắng (0) tới xanh (dương) giúp nhận diện cường độ và hướng tương quan.Hàm geom_text gắn nhãn số liệu lên mỗi ô để người đọc dễ theo dõi. Các tùy chỉnh giao diện bằng theme_minimal và theme bao gồm xoay nhãn trục x, loại bỏ lưới, căn giữa tiêu đề.Hàm coord_fixed() giữ tỷ lệ ngang-dọc bằng nhau, đảm bảo các ô không bị méo.
Nhận xét
Ma trận tương quan giữa WCR, LM, LDE và IRE cho thấy các mối quan hệ tài chính nội tại giữa đòn bẩy và thanh khoản. LDE và IRE có tương quan dương mạnh (+0.73), phản ánh rằng sử dụng nợ dài hạn nhiều làm tăng gánh nặng lãi vay, từ đó tăng rủi ro tài chính. LM và IRE tương quan âm mạnh (-0.70), cho thấy chi phí lãi vay cao làm giảm vốn lưu động ròng, thắt chặt thanh khoản ngắn hạn. WCR hầu như độc lập với các chỉ số khác, chứng tỏ khả năng thanh toán nợ ngắn hạn được quản lý riêng biệt, không chịu ảnh hưởng trực tiếp từ cấu trúc đòn bẩy hay gánh nặng lãi vay.
muc_do_vars <- c("Muc_do_CDC", "Muc_do_LDTA", "Muc_do_NCAR", "Muc_do_FAE")
data_long <- data3 %>% select(all_of(muc_do_vars)) %>%
pivot_longer(cols = everything(), names_to = "Chi_tieu", values_to = "Muc_do") %>%
group_by(Chi_tieu, Muc_do) %>% summarise(So_luong = n(), .groups = "drop") %>%
mutate(Chi_tieu = recode(Chi_tieu,"Muc_do_CDC" = "CDC","Muc_do_LDTA" = "LDTA",
"Muc_do_NCAR" = "NCAR", "Muc_do_FAE" = "FAE")) %>%
complete(Chi_tieu, Muc_do = c(0, 1), fill = list(So_luong = 0))
ggplot(data_long, aes(x = Chi_tieu, y = So_luong, fill = factor(Muc_do))) +
geom_col(width = 0.9, position = position_dodge(width = 0.9),
alpha = 0.9, color = "black") +
coord_flip() + geom_text(aes(label = ifelse(So_luong > 0, So_luong, "")),
position = position_dodge(width = 0.9), hjust = -0.2,
size = 3, color = "black") +
scale_fill_manual(values = c("0" = "#E74C3C", "1" = "#2ECC71"),
name = "Mức độ", labels = c("Không đạt", "Đạt")) +
labs(title = "Số lần đạt và không đạt ngưỡng của các chỉ tiêu tài chính",
x = "Chỉ tiêu", y = "Số lượng quan sát") +
theme_minimal() +
theme(legend.position = "right",
plot.title = element_text(hjust = 0.5, face = "bold", size = 13),
axis.text = element_text(size = 10),
axis.title = element_text(size = 10))
Giải thích
Đoạn code này chuyển dữ liệu sang dạng dài với pivot_longer, nhóm và đếm số lượng doanh nghiệp đạt hoặc không đạt ngưỡng (group_by + summarise).Hàm recode đổi tên biến cho trực quan, complete đảm bảo có đủ nhóm 0/1. Biểu đồ sử dụng geom_col vẽ cột ngang, coord_flip chuyển chiều ngang, geom_text gắn nhãn số liệu, và scale_fill_manual phân biệt màu “Đạt” và “Không đạt”.
Nhận xét
Biểu đồ số lần đạt và không đạt ngưỡng phản ánh sự phân cực rõ rệt về sức khỏe tài chính trong mẫu doanh nghiệp. Các chỉ số liên quan đến cấu trúc tài sản (LDTA, FAE) đều ở mức an toàn cao, cho thấy doanh nghiệp bảo thủ, không lạm dụng nợ dài hạn và đủ vốn để tài trợ tài sản cố định. Ngược lại, các chỉ số về hoạt động và vốn (CDC, NCAR) lại thấp, với phần lớn doanh nghiệp không đạt ngưỡng an toàn, cảnh báo về rủi ro dòng tiền và vốn ròng. Như vậy, mẫu doanh nghiệp này dù “an toàn trên giấy tờ” về cơ cấu tài sản, nhưng thực tế vận hành lại yếu kém, đặc biệt về khả năng tạo dòng tiền và duy trì vốn an toàn.
indicators <- c("FAE", "LM", "LDE", "IRE")
dat_long <- data3 %>% select(all_of(indicators), Quy) %>%
pivot_longer(cols = all_of(indicators), names_to = "Indicator", values_to = "Value") %>%
mutate(Quy = factor(Quy, levels = 1:4, labels = c("Quý 1", "Quý 2", "Quý 3", "Quý 4")))
plot_density_for_indicator <- function(ind) {
df_sub <- dat_long %>% filter(Indicator == ind)
ggplot(df_sub, aes(x = Value, color = Quy, fill = Quy)) +
geom_density(alpha = 0.6, linewidth = 0.5) +
scale_color_manual(values = c("Quý 1" = "#FF9999", "Quý 2" = "#66B2FF",
"Quý 3" = "#99FF99", "Quý 4" = "#FFCC99"),
name = "Quý") +
scale_fill_manual(values = c("Quý 1" = "#FF9999", "Quý 2" = "#66B2FF",
"Quý 3" = "#99FF99", "Quý 4" = "#FFCC99"),
name = "Quý") +
labs(title = ind, x = "Giá trị", y = "Mật độ") + theme_minimal(base_size = 10) +
theme(plot.title = element_text(hjust = 0.5, face = "bold", size = 11),
legend.position = "none", axis.text = element_text(size = 9),
axis.title = element_text(size = 10))}
p_FAE <- plot_density_for_indicator("FAE")
p_LM <- plot_density_for_indicator("LM")
p_LDE <- plot_density_for_indicator("LDE")
p_IRE <- plot_density_for_indicator("IRE")
final_plot <- (p_FAE + p_LM) / (p_LDE + p_IRE) + plot_layout(guides = "collect") +
plot_annotation(title = "Phân bố mật độ các chỉ số tài chính theo quý") &
theme(legend.position = "right", legend.title = element_text(size = 9),
legend.text = element_text(size = 8),
plot.title = element_text(hjust = 0.5, face = "bold", size = 13))
final_plot
Giải thích
Đoạn code này chuyển dữ liệu sang dạng dài (pivot_longer) và gán nhãn factor cho quý, nhằm chuẩn bị cho việc trực quan hóa phân bố mật độ theo từng quý. Hàm plot_density_for_indicator lọc từng chỉ số và vẽ biểu đồ mật độ với geom_density, alpha và linewidth để hiển thị rõ đường mật độ.
Bốn biểu đồ mật độ sau đó được ghép theo lưới 2x2 bằng plot_layout và đặt tiêu đề tổng thể với plot_annotation, hỗ trợ so sánh trực quan sự biến động và xu hướng mùa vụ của từng chỉ số tài chính trong các quý, đồng thời nhận diện đỉnh, đuôi và mức độ phân tán khác nhau giữa các quý.
Nhận xét
Biểu đồ mật độ cho thấy sự biến động theo quý của bốn chỉ số tài chính, làm nổi bật tính mùa vụ trong cơ cấu nợ và chi phí lãi vay. LDE và IRE lệch phải, với đa số doanh nghiệp sử dụng ít nợ dài hạn và gánh nặng lãi vay thấp, nhưng Q1 và Q4 có “đuôi” dài hơn, phản ánh ghi nhận nợ mới hoặc tái cấu trúc vào đầu và cuối năm. FAE cũng lệch phải nhưng phân bố 4 quý tương đồng, cho thấy đầu tư tài sản cố định là quyết định dài hạn, ít chịu ảnh hưởng mùa vụ. LM ổn định quanh mức 0 và phân bố giống nhau giữa các quý, chứng tỏ doanh nghiệp duy trì chính sách vốn lưu động nhất quán suốt năm.