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. Sự nhiệt tình và tâm huyết của Thầy chính là động lực lớn giúp em có thể hoàn thành tốt bài tiểu luận này.

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!

PHẦN 1: ỨNG DỤNG R ĐỂ PHÂN TÍCH GIÁ TRỊ GIAO DỊCH TÍN DỤNG

CHƯƠNG 1: GIỚI THIỆU VÀ TÌM HIỂU BỘ DỮ LIỆU

1.1. Tải các thư viện cần thiết

library(readxl)
library(dplyr)
library(ggplot2)
library(data.table)
library(tidyr)
library(knitr)
library(kableExtra)
library(viridis)
library(ggrepel)
library(scales)
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ả:

  • Gói readxl được dùng để đọc và nhập dữ liệu từ các tệp Excel ,csv đảm bảo tính chính xác của nguồn dữ liệu đầu vào.
  • Gói dplyr hỗ trợ thao tác, trích lọc và tổng hợp dữ liệu nhanh chóng trong khuôn khổ phân tích định lượng.
  • Gói ggplot2 là công cụ để xây dựng các biểu đồ minh họa, giúp thể hiện xu hướng và quan hệ giữa các biến.
  • Gói data.table phục vụ xử lý dữ liệu lớn với tốc độ cao, trong khi tidyr giúp chuẩn hóa và định dạng dữ liệu theo cấu trúc phù hợp cho mô hình hóa.
  • GóiknitrkableExtra được sử dụng để tạo bảng biểu chuyên nghiệp khi xuất báo cáo.
  • Gói viridisggrepel giúp hiển thị biểu đồ thông qua màu sắc đa dạng và nhãn không chồng chéo.
  • Gói scales được dùng để định dạng các trục và giá trị hiển thị
  • Gói moments hỗ trợ tính toán các đặc trưng phân bố như độ lệch và độ nhọn, corrplot giúp minh họa mối tương quan giữa các biến, treemapifyggridges cung cấp các dạng biểu đồ trực quan hiện đại thể hiện cấu trúc và phân bố dữ liệu.
  • Gói patchwork dùng để kết hợp nhiều biểu đồ ggplot2 lại thành một bố cục hoàn chỉnh.
  • Cuối cùng, tibble được sử dụng để hiển thị dữ liệu gọn gàng hơn trong quá trình xử lý, và showtext đảm bảo hiển thị đúng phông chữ tiếng Việt khi xuất file PDF của luận văn.

1.2. Nhập dữ liệu từ file CSV

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.

1.3. Chuyển đổi sang data table để tối ưu hóa

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.

1.4. Xem 5 dòng dữ liệu đầu tiê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ệ.

1.5. Xem 5 dòng dữ liệu cuối cùng

kable(tail(data, 5),
      caption = "5 dòng dữ liệu 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")

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.

1.6. Kiểm tra cấu trúc và kiểu dữ liệu

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: - Kiểu số (num) — thường dùng cho định lượng. - Kiểu chuỗi (chr) — mô tả thuộc tính dạng văn bản. - Kiểu nhân tố (Factor) — đại diện cho dữ liệu phân loại. - Kiểu logic (logi) — biểu thị giá trị đúng/sai.

1.7. Thống kê sơ bộ các phương thức thanh toán

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.",
       format = "latex", 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")

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.

  • Câu lệnh.N đếm số lượng giao dịch theo từng nhóm, với by = () dùng để gom nhóm dữ liệu theo từng loại phương thức.
  • Kết quả được sắp xếp giảm dần theo tần suất bằng order(-N), giúp xác định phương thức được sử dụng nhiều nhất.

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.

1.8. Kiểm tra kích thước dữ liệu

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.

1.9. Liệt kê tên các cột (biến)

names(data)
##  [1] "id"             "date"           "client_id"      "card_id"       
##  [5] "amount"         "use_chip"       "merchant_id"    "merchant_city" 
##  [9] "merchant_state" "zip"            "mcc"            "errors"

1.10. Số lượng giá trị duy nhất cho các biến định tính

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.",
       format = "latex", booktabs = TRUE,
      col.names = c("Biến", "Số lượng duy nhất")) %>%
  kable_styling(font_size = 11, position = "center",latex_options = "hold_position")

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:

  • Biến use_chip chỉ có 3 giá trị duy nhất, phản ánh ba hình thức giao dịch chính (Swipe, Chip, Online).
  • Biến merchant_city có đến 12.492 thành phố khác nhau.
  • Biến merchant_state gồm 200 bang khác nhau.
  • Biến mcc (Merchant Category Code) có 109 loại mã ngành, biểu thị đa dạng lĩnh vực hoạt động thương mại.

CHƯƠNG 2: XỬ LÍ DỮ LIỆU THÔ VÀ MÃ HÓA DỮ LIỆU

2.1. Kiểm tra và đếm tổng giá trị bị thiếu (NA))

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) {
  kable(na_df[na_counts > 0, ],
        caption = "Số lượng giá trị thiếu theo từng cột.",
         format = "latex", 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.")
}

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:

  • Biến merchant_state có 1.563.700 giá trị NA, chiếm một phần đáng kể, phản ánh nhiều giao dịch không xác định vị trí bang hoặc khu vực.
  • Biến errors có 13.094.522 giá trị NA, cho thấy phần lớn giao dịch không phát sinh lỗi trong quá trình xử lý.
  • Biến zip có 1,65 triệu giá trị NA không phải là lỗi dữ liệu. Đây chính là đặc điểm nhận dạng của các giao dịch “ONLINE”. Con số này giúp khoanh vùng chính xác quy mô của phân khúc giao dịch và xác nhận rằng việc loại bỏ cột zip là một bước xử lý hợp lý.

2.2. Xử lý giá trị thiếu (Loại bỏ cột zip)

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.

2.3. Kiểm tra hàng trùng lặp hoàn toà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

2.4. Chuẩn hóa dữ liệu chuỗi: Thành phố

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.",
       format = "latex", booktabs = TRUE, col.names = c("Thành phố", "Số lượng"))%>%
  kable_styling(font_size = 11, position = "center",latex_options = "hold_position")

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.

2.5. Chuyển đổi kiểu dữ liệu: Cột số lượng giao dịch

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"

Giải thích

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 đồ.

Nhận xét

Kết quả cho thấy biến amount đã được chuyển về dạng số, giúp đảm bảo tính tương thích trong các phép toán và mô hình phân tích định lượng. Về mặt thống kê – kinh tế, việc chuẩn hóa này là cần thiết để các giá trị giao dịch có thể được so sánh, tổng hợp và phân tích xu hướng chi tiêu một cách chính xác.

2.6. Chuyển đổi kiểu dữ liệu: Cột ngày

if (!inherits(data$date, "POSIXct")) {
  data[, date := as.POSIXct(date)]
}
data[, year := year(date)]
data[, month := month(date)]
data[, hour := hour(date)]

Giải thích

Điều kiện if() để 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 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ờ.

Nhận xét

Kết quả hiển thị cho thấy biến date đã ở dạng POSIXct, đảm bảo khả năng xử lý, lọc và trích xuất thông tin thời gian chính xác như năm, tháng, ngày, hoặc khung giờ. Về mặt kinh tế, việc chuẩn hóa này giúp phân tích xu hướng giao dịch theo thời gian, từ đó hỗ trợ cho các bước phân tổ và trực quan hóa dữ liệu.

2.7. Mã hóa biến mới: Phương thức thanh toán

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",
       format = "latex", booktabs = TRUE, col.names = c("use_chip","payment_type")) %>%
  kable_styling(font_size = 11, position = "center",latex_options = "hold_position")

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. Về mặt thống kê – kinh tế, việc mã hóa này giúp quá trình phân tổ và trực quan hóa dữ liệu theo từng hình thức thanh toán thuận tiện hơn.

2.8. Mã hóa biến mới: Giờ

data[, hour := hour(date)]
kable(head(data[,.(date, hour)]),
      caption = "Mã hóa biến giờ",
      format = "latex", booktabs = TRUE, col.names = c("Ngày giờ gốc", "Giờ")) %>%
  kable_styling(font_size = 11, position = "center",latex_options = "hold_position")

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.

2.9. Mã hóa biến mới: Thứ trong tuần

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",
      format = "latex", booktabs = TRUE, col.names = c("Ngày giờ gốc", "Thứ"))%>%
  kable_styling(font_size = 11, position = "center",latex_options = "hold_position")

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,

2.10. Mã hóa biến mới: Tháng và năm

kable(head(data[,.(date, year, month)]),
      caption = "Mã hóa biến tháng và năm",
      format = "latex", 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")

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.

2.11. Mã hóa biến mới: Khung giờ

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ờ",
        format = "latex", booktabs = TRUE, col.names = c("Giờ", "Khung giờ")) %>%
  kable_styling(font_size = 11, position = "center",latex_options = "hold_position")

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'.",
      format = "latex", booktabs = TRUE, col.names = c("Thứ", "Cuối tuần")) %>%
  kable_styling(font_size = 11, position = "center",latex_options = "hold_position")

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.

2.13. Mã hóa biến mới: Có lỗi

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",
       format = "latex", booktabs = TRUE, col.names = c("Có lỗi", "Số lượng"))%>%
  kable_styling(font_size = 11, position = "center",latex_options = "hold_position")

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.

2.14. Lọc và tạo tập dữ liệu con (Giao dịch thành công)

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 giảm giá trị 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ẩy 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 giảm giá trị, 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.

2.15. Lọc và tạo tập dữ liệu con (Giao dịch hoàn tiền)

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.

2.16. Mã hóa biến mới: Nhóm giá trị giao dịch giảm giá trị

data_am <- data[amount < 0]
data_abs <- copy(data_am)
data_abs[, amount := abs(amount)]
data_abs[, nhom_gia_tri := cut(amount, breaks = c(0, 50, 250, 1000, Inf),
                                labels = c("Rất nhỏ (0-50)", "Nhỏ (50-250)",
                                           "Trung bình (250-1k)", "Lớn (>1k)"),
                                right = FALSE)]
kable(head(data_abs[,.(amount, nhom_gia_tri)]), 
      caption = "Mã hóa biến nhóm giá trị (Giao dịch Hoàn tiền)",
      format = "latex", booktabs = TRUE, col.names = c("Giá trị", "Nhóm")) %>%
  kable_styling(font_size = 11, position = "center",latex_options = "hold_position")

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 4 khoảng giá trị theo tiêu chí:

  • [0, 50) → Rất nhỏ (0–50).
  • [50, 250) → Nhỏ (50–250).
  • [250, 1000) → Trung bình (250–1000).
  • [1000, +Inf) → Lớn (>1000).

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

CHƯƠNG 3:THỰC HIỆN CÁC THỐNG KÊ CƠ BẢN

3.1. PHÂN TỔ DỮ LIỆU

3.1.1. Phân tổ giao dịch giảm giá trị theo phương thức thanh toán

pt_method <- data_abs[,.N, by = payment_type][order(-N)]
kable(pt_method, 
      caption = "Số lượng giao dịch giảm giá trị 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 = 11, position = "center",latex_options = "hold_position")

Giải thích

  • Câu lệnh 1 dùng để đếm số lượng dòng trong bảng data_abs, theo từng nhóm của biến payment_type, sau đó sắp xếp theo thứ tự giảm dần.
  • Câu lệnh 2 dùng để in ra bảng đẹp khi xuất ra pdf.

Nhận xét

Từ kết quả trên cho thấy các giao dịch giảm giá trị tập trung chủ yếu ở các kênh thanh toán vật lý. Phương thức Swipe chiếm số lượng cao nhất (389.176), cao hơn nhiều so với Chip (264.190), điều này phản ánh rủi ro vận hành hoặc tỷ lệ tranh chấp cao hơn từ công nghệ thẻ từ cũ. Ngoài ra, kênh Online có số lượng giao dịch giảm với giá trị 6.683 thấp bất thường, điều này ngụ ý rằng các quy trình điều chỉnh cho kênh này có thể được xử lý riêng biệt và không được ghi lại trong tập dữ liệu.

3.1.2. Phân tổ giao dịch giảm giá trị theo khung giờ giao dịch

pt_hour <- data_abs[,.N, by = khung_gio][order(-N)]
kable(pt_hour, caption = "Số lượng giao dịch giảm giá trị theo khung giờ",
       format = "latex", booktabs = TRUE, col.names = c("Khung giờ", "Số lượng")) %>%
  kable_styling(font_size = 11, position = "center",latex_options = "hold_position")

Giải thích

  • Câu lệnh 1 dùng để đếm số lượng dòng trong bảng data_abs, theo từng nhóm của biến khung_gio, sau đó sắp xếp theo thứ tự giảm dần.
  • Câu lệnh 2 dùng để in ra bảng đẹp khi xuất ra pdf.

Nhận xét

Kết quả này cho thấy các giao dịch giảm giá trị xảy ra chủ yếu vào ban ngày, tập trung cao nhất vào chiều (292.491) và sáng (235.684). Hình thức này tương đồng với phân bố của các giao dịch thành công, cho thấy các hoạt động như hoàn trả, sửa lỗi, hoặc điều chỉnh giao dịch chủ yếu được xử lý trong giờ làm việc và hoạt động kinh doanh cao điểm. Ngoài ra số lượng giảm mạnh vào tối và đặc biệt là đêm (25.992) khẳng định rằng đây là các nghiệp vụ gắn liền với hoạt động tại cửa hàng vật lý.

3.1.3. Phân tổ giao dịch giảm giá trị theo thứ trong tuần

pt_weekday <- data_abs[,.N, by = weekday][order(factor(weekday, levels = display_levels))]
kable(pt_weekday, caption = "Số lượng giao dịch giảm giá trị theo thứ trong tuần",
       format = "latex", booktabs = TRUE, col.names = c("Thứ", "Số lượng")) %>%
  kable_styling(font_size = 11, position = "center",latex_options = "hold_position")

Giải thích

  • Câu lệnh 1 dùng để tính số lượng giao dịch theo từng thứ trong tuần
  • Câu lệnh 2 dùng để in ra bảng đẹp khi xuất ra pdf

Nhận xét

Từ kết quả trên cho thấy sự phân bố đồng đều của các giao dịch giảm giá trị trên tất cả các ngày trong tuần. Số lượng giao dịch dao động rất hẹp (từ 92.651 đến 95.678), không có sự chênh lệch đáng kể giữa ngày thường và cuối tuần.

3.1.4. Phân tổ giao dịch giảm giá trị theo năm

data[, year := year(date)]
data[, month := month(date)]
data_am <- data[amount < 0]
pt_year <- data_am[,.N, by = year][order(year)]
kable(pt_year, caption = "Số lượng giao dịch giảm giá trị theo năm",
       format = "latex", booktabs = TRUE, col.names = c("Năm", "Số lượng")) %>%
  kable_styling(font_size = 11, position = "center",latex_options = "hold_position")

Giải thích

  • Câu lệnh 1 và 2 dùng để lấy năm và tháng của cột date
  • Câu lệnh 3 dùng để lấy các amount < 0
  • Câu lệnh 4 dùng để đếm số lượng theo năm các giao dịch giảm giá trị
  • Câu lệnh 5 dùng để in ra bảng đẹp khi xuất ra pdf

Nhận xét

Từ 2010 đến 2017, số lượng giao dịch giảm giá trị tăng đều đặn (từ 64.044 lên 69.043), cùng với sự tăng trưởng chung của tổng khối lượng giao dịch thành công. Ngoài ra, có sự sụt giảm rõ rệt vào năm 2018 và 2019 (chỉ còn 56.900). Sự sụt giảm này có thể phản ánh tác động tích cực của việc áp dụng rộng rãi công nghệ Chip hoặc sự cải thiện chung trong quy trình nghiệp vụ, giúp giảm tỷ lệ lỗi và điều chỉnh giao dịch.

3.1.5. Phân tổ giao dịch giảm giá trị theo nhóm giá trị giao dịch

pt_value <- data_abs[,.N, by = nhom_gia_tri][order(-N)]
kable(pt_value, 
      caption = "Số lượng giao dịch giảm giá trị 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 = 11, position = "center",latex_options = "hold_position")

Giải thích

  • Câu lệnh 1 dùng để đếm số lượng giao dịch giảm giá trị theo từng nhóm của biến nhom_gia_tri, sau đó sắp xếp theo thứ tự giảm dần.
  • Câu lệnh 2 dùng để in ra bảng đẹp khi xuất ra pdf.

Nhận xét

Từ kết quả này cho thấy sự tập trung bất thường, với 92% (609.640) giao dịch giảm giá trị nằm trong nhóm nhỏ (50-250 USD)“. Ngược lại, các nhóm rất nhỏ (182) và lớn gần như không tồn tại. Điều này ngụ ý rằng việc hoàn trả sản phẩm thường rơi vào khoảng giá trị này, trong khi các giao dịch giảm giá trị rất nhỏ hoặc rất lớn có thể bị xử lý theo quy trình riêng.

3.1.6. Phân tổ giao dịch giảm giá trị theo thành phố

pt_city <- data_abs[,.N, by = merchant_city][order(-N)][1:10]
kable(pt_city, 
      caption = "Top 10 thành phố có số lượng giao dịch giảm giá trị cao nhất",
       format = "latex", booktabs = TRUE, col.names = c("Thành phố", "Số lượng")) %>%
  kable_styling(font_size = 11, position = "center",latex_options = "hold_position")

Giải thích

  • Câu lệnh 1 dùng để đếm số lượng giao dịch giảm giá trị theo từng nhóm của biến merchant_city, sau đó sắp xếp theo thứ tự giảm dần top 10 thành phố.
  • Câu lệnh 2 dùng để in ra bảng đẹp khi xuất ra pdf.

Nhận xét

Kết quả này cho thấy các giao dịch giảm giá trị tập trung chủ yếu ở các trung tâm đô thị, với “HOUSTON” (10.906) dẫn đầu. Ngoài ra, sự thay đổi vị trí của “ONLINE”: trong khi nó dẫn đầu tuyệt đối ở các giao dịch thành công (1,55 triệu), thì ở đây nó chỉ chiếm 9.576 giao dịch, tương đương các thành phố lớn khác. Điều này củng cố luận điểm rằng bộ dữ liệu 660.000 giao dịch giảm giá trị này gần như chỉ phản ánh các nghiệp vụ tại điểm bán, còn các quy trình điều chỉnh hoặc hoàn tiền cho thương mại điện tử gần như chắc chắn được xử lý qua một kênh khác và không bị ghi nhận là giá trị âm trong bộ dữ liệu này.

3.1.7. Phân tổ giao dịch giảm giá trị theo bang

pt_state <- data_abs[,.N, by = merchant_state][order(-N)][1:10]
kable(pt_state, caption = "Top 10 bang có số lượng giao dịch giảm giá trị cao nhất",
       format = "latex", booktabs = TRUE, col.names = c("Bang", "Số lượng")) %>%
  kable_styling(font_size = 11, position = "center",latex_options = "hold_position")

Giải thích

  • Câu lệnh 1 dùng để đếm số lượng giao dịch giảm giá trịs theo từng nhóm của biến merchant_state, sau đó sắp xếp theo thứ tự giảm dần top 10 bang.
  • Câu lệnh 2 dùng để in ra bảng đẹp khi xuất ra pdf.

Nhận xét

Tương tự như phân tích theo thành phố, bảng xếp hạng các bang có giao dịch giảm giá trị cao nhất (dẫn đầu là CA, TX, FL, NY) gần như trùng khớp hoàn toàn với bảng xếp hạng các bang có giao dịch thành công cao nhất. Điều này cho thấy số lượng giao dịch giảm giá trị như điều chỉnh hay hoàn trả là một nghiệp vụ có tỷ lệ thuận trực tiếp với tổng khối lượng giao dịch tại điểm bán. Mặt khác, là sự vắng mặt của “NA” (kênh Online) trong top 10. Điều này củng cố luận điểm rằng quy trình điều chỉnh hoặc hoàn tiền cho kênh thương mại điện tử được xử lý riêng biệt và không nằm trong bộ dữ liệu 660.000 giao dịch này.

3.1.8. Phân tổ giao dịch giảm giá trị theo loại ngày (cuối tuần / ngày trong tuần)

pt_weekend <- data_abs[,.N, by = cuoi_tuan]
kable(pt_weekend, caption = "Số lượng giao dịch giảm giá trị theo loại ngày",
       format = "latex", booktabs = TRUE, col.names = c("Cuối tuần", "Số lượng")) %>%
  kable_styling(font_size = 11, position = "center",latex_options = "hold_position")

Giải thích

  • Câu lệnh 1 dùng để đếm số giao dịch giảm giá trị theo loại ngày.
  • Câu lệnh 2 dùng để in ra bảng đẹp khi xuất ra pdf.

Nhận xét

Bảng thống kê này cho thấy số lượng giao dịch giảm giá trị vào ngày thường (FALSE: 471.979) cao hơn khoảng 2,5 lần so với cuối tuần (TRUE: 188.070). Tuy nhiên, sự chênh lệch này chủ yếu là do có 5 ngày thường so với 2 ngày cuối tuần. Nếu tính trung bình theo ngày, số lượng giao dịch gần như hoàn toàn giống nhau (khoảng 94.000 giao dịch/ngày). Điều này khẳng định mạnh mẽ giả thuyết rằng các giao dịch giảm giá trị này không phải là hoạt động do khách hàng khởi xướng theo thời gian thực mà là các nghiệp vụ xử lý tự động của hệ thống, được lên lịch chạy hàng ngày với một khối lượng công việc nhất quán.

3.1.9. Phân tổ giao dịch giảm giá trị theo năm và phương thức

pt_year_method <- data_abs[,.N, by = c("year", "payment_type")]
setorder(pt_year_method, "year", "payment_type")
kable(head(pt_year_method, 10), 
      caption = "Số lượng giao dịch giảm giá trị 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 = 11, position = "center",latex_options = "hold_position")

Giải thích

  • Câu lệnh 1 dùng để đếm số lượng giao dịch giảm giá trị theo năm và phương thức.
  • Câu lệnh 2 dùng để sắp xếp bảng.
  • Câu lệnh 3 dùng để in ra bảng đẹp khi xuất ra pdf.

Nhận xét

Bảng thống kê này cho thấy quẹt thẻ từ là nguồn chính tuyệt đối của các giao dịch giảm giá trị (chiếm khoảng 63k đến 66k/năm), trong khi kênh Online (chỉ khoảng từ 900 đến 1000/năm). Điều này cho thấy rằng bộ dữ liệu 660.000 giao dịch này chủ yếu phản ánh các nghiệp vụ điều chỉnh tại điểm bán. Ngoài ra, sự tăng nhẹ của các giao dịch Swipe từ 2010-2013 cũng tương ứng với sự tăng trưởng chung của việc sử dụng thẻ từ trong giai đoạn đó, cho thấy một tỷ lệ lỗi hay điều chỉnh ổn định của công nghệ này.

3.1.10. Phân tổ giao dịch giảm giá trị theo năm và khung giờ

pt_year_hour <- data_abs[,.N, by =.(year, khung_gio)][order(year)]
kable(head(pt_year_hour, 10), 
      caption = "Số lượng giao dịch giảm giá trị theo năm và khung giờ",
       format = "latex", booktabs = TRUE, col.names = c(
         "Năm", "Khung giờ", "Số lượng")) %>% kable_styling(font_size = 11, position = "center",latex_options = "hold_position")

Giải thích

  • Câu lệnh 1 dùng để đếm số lượng giao dịch giảm giá trị theo năm và khung giờ
  • Câu lệnh 2 dùng để in ra bảng đẹp khi xuất ra pdf.

Nhận xét

Bảng phân tích này tiếp tục củng cố luận điểm về nghiệp vụ xử lý tại điểm bán. Trong đó, các giao dịch giảm giá trị tập trung cao nhất vào chiều và sáng. Đây là giờ hoạt động kinh doanh cao điểm và gần như rất ít vào đêm. Hình thức này lặp lại ổn định qua các năm 2010, 2011, 2012. Từ những điều đó cho thấy các nghiệp vụ vận hành như hoàn trả, điều chỉnh được thực hiện bởi nhân viên trong giờ làm việc, chứ không phải các sự kiện tự động, và quy mô của chúng tăng trưởng ổn định cùng với tổng khối lượng giao dịch chung.

3.2. THỐNG KÊ MÔ TẢ

Ở phần này, em sẽ tiến hành dùng các câu lệnh tính các đặc trưng thống kê, sau đó lập các bảng thống kê mô tả, cụ thể như sau:

  • mean(…, na.rm=TRUE): dùng để tính giá trị trung bình cộng của một biến
  • sd(…, na.rm=TRUE): dùng để tính độ lệch chuẩn, đo lường mức độ phân tán của dữ liệu so với giá trị trung bình.
  • var(…, na.rm=TRUE): dùng để tính phương sai của dữ liệu
  • min(…) và max(…): dùng để xác định giá trị quan sát nhỏ nhất và giá trị quan sát lớn nhất trong bộ dữ liệu
  • quantile(…, 0.25) và quantile(…, 0.75): dùng để tính toán các giá trị tứ phân vị, cụ thể là Q1 (mốc 25%) và Q3 (mốc 75%).
  • median(…): dùng để tính giá trị trung vị hay tứ phân vị mốc 50% (Q2), đại diện cho giá trị chính giữa của tập dữ liệu.
  • skewness(…): dùng để tính độ xiên hay đại lượng đo lường tính bất đối xứng của phân bố dữ liệu quanh giá trị trung bình.
  • kurtosis(…):dùng để tính hệ số nhọn hay độ nhọn của bộ dữ liệu

3.2.1. Thống kê mô tả: Số lượng giao dịch giảm giá trị theo phương thức thanh toán

summary_abs_method <- data_abs[,.(
`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_abs_method[, (cols_to_round) := round(.SD, 2),.SDcols = cols_to_round]
kable(summary_abs_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")

Nhận xét

Từ bảng trên cho thấy Swipe và Chip chiếm phần lớn giao dịch (tổng 633.566 trên 640.249 giao dịch), trong đó Swipe dẫn đầu với 369.176 giao dịch với trung bình 101.75 USD, cao hơn nhiều so với trung vị 78 USD, do phân bố lệch phải mạnh (độ xiên 3.08). Điều này cho thấy đa số giao dịch nhỏ khoảng 78 giao dịch, nhưng có đuôi dài gồm các khoản điều chỉnh lớn kéo trung bình lên. Hơn thế nữa, Chip cũng tương tự, với độ xiên 3.35 thậm chí lệch hơn Swipe. Trong khi đó, Online chỉ có 6.683 giao dịch, nhưng lại rất đặc biệt với trung bình (297.56) gần bằng trung vị (296), độ xiên gần như bằng0. Điều này chứng tỏ đây là các giao dịch có giá trị ổn định, có thể là quy trình tự động hóa như hoàn tiền cố định hoặc bù trừ hệ thống và không mang tính ngẫu nhiên như hai kênh còn lại. Chính khác biệt rõ rệt này cần được xem xét riêng trong quản lý rủi ro và báo cáo tài chính.

3.2.2. Thống kê mô tả: Số lượng giao dịch giảm giá trị theo khung giờ giao dịch

summary_abs_hour <- data_abs[,.(
`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_abs_hour[, (cols_to_round) := round(.SD,2),.SDcols=cols_to_round]
kable(summary_abs_hour, caption="Thống kê mô tả số lượng giao dịch giảm giá trị theo
      khung giờ giao dịch", format = "latex", booktabs = TRUE) %>%
  kable_styling(latex_options=c("scale_down","hold_position"), font_size=9,
                position="center")

Nhận xét

Từ bảng thống kê mô tả cho thấy giao dịch giảm giá trị tập trung nhiều nhất vào đêm (25.992 giao dịch, trung bình 177.07), có độ lệch chuẩn cao (136.78), do đó biến động lớn. Buổi sáng có số lượng giao dịch thấp nhất nhưng độ nhọn cao nhất (19.61), điều này phản ánh dữ liệu phân bố dồn về trung tâm. Buổi chiều và tối có mức trung bình lần lượt là 94 và 130, nhưng chiều có độ lệch chuẩn nhỏ hơn do đó ổn định hơn. Nhìn chung, đêm là khung giờ giao dịch mạnh nhất, trong khi sáng ít giao dịch nhưng ổn định hơn.

3.2.3. Thống kê mô tả: Số lượng giao dịch giảm giá trị theo thứ trong tuần

summary_abs_weekday <- data_abs[,.(
`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_abs_weekday[, (cols_to_round):=round(.SD,2),.SDcols=cols_to_round]
kable(summary_abs_weekday, caption="Thống kê mô tả số lượng giao dịch giảm giá trị theo
      thứ trong tuần", format = "latex", booktabs = TRUE) %>%
  kable_styling(latex_options=c("scale_down","hold_position"), font_size=9, position="center")

Nhận xét

Từ bảng thống kê cho thấy số lượng giao dịch giảm giá trị dao động theo ngày trong tuần, với thứ hai là cao nhất với (95.678 giao dịch, trung bình 106.21), tiếp theo là thứ tư và thứ năm (khoảng 95.000 giao dịch). Ngoài ra, thứ sáu có trung bình cao nhất (105.19) nhưng độ lệch chuẩn lớn (88.62, do đó biến động mạnh. Mặt khác, chủ nhật có độ nhọn cao nhất (12.88) do đó phân bố dồn về trung tâm, trong khi thứ bảy có độ nhọn thấp nhất (10.51) nên dữ liệu trải rộng hơn. Nhìn chung, tất cả các ngày đều có min = 0.01–0.08 và max = 500 nên tập giá trị số lương giao dịch giảm giá trị cố định.

3.2.4. Thống kê mô tả: Số lượng giao dịch giảm giá trị theo năm

summary_abs_year <- data_abs[,.(
`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_abs_year[, (cols_to_round):=round(.SD,2),.SDcols=cols_to_round]
kable(summary_abs_year, caption="Thống kê mô tả số lượng giao dịch giảm giá trị theo
      năm", format = "latex", booktabs = TRUE) %>%
  kable_styling(latex_options=c("scale_down","hold_position"), font_size=9, position="center")

Nhận xét

Từ bảng thống kê mô tả trên cho thấy các giao dịch giảm giá trị xảy ra nhiều nhất vào giờ kinh doanh cao điểm (chiều và sáng). Tuy nhiên, có sự khác biệt rõ rệt về bản chất: các giao dịch ban ngày có giá trị trung bình thấp (từ 91 đến 94 giao dịch) và lệch phải mạnh với độ xiên là 4, cho thấy đây là các khoản điều chỉnh nhỏ. Ngược lại, đêm và tối có khối lượng thấp hơn nhưng giá trị trung bình lần lượt là 177 và 130 cao hơn hẳn ngày và chiều. Chính vì thế, các nghiệp vụ điều chỉnh giá trị cao, có thể là các lô xử lý đặc thù hoặc rủi ro, được thực hiện ngoài giờ hành chính.

3.2.5. Thống kê mô tả: Số lượng giao dịch giảm giá trị theo nhóm giá trị giao dịch

summary_abs_value <- data_abs[,.(
`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_abs_value[, (cols_to_round):=round(.SD,2),.SDcols=cols_to_round]
kable(summary_abs_value, caption="Thống kê mô tả số lượng giao dịch giảm giá trị 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")

Nhận xét

Từ bảng thống kê mô tả trên cho thấy 609.640 giao dịch giảm giá trị tập trung vào nhóm nhỏ (50-250), với phân bố lệch phải (độ xiên 3.08), từ đó cho thấy đây là các nghiệp vụ phổ biến nhưng có rủi ro cao. Ngoài ra, ở nhóm trung bình (250-1k) thì phân bố đối xứng với độ xiên 0.00 và trung bình (374.65 giao dịch) gần trùng khớp trung vị (375.00 giao dịch). Điều này cho thấy 50.227 giao dịch này rất có thể là các bút toán điều chỉnh tự động, có giá trị cố định (khoảng 375 giao dịch), chứ không phải các khoản hoàn tiền ngẫu nhiên. Nhóm rất nhỏ (182 giao dịch) điều này cho thấy một ngưỡng tối thiểu cho các giao dịch điều chỉnh.

3.2.6. Thống kê mô tả: Top 10 số lượng giao dịch giảm giá trị theo thành phố

summary_abs_city <- data_abs[,.(
`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_abs_city[, (cols_to_round[1:3]):=round(.SD,2),.SDcols=cols_to_round[1:3]]
kable(summary_abs_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")

Nhận xét

Bảng thống kê này cho thấy hai hình thức giảm giá trị hoàn toàn riêng biệt:

  • Hình thức 1: Điều chỉnh vận hành (đa số các thành phố): Các thành phố như “HOUSTON”, “OLYMPIA”, “FARMINGTON” có phân bố lệch phải mạnh với độ xiên từ 3 đến 8. Ngoài ra giá trị trung bình từ 75 đến 82 giao dịch cao hơn Trung vị từ 75 đến 76 giao dịch. Điều này phản ánh các nghiệp vụphần lớn là các giao dịch điều chỉnh hoặchoàn tiền nhỏ và một số ít các giao dịch giá trị lớn kéo trung bình lên.

  • Hình thức 2: Nghiệp vụ cố định (các ngoại lệ): “ONLINE”, “OAKLAND”, và “LAS VEGAS” là các ngoại lệ. Phân bố của chúng hoàn toàn đối xứng (độ xiên xấp xỉ 0), với trung bình gần bằng trung vị (ví dụ: ONLINE 298.7 giao dịch với 298 giao dịch). Từ đó, ta có thể khẳng định không phải là các khoản hoàn tiền ngẫu nhiên, mà là các quy trình điều chỉnh tự động, có giá trị cố định.

3.2.7. Thống kê mô tả: Top 10 số lượng giao dịch giảm giá trị theo bang

summary_abs_state <- data_abs[,.(
`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_abs_state[, (cols_to_round[1:3]):=round(.SD,2),.SDcols=cols_to_round[1:3]]
kable(summary_abs_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")

Nhận xét

Bảng thống kê mô tả này cho thấy hai hình thức rủi ro khác nhau giữa các bang:

  • Hình thức 1: Đa số các bang hàng đầu (như CA, TX, FL, NY, IL) đều cho thấy một mô hình nhất quán: các giao dịch giảm giá trị có phân bố lệch phải mạnh (Độ xiên 3.0 - 4.0). Giá trị Trung vị (77-79 giao dịch) thấp hơn đáng kể so với giá trị trung bình (91-102 giao dịch). Về mặt kinh tế, điều này có nghĩa là rủi ro ở các bang này chủ yếu bao gồm các nghiệp vụ điều chỉnh/hoàn tiền nhỏ, phổ biến, nhưng vẫn tồn tại một “đuôi rủi ro” gồm các giao dịch giá trị rất lớn kéo trung bình lên.

  • Hình thức 2: “MI” (Michigan) là một ngoại lệ đáng chú ý. Bang này có giá trị trung bình (132.85 giao dịch) cao hơn hẳn so với các bang khác, và phân bố ít lệch hơn (độ xiên 1.8). Điều này cho thấy các giao dịch giảm giá trị ở Michigan có xu hướng xảy ra ở mức giá trị cao hơn, điều này gợi ý về một cơ cấu ngành hàng hoặc rủi ro vận hành khác biệt so với các bang còn lại.

3.2.8. Thống kê mô tả: Số lượng giao dịch giảm giá trị theo loại ngày (cuối tuần) hoặc (ngày)

summary_abs_weekend <- data_abs[,.(
`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_abs_weekend[, (cols_to_round):=round(.SD,2),.SDcols=cols_to_round]
kable(summary_abs_weekend, caption="Thống kê mô tả số lượng giao dịch giảm giá trị theo
      loại ngày", format = "latex", booktabs = TRUE) %>%
  kable_styling(latex_options=c("scale_down","hold_position"), font_size=9, position="center")

Nhận xét

Từ bảng trên cho thấy sự giống hệt nhau trong cấu trúc phân bố giao dịch giảm giá trị giữa “Ngày thường” (FALSE) và “Cuối tuần” (TRUE). Mặc dù có 5 ngày thường và 2 ngày cuối tuần, các chỉ số cốt lõi như giá trị trung bình ($102.16 và $102.64), trung vị ($78 và $79), và độ xiên (3.05 và 3.03) là gần như y hệt. Về mặt kinh tế, điều này bác bỏ giả thuyết về hoạt động do khách hàng khởi xướng như trả hàng. Thay vào đó, có thể khẳng định rằng đây là các nghiệp vụ xử lý tự động theo lô, được hệ thống lên lịch chạy hàng ngày với một quy trình nghiệp vụ và cấu trúc rủi ro nhất quán, bất kể là ngày làm việc hay ngày nghỉ.

3.2.9. Thống kê mô tả: Số lượng giao dịch giảm giá trị theo năm và phương thức

summary_abs_year_method <- data_abs[,.(
`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_abs_year_method[, (cols_to_round[1:3]):=round(.SD,2),.SDcols=cols_to_round[1:3]]
kable(head(summary_abs_year_method,10), caption="Thống kê mô tả số lượng giao dịch
      giảm giá trị 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")

Nhận xét

Bảng thống kê chi tiết này củng cố mạnh mẽ kết luận rằng “Swipe” và “Online” là hai quy trình nghiệp vụ hoàn toàn khác biệt.

  • Kênh “Swipe”: Phân bố của “Swipe” lệch phải mạnh với độ xiên ~3.2 và cực kỳ ổn định qua 5 năm. Ngoài ra trung vị luôn là $78, trong khi trung bình lại là $99. Về mặt kinh tế, điều này cho thấy các nghiệp vụ giảm giá trị tại điểm bán có một quy trình vận hành nhất quán: phần lớn là các điều chỉnh nhỏ (quanh $78) và một đoạn rủi ro gồm các giao dịch lớn (lên đến $5000).

  • Kênh “Online”: Kênh “Online” có phân bố đối xứng tuyệt đối với độ xiên \(\approx 0\), với trung bình ($295.46) gần như trùng khớp hoàn toàn với trung vị ($295.00). Hình thức này lặp lại y hệt mỗi năm, bác bỏ giả thuyết đây là các khoản hoàn tiền hoặc tranh chấp ngẫu nhiên, mà khẳng định đây là một quy trình tự động, có giá trị cố định (khoảng $295 đến $$299) của hệ thống.

3.2.10. Thống kê mô tả: Số lượng giao dịch giảm giá trị theo năm và khung giờ

summary_abs_year_hour <- data_abs[,.(`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_abs_year_hour[, (cols_to_round[1:3]):=round(.SD,2),.SDcols=cols_to_round[1:3]]
kable(head(summary_abs_year_hour,10), caption="Thống kê mô tả số lượng giao dịch
      giảm giá trị theo năm và khung giờ", format = "latex", booktabs = TRUE) %>%
  kable_styling(latex_options=c("scale_down","hold_position"), font_size=9, position="center")

Nhận xét

Bảng thống kê cho thấy một mô thức nghiệp vụ kép, rất ổn định qua các năm. Các giao dịch giảm giá trị trong giờ hành chính (sáng và chiều) chiếm số lượng lớn nhất, nhưng có giá trị trung bình thấp (91-94 giao dịch) và phân bố lệch phải mạnh (độ xiên khoảng 4.0), cho thấy đây là các nghiệp vụ điều chỉnh hoặc hoàn tiền nhỏ, phổ biến tại điểm bán. Ngược lại, các giao dịch tối và đêm có khối lượng thấp hơn nhiều nhưng giá trị trung bình (130-181 giao dịch) và trung vị (83-98 giao dịch) cao hơn hẳn. Điều này ngụ ý rằng các nghiệp vụ điều chỉnh giá trị cao, có thể là các lô xử lý tự động hoặc rủi ro, được thực hiện ngoài giờ kinh doanh cao điểm.

CHƯƠNG 4: TRỰC QUAN HÓA DỮ LIỆU

4.1. Số lượng giao dịch giảm giá trị theo phương thức thanh toán

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) +
labs(title = "Số lượng giao dịch giảm giá trị 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

  • Câu lệnh 1 dùng để vẽ biểu đồ cột của số lượng giao dịch giảm giá trị theo từng phương thức thanh toán.
  • Câu lệnh 10 dùng để vẽ đường trung bình của số lượng giao dịch giảm giá trị theo từng phương thức thanh toán.

Nhận xét

Biểu đồ cho thấy giao dịch giảm giá trị chủ yếu tập trung ở kênh vật lý. Phương thức quẹt thẻ từ chiếm tỷ trọng lớn nhất với 389.176 giao dịch, cao hơn so với chip (264.190). Điều này phản ánh rủi ro vận hành hoặc tỷ lệ tranh chấp cao hơn từ công nghệ thẻ từ cũ. Kênh Online có số lượng rất thấp (chỉ 6.683), gợi ngụ ý rằng các quy trình điều chỉnh cho kênh này có thể được xử lý riêng biệt và không được ghi lại trong bộ dữ liệu.

4.2. Biểu đồ bánh donut về cơ cấu tỷ trọng giao dịch giảm giá trị theo phương thức thanh toán

pt_method <- data_abs[,.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 = "black", 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 giảm giá trị 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

  • Câu lệnh 1,2 dùng để chuẩn bị dữ liệu
  • Câu lệnh 7 dùng để vẽ biểu đồ bánh donut tỷ trọng giao dịch giảm giá trị theo phương thức

Nhận xét

Biểu đồ donut cho thấy Swipe chiếm 59% giao dịch giảm giá trị, là kênh chủ đạo, tiếp theo là Chip (40%), trong khi Online chỉ 1% rất ít so với tổng thể. Điều này cho thấy phần lớn điều chỉnh hoặc hoàn tiền xảy ra tại điểm bán vật lý (quẹt thẻ hoặc chip), trong khi kênh trực tuyến hiếm khi phát sinh giao dịch giảm giá trị. Sự chênh lệch rõ rệt giữa các kênh gợi ý cần tập trung kiểm soát rủi ro ở Swipe và Chip, đồng thời xem xét lại quy trình Online để xác định nguyên nhân tỷ trọng quá thấp có thể do hệ thống tự động hóa hoặc hạn chế chính sách.

4.3. Biểu đồ cột về phân bố khối lượng giao dịch giảm giá trị theo khung giờ trong ngày

ggplot(data_abs, 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 giảm giá trị theo khung giờ trong ngày",
       x = "Khung giờ", y = "Số lượng giao dịch") +
  geom_text(stat = "count", aes(label = scales::comma(
    after_stat(count)),big.mark = ".", decimal.mark = ","), 
            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

  • Câu lệnh 1 dùng để vẽ biểu đồ cột thể hiện số lượng giao dịch giảm giá trị theo khung giờ trong ngày.

Nhận xét

Biểu đồ cột cho thấy giao dịch giảm giá trị tập trung mạnh vào ban ngày, đặc biệt là khung giờ chiều (292.491) và sáng (235.684), chiếm đa số tổng khối lượng. Đây là các khung giờ hoạt động kinh doanh cao điểm, nơi các nghiệp vụ như hoàn trả hoặc sửa lỗi được xử lý. Số lượng giao dịch giảm mạnh ở tối và đặc biệt thấp ở đêm (chỉ 25.992). Từ đó cho thấy đây là các nghiệp vụ gắn liền với hoạt động tại cửa hàng vật lý, không phải giao dịch trực tuyến tự động.

4.4. Biểu đồ cột chồng so sánh khối lượng giao dịch giảm giá trị theo năm và phương thức thanh toán

Giải thích

  • Câu lệnh 1 dùng để vẽ biểu đồ cột chồng so sánh khối lượng giao dịch giảm giá trị theo năm và phương thức thanh toán

Nhận xét

Biểu đồ cột chồng này cho thấy sự thay đổi rõ rệt trong cơ cấu giao dịch giảm giá trị theo phương thức thanh toán qua các năm. Từ 2010 đến 2014, kênh Swipe chiếm gần như tuyệt đối với hơn 63.000 giao dịch mỗi năm. Tuy nhiên, từ 2015, Chip đột ngột trở thành kênh chính, đánh dấu sự chuyển dịch công nghệ. Kênh Online luôn ở mức rất thấp, khẳng định đây là một nghiệp vụ riêng biệt, không nằm trong dòng chảy chính của dữ liệu. Sự sụt giảm mạnh vào 2019 cho thấy hệ thống đã được cải tiến, giảm thiểu các giao dịch cần điều chỉnh.

4.5. Biểu đồ đường thẳng về xu hướng biến động tổng số giao dịch giảm giá trị qua các năm

pt_year <- data_abs[,.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, 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 giảm giá trị 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

  • Câu lệnh 1 dùng để chuẩn bị dữ liệu mà em mong muốn
  • Câu lệnh 6 dùng để vẽ biểu đồ đường xu hướng tăng trưởng số lượng giao dịch giảm giá trị theo năm.

Nhận xét

Biểu đồ thể hiện xu hướng tăng trưởng số lượng giao dịch giảm giá trị từ 2010–2019. Từ năm 2010 (64.044) đến 2017 (69.043), con số tăng nhẹ, ổn định quanh mốc 67.000–69.000. Tuy nhiên, năm 2018 bắt đầu giảm (67.534) và rơi mạnh vào 2019 xuống còn 56.900, đây là mức thấp nhất trong giai đoạn. Tuy nhiên, sau chu kỳ tăng trưởng kéo dài, thị trường đã bước vào giai đoạn chững lại hoặc suy giảm rõ rệt từ cuối 2018.

4.6. Biểu đồ Histogram về giá trị giao dịch giảm giá trị

amount_trim <- data_abs[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..),
                 bin= 20, fill = "#56B4E9", color = "white", alpha = 0.7) +
  geom_density(color = "red", size = 1) +
  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 giảm giá trị",
       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

  • Câu lệnh 1 dùng để chuẩn bị dữ liệu
  • Câu lệnh 2 dùng để tính giá trị giao dịch trung bình
  • Câu lệnh 3 dùng để vẽ biểu đồ Histogramđường mật độ giá trị giao dịch giảm giá trị
  • Câu lệnh 4 và 6 dùng để tạo biểu đồ Histogramđường mật độ
  • Câu lệnh 21 dùng để tạo nhóm chứa các biểu đồ Histogramđường mật độ

Nhận xét

Biểu đồ mật độ cho thấy phần lớn giao dịch giảm giá trị tập trung ở mức thấp, với đỉnh mật độ cao nhất quanh khoảng 70–100 USD, phù hợp với giá trị trung bình 98.6 USD được đánh dấu. Phân bố lệch phải rõ rệt: phần lớn giao dịch nhỏ, nhưng có “đuôi dài” kéo đến 500 USD, phản ánh các khoản điều chỉnh hoặc hoàn tiền lớn hiếm gặp nhưng ảnh hưởng mạnh đến trung bình. Dạng phân phối này cho thấy tính chất bất đối xứng, thế nên cần thận trọng khi dùng trung bình làm thước đo đại diện; nên kết hợp trung vị để đánh giá chính xác hơn.

4.7. Biểu đồ tròn về giá trị trung bình giao dịch giảm giá trị theo thứ trong tuần

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 giảm giá trị 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

  • Câu lệnh 1 dùng để chuẩn bị dữ liệu mà em mong muốn
  • Câu lệnh 4 dùng để vẽ biểu đồ tròn biểu thị tỷ trọng số lượng giao dịch giảm giá trị theo thứ trong tuần.

Nhận xét

Biểu đồ tròn này cho thấy tỷ trọng của các giao dịch giảm giá trị được phân bố đồng đều gần như tuyệt đối qua tất cả các ngày trong tuần, với mỗi ngày chiếm xấp xỉ 14.0% - 14.5% (tương đương 1/7). Sự đồng đều này, đặc biệt là việc không có sự khác biệt giữa ngày thường và cuối tuần. Do đó đây không phải là các nghiệp vụ do khách hàng khởi xướng như trả hàng. Thay vào đó, đây là bằng chứng cho thấy các giao dịch này là quy trình xử lý theo lô tự động của hệ thống, được lên lịch chạy hàng ngày với một khối lượng công việc nhất quán.

4.8. Biểu đồ treemap về top 10 thành phố có khối lượng giao dịch giảm giá trị cao nhất

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 = 10 ) +
  scale_fill_brewer(palette = "Paired") +
  labs(title = "Top 10 thành phố có số lượng giao dịch giảm giá trị 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

  • Câu lệnh 1 dùng để vẽ biểu đồ treemap biểu thị top 10 thành phố có số lượng giao dịch giảm giá trị cao nhất.
  • Câu lệnh 4 dùng để chọn bảng màu cho các thành phố để dễ phân biệt.

Nhận xét

Biểu đồ này cho thấy sự phân bổ của các giao dịch giảm giá trị cân bằng hơn nhiều so với giao dịch thành công. Khác với trước đây, “ONLINE” (9.576) không còn thống trị mà chỉ ngang hàng với các thành phố vật lý lớn khác, trong khi “HOUSTON” (10.906) lạị dẫn đầu. Điều này cho thấy bộ dữ liệu 660.000 giao dịch này chủ yếu phản ánh các nghiệp vụ điều chỉnh tại điểm bán vật lý, và số lượng ONLINE thấp tiếp tục ngụ ý rằng quy trình điều chỉnh hoặc hoàn tiền cho thương mại điện tử được xử lý riêng biệt và không nằm trong bộ dữ liệu này.

4.9. Biểu đồ cột ngang 10 bang có lượng giao dịch giảm giá trị cao nhất

pt_state <- data_abs[,.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) +
  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 giảm giá trị 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 1 dùng để chuẩn bị dữ liệu 10 bang có số lượng giao dịch giảm giá trị cao nhất
  • Câu lệnh 2 dùng để vẽ biểu đồ cột biểu thị top 10 bang có số lượng giao dịch giảm giá trị cao nhất.

Nhận xét

Biểu đồ cột ngang cho thấy sự phân bố giao dịch giảm giá trị theo bang, với California (CA) dẫn đầu tuyệt đối (84.479 giao dịch), gấp đôi Texas (TX). Điều này phản ánh quy mô kinh tế và lưu lượng giao dịch tại các bang lớn. Tuy nhiên, phát hiện quan trọng là sự vắng mặt của kênh Online trong top 10, cho nên các nghiệp vụ điều chỉnh hoặc hoàn tiền cho thương mại điện tử được xử lý riêng biệt và không nằm trong bộ dữ liệu này.

4.10. Biểu đồ cột so sánh số lượng giao dịch giảm giá trị giữa ngày thường và cuối tuần

pt_weekend <- data_abs[,.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,big.mark = ".", decimal.mark = ",")),
            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 giảm giá trị: 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, 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

  • Câu lệnh 1, 2 dùng để chuẩn bị dữ liệu
  • Câu lệnh 3 dùng để vẽ biểu đồ cột so sánh giao dịch giảm giá trị giữa ngày thường vào cuối tuần

Nhận xét

Biểu đồ cột này so sánh khối lượng giao dịch giảm giá trị giữa ngày thường (471.979) và cuối tuầ (188.070). Dù số lượng chênh lệch lớn, tỷ trọng cho thấy 71.5% giao dịch xảy ra vào ngày thường, trong khi cuối tuần chỉ chiếm 28.5%. Tuy nhiên, nếu tính trung bình theo ngày (5 ngày thường vs 2 ngày cuối tuần), thì mật độ giao dịch gần như bằng nhau (khoảng 94.000 giao dịch/ngày). Điều này khẳng định đây là các nghiệp vụ xử lý tự động của hệ thống, được lên lịch chạy hàng ngày với một khối lượng công việc nhất quán, chứ không phải do hành vi khách hàng.

4.11. Biểu đồ đường thẳng về xu hướng giá trị trung bình giao dịch giảm giá trị theo năm

avg_year <- data_abs[,.(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 giảm giá trị 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

  • Câu lệnh 1 dùng để chuẩn bị dữ liệu
  • Câu lệnh 7 dùng để vẽ biểu đồ đường dùng để biểu thị xu hướng giá trị trung bình giao dịch giảm giá trị theo năm

Nhận xét

Biểu đồ này cho thấy giá trị trung bình của một giao dịch giảm giá trị luôn ở mức cao và ổn định (dao động quanh 102-103 USD), cao gần gấp đôi so vớii giá trị trung bình của một giao dịch thành công (khoảng 51 USD). Điều này cho thấy đường xu hướngg gần như bằng phẳng, cho thấy mức giá trị trung bình này không hề thay đổi trong suốt 10 năm. Điều này cho thấy đây là các giao dịch hoàn tiền ngẫu nhiên của khách hàng, mà khẳng định đây là các nghiệp vụ điều chỉnh có quy chuẩn, mang tính thủ tục với một mức giá trị cố định, không thay đổi theo thời gian.

4.12. Biểu cột kép cơ cấu Phần trăm giao dịch giảm giá trị theo khung giờ và phương thức

pt_hour_method <- data_abs[,.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 giao dịch giảm giá trị 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

  • Câu lệnh 1,2 và 3 dùng để chuẩn bị dữ liệu
  • Câu lệnh 4 dùng để vẽ biểu đồ cột kép dùng để biểu thị cơ cấu giao dịch giảm giá trị theo khung giờ và phương thức

Nhận xét

Biểu đồ này khẳng định rằng nghiệp vụ giảm giá trị gần như chỉ xảy ra ở kênh thanh toán vật lý. Kênh Online chiếm tỷ lệ rất nhỏ (0.7% - 4.2%) trong mọi khung giờ, do đó quy trình điều chỉnh hoặc hoàn tiền cho thương mại điện tử được xử lý riêng biệt. Trong số các kênh vật lý, Swipe luôn chiếm đa số (57-60%) trong các khung giờ, cho thấy công nghệ cũ này vẫn là nguồn phát sinh các nghiệp vụ điều chỉnh chính, ngay cả khi chip đã được áp dụng.

4.13. Phân bố giá trị trung bình giao dịch giảm giá trị theo bang và loại ngày

pt_state_day <- data_abs[,.(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.9), 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 giảm giá trị 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

  • Câu lệnh 1,2,3,4 và 5 dùng để chuẩn bị dữ liệu
  • Câu lệnh 6 dùng để vẽ biểu đồ cột đôi dùng để biểu thị giá trị trung bình giao dịch giảm giá trị theo bang và loại ngày.

Nhận xét

Biểu đồ trên so sánh giá trị trung bình giao dịch giảm giá trị theo từng bang và loại ngày (ngày thường với cuối tuần). Đa số các quốc gia như Maldives, Egypt, Malaysia hay New Zealand có giá trị trung bình ở mức 300-350 USD, với sự chênh lệch nhỏ giữa hai loại ngày. Tuy nhiên, có hai ngoại lệ đáng chú ý đó là Barbados và Morocco cho thấy giá trị trung bình cao hơn vào cuối tuần (448 và 438 USD), trong khi Oman lại có giá trị trung bình cực cao vào ngày thường (402.7 USD). Điều này cho thấy các giao dịch giảm giá trị có giá trị lớn (trên 400 USD) dường như gắn liền với các ngành dịch vụ hoặc du lịch, nơi các khoản thanh toán lớn vào cuối tuần dễ phát sinh tranh chấp hoặc điều chỉnh nhất. Ngược lại, Oman có thể phản ánh một cơ cấu ngành hàng khác biệt.

4.14. Biểu đồ nhiệt về số lượng giao dịch giảm giá trị theo khung giờ và thứ trong tuần

pt_heat <- data_abs[,.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 giảm giá trị 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

  • Câu lệnh 1 dùng để chuẩn bị dữ liệu số lượng giao dịch theo ngày giờ và thứ.
  • Câu lệnh 2 dùng để vẽ biểu đồ nhiệt dùng để biểu thị số lượng giao dịch giảm giá trị theo khung giờ và thứ trong tuần.

Nhận xét

Biểu đồ nhiệt này cho thấy sự phân bố số lượng giao dịch giảm giá trị theo cả hai chiều khung giờ trong ngày và thứ trong tuần với cách hoạt động cực kỳ nhất quán: các giao dịch giảm giá trị tập trung cao nhất vào giờ kinh doanh cao điểm, cụ thể là chiều (40.980 - 42.547) và sáng (33.109 - 34.041), đồng thời giảm mạnh vào tối (14.565 - 15.658) và đặc biệt thấp vào đêm (3.662 - 3.772). Điều này cho thấy các nghiệp vụ vận hành hoặc xử lý lô tự động, được thực hiện bởi nhân viên trong giờ làm việc, chứ không phải các sự kiện ngẫu nhiên do khách hàng.

Quan trọng hơn, mô thức này lặp lại y hệt ở tất cả 7 ngày trong tuần, kể cả thứ bảy và chủ nhật. Sự đồng đều tuyệt đối này bác bỏ giả thuyết về hoạt động do khách hàng khởi xướng như trả hàng, mà củng cố mạnh mẽ rằng đây là quy trình hệ thống được lên lịch chạy hàng ngày với một khối lượng công việc nhất quán, bất kể là ngày thường hay cuối tuần.

4.15. Biểu đồ bong bóng về tần suất giao dịch giảm giá trị theo thành phố và năm

pt_city_year <- data_abs[,.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 giảm giá trị của top 50 thành phố theo năm",
subtitle = "Mỗi điểm biểu thị tần suất giao dịch giảm giá trị 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

  • Câu lệnh 1 dùng để chuẩn bị dữ liệu
  • Câu lệnh 2 dùng để chuyển cột Year về định dạng số nguyên
  • Câu lệnh 3 dùng để vẽ biểu đồ bong bóng dùng để biểu thị tần suất giao dịch giảm giá trị của top 50 thành phố theo năm

Nhận xét

Biểu đồ này cho thấy một xu hướng kinh tế rất tích cực: tần suất các giao dịch giảm giá trị đã giảm đi đáng kể tại hầu hết các thành phố hàng đầu trong suốt thập kỷ. Kích thước của các bong bóng có xu hướng thu hẹp lại khi di chuyển từ năm 2010 (màu tím) đến 2019 (màu vàng). Điều này thể hiện rõ nhất ở “HOUSTON” và “ONLINE”, những nơi có bong bóng lớn nhất vào năm 2010 nhưng đã co lại đáng kể vào năm 2019. Về mặt kinh tế, sự sụt giảm này là một dấu hiệu của sự trưởng thành và cải tiến quy trình. Nó cho thấy hệ thống thanh toán ngày càng hiệu quả hơn, có thể là do việc áp dụng công nghệ “Chip” an toàn hơn, dẫn đến ít giao dịch lỗi, ít tranh chấp và ít cần điều chỉnh hơn.

4.16. Biểu đồ nhiệt giá trị trung bình giao dịch giảm giá trị theo khung giờ và phương thức

avg_by_hour_method <- data_abs[, .(avg_amount = mean(amount, na.rm = TRUE)), 
                               by = .(khung_gio, payment_type)]
avg_by_hour_method[, khung_gio := factor(khung_gio, 
                                         levels = c("Đêm", "Sáng", "Chiều", "Tối"))]
ggplot(avg_by_hour_method, aes(x = payment_type, y = khung_gio, fill = avg_amount)) +
  geom_tile(color = "white", linewidth = 0.8) +
  geom_text(aes(label = scales::dollar(round(avg_amount, 0))), 
            color = "black", size = 4, fontface = "bold") +
  scale_fill_gradient2(low = "#E63946", mid = "#FF9E6D", high = "#FFD166",
  midpoint = median(avg_by_hour_method$avg_amount),name = "Giá trị trung bình" ) +
  labs(
    title = "Giá trị trung bình giao dịch giảm giá trị theo khung giờ và phương thức",
    subtitle = "Đơn vị: USD | Nguồn: Transactiondataset",
    x = "Phương thức thanh toán",y = "Khung giờ") +
  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.text = element_text(size = 11),panel.grid = element_blank())

Giải thích

  • Câu lệnh 1 và 3 dùng để chuẩn bị dữ liệu.
  • Câu lệnh 5 dùng để vẽ biểu đồ nhiệt dùng để biểu thịGiá trị trung bình giao dịch giảm giá trị theo khung giờ và phương thức.

Nhận xét

Biểu đồ nhiệt cho thấy giá trị trung bình giao dịch giảm giá trị có sự khác biệt rõ rệt giữa các phương thức và khung giờ. Kênh Online luôn có giá trị cao nhất ( khoảng ($293–$302), cho thấy đây là một quy trình nghiệp vụ tự động, cố định, không liên quan đến hoạt động tại điểm bán. Trong khi đó, kênh Swipe có giá trị thấp hơn (khoảng $91–$175), phản ánh rủi ro vận hành hoặc tỷ lệ tranh chấp cao hơn từ công nghệ cũ. Khung giờ chiều và sáng là thời điểm tập trung của các giao dịch điều chỉnh, phù hợp với giờ làm việc.

4.17. Biểu đồ giá trị trung bình giao dịch giảm giá trị theo các yếu tố thời gian

global_mean_amount <- mean(data_abs$amount, na.rm = TRUE)
year_summary <- data_abs[, .(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_abs[, .(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_abs[, .(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_abs[, .(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) +
  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 giảm giá trị theo các yếu tố thời gian",
    theme = theme(plot.title = element_text(hjust = 0.5, face = "bold", size = 14)))
final_plot

Ý nghĩa

  • Câu lệnh 1,2 dùng để chuẩn bị dữ liệu
  • Câu lệnh 4,6,30 và 43dùng để tạo biểu đồ đườngđường trung bình của giá trị trung bình giao dịch giảm giá trị lần lượt theo các yếu tố: năm, tháng, thứ và giờ.
  • Câu lệnh 53 dùng để tạo nhóm chứa cácbiểu đồ đườngđường trung bình của giá trị trung bình giao dịch giảm giá trị lần lượt theo các yếu thời gian theo bố cục mà em mong muốn.

Nhận xét

Biểu đồ trên thể hiện giá trị trung bình giao dịch giảm giá trị theo 4 yếu tố thời gian: năm, tháng, thứ và giờ. Dữ liệu cho thấy giá trị này cực kỳ ổn định trong suốt 10 năm (2010-2019), dao động quanh mức 102-103 giao dịch, với đường xu hướng gần như bằng phẳng. Điều này bác bỏ giả thuyết đây là các giao dịch hoàn tiền ngẫu nhiên của khách hàng, mà khẳng định chúng là các nghiệp vụ điều chỉnh có quy chuẩn, mang tính thủ tục với một mức giá trị cố định. Điều này tương tự lặp lại ở cấp độ tháng và ngày trong tuần, cho thấy sự nhất quán về mặt quy trình. Riêng theo khung giờ, giá trị trung bình thấp vào ban ngày (khoảng 100-105 giao dịch) và tăng vọt vào buổi tối (lên đến 250 giao dịch), điều này nghĩa là các nghiệp vụ điều chỉnh các dịch giá trị cao thường được xử lý ngoài giờ hành chính, có thể là các lô xử lý tự động hoặc rủi ro đặc thù.

PHẦN 2: DỮ LIỆU BÁO CÁO TÀI CHÍNH CỦA CAMIMEX GROUP (CMX)

CHƯƠNG 1: GIỚI THIỆU VÀ TÌM HIỂU BỘ DỮ LIỆU

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ừ quý 1 năm 2015 đến quý 4 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ý.

1.1. Tải gói cần thiết

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, em đã 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, em 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:

  • Gói readxl: dùng để tải file dữ liệu của tụi em lên R với định dạng .xlsx.
  • Gói dplyr: dùng để xử lý và biến đổi dữ liệu.
  • Gói ggplot2: dùng để trực quan hóa dữ liệu.
  • Gói tibble: dùng để tạo và làm việc với các data frame kiểu mới, giúp dữ liệu hiển thị gọn gàng, dễ đọc hơn và thao tác với các cột thuận tiện hơn.
  • Gói moments: cung cấp các hàm tính toán các thống kê bậc cao như độ lệch (skewness) và độ nhọn (kurtosis) của dữ liệu, giúp đánh giá phân phối của biến số.
  • Gói tidyr: dùng để biến đổi cấu trúc dữ liệu, giúp chuyển đổi dữ liệu giữa dạng wide (rộng) và long (dài), tách hoặc gộp các cột, sắp xếp lại dữ liệu để thuận tiện cho việc phân tích và trực quan hóa.
  • Gói ggrepel: dùng để tự động điều chỉnh nhãn trên biểu đồ, giúp các nhãn không bị chồng chéo khi trực quan hóa dữ liệu với ggplot2, đặc biệt hữu ích cho scatter plot hoặc biểu đồ điểm.
  • Gói scales dùng để định dạng đẹp và dễ hiểu cho các trục số, nhãn, tỷ lệ trong biểu đồ. Ví dụ: chuyển số thập phân thành phần trăm (0.75 → 75%), thêm dấu phẩy vào số lớn (1000000 → 1,000,000), hoặc định dạng tiền tệ, ngày tháng.
  • Gói corrplot dùng để trực hóa ma trận hệ số tương quan.
  • Gói patchwork dùng để kết hợp nhiều biểu đồ ggplot2 lại thành một bố cục hoàn chỉnh.
  • Gói gridExtra dùng để sắp xếp, bố trí và xuất nhiều biểu đồ (hoặc bảng) lên cùng một trang.
  • Gói reshape2 dùng để chuyển đổi cấu trúc dữ liệu (rộng thành dài hoặc ngược lại)
  • Gói ggdist dùng để tạo các biểu đồ phân bố nâng cao, như violin plot, half-eye plot, interval plot.
  • Gói knitr dùng tạo bảng đẹp trong pdf
  • Gói kableExtra dùng để trang trí và tùy chỉnh bảng được tạo bởi hàm kable()

1.2. Đọc dữ liệu từ file Excel

data <- read_excel(file.choose())

Ở dòng lệnh này, em 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 em còn sử dụng lệnh file.choose() để chọn được file CMX11.xlsx - file dữ liệu mà em mong muốn. Sau khi đã tải được file CMX11.xlsx vào R, em gán bộ dữ liệu này vào biến data để thuận thiện phân tích.

1.3. Kiểm tra kiểu dữ liệu của bộ dữ liệu CMX

class(data)
## [1] "tbl_df"     "tbl"        "data.frame"

Ở dòng lệnh này, em 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 em đang nghiên cứu là dataframe.

1.4. Xác định số dòng và số cột

dim(data)
## [1] 119  41

Ở dòng lệnh này, em 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òng41 cột.

1.5. Xem danh sách các biến

colnames(data)
##  [1] "Tiêu chí" "2015_Q1"  "2015_Q2"  "2015_Q3"  "2015_Q4"  "2016_Q1" 
##  [7] "2016_Q2"  "2016_Q3"  "2016_Q4"  "2017_Q1"  "2017_Q2"  "2017_Q3" 
## [13] "2017_Q4"  "2018_Q1"  "2018_Q2"  "2018_Q3"  "2018_Q4"  "2019_Q1" 
## [19] "2019_Q2"  "2019_Q3"  "2019_Q4"  "2020_Q1"  "2020_Q2"  "2020_Q3" 
## [25] "2020_Q4"  "2021_Q1"  "2021_Q2"  "2021_Q3"  "2021_Q4"  "2022_Q1" 
## [31] "2022_Q2"  "2022_Q3"  "2022_Q4"  "2023_Q1"  "2023_Q2"  "2023_Q3" 
## [37] "2023_Q4"  "2024_Q1"  "2024_Q2"  "2024_Q3"  "2024_Q4"

Ở dòng lệnh này, em 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ụi em đượ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ý. Để khắc phục vấn đề này, em sẽ xử lý thông qua phần 2:Xử lý dữ liệu thô, mã hóa dữ liệu

1.6. Xem các dòng đầu, các cột đầu để hiểu cấu trúc dữ liệu

kable(data[1:6, 1:6], booktabs = FALSE,linesep = "",align = "c",escape = FALSE) %>%
  kable_styling(latex_options = "striped",font_size = 7)
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
  1. Tiền
5402139458 7363850792 8178935850 4319012577 9260939793
  1. Các khoản tương đương tiền
0 NA NA NA NA
  1. Các khoản đầu tư tài chính ngắn hạn
0 NA NA NA NA

Ở dòng lệnh này, em 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.

1.7. Kiểm tra giá trị bị thiếu của bộ dữ liệu

any(is.na(data))
## [1] TRUE

Ở câu lệnh này, em 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:

  • any(x) với x là một vecto ,matrix, dataframe, array. Kết quả trả về sẽ là TRUE nếu có ít nhất một TRUE, ngược lại sẽ trả về FALSE.
  • is.na(x) với x là vecto, matrix, array, dataframe và list. Kết quả trả về sẽ là một vecto logic hoặc một matrix logic hay một dataframe logic… (với giá trị bị thiếu sẽ là TRUE còn không có thì sẽ là FALSE)

Kết quả cho thấy bộ dữ liệu có ít nhất một giá trị bị thiếu.

1.8. Kiểm tra giá trị bị thiếu của từng cột

colSums(is.na(data))
## Tiêu chí  2015_Q1  2015_Q2  2015_Q3  2015_Q4  2016_Q1  2016_Q2  2016_Q3 
##        0       60       63       66       69       66       65       67 
##  2016_Q4  2017_Q1  2017_Q2  2017_Q3  2017_Q4  2018_Q1  2018_Q2  2018_Q3 
##       67       66       66       66       66       65       64       66 
##  2018_Q4  2019_Q1  2019_Q2  2019_Q3  2019_Q4  2020_Q1  2020_Q2  2020_Q3 
##       66       63       57       55       54       54       53       52 
##  2020_Q4  2021_Q1  2021_Q2  2021_Q3  2021_Q4  2022_Q1  2022_Q2  2022_Q3 
##       53       53       54       56       55       56       54       53 
##  2022_Q4  2023_Q1  2023_Q2  2023_Q3  2023_Q4  2024_Q1  2024_Q2  2024_Q3 
##       53       54       54       56       57       57       56       54 
##  2024_Q4 
##       55

Ở câu lệnh này, em 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:

  • colSums(x) với x là một matrix, dataframe, array . Kết quả trả về sẽ là tổng của từng cột.
  • is.na(x) với x là vecto, matrix, array, dataframe và list. Kết quả trả về sẽ là một vecto logic hoặc một matrix logic hay một dataframe logic… (với giá trị bị thiếu sẽ là TRUE còn không có thì sẽ là FALSE)

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 em sẽ khắc phục ở phần 2:Xử lý dữ liệu thô, mã hóa dữ liệu.

1.9. Xem thông tin của các Tiêu chí

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, em 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.

1.10. Kiểm tra trùng lặp

any(duplicated(data))
## [1] FALSE

Ở câu lệnh này, em 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:

  • any(x) với x là một vecto ,matrix, dataframe, array. Kết quả trả về sẽ là TRUE nếu có ít nhất một TRUE, ngược lại sẽ trả về FALSE.
  • duplicated(x) với x là một dataframe, vector, list, …. Kết quả nhận được sẽ ở dạng TRUE FALSE

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 em đượ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.

CHƯƠNG 2: XỬ LÝ DỮ LIỆU THÔ, MÃ HÓA DỮ LIỆU

2.1. Chuyển đổi hàng thành cột

Như nãy em đã đề cập, dữ liệu của tụi em đượ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ý. Do đó để thuận lợi phân tích, em 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 em sử dụng hàm as.matrix ,t ,colnames , function, as.numericlapply để chuyển hàng thành cột, đồng thời dữ liệu về dạng số. Cú pháp như sau:

  • as.matrix(x) với x là một dataframe, vecto, array (2 chiều). -> đưa dữ liệu về dạng matrix
  • as.numeric(x) với x là một dataframe, vecto, array.-> đưa dữ liệu về dạng số
  • t(x) với x là một matrix -> Chuyển vị ma trận
  • colnames(x) với x là một dataframe, matrix, array (2 chiều).
  • lapply(x, FUN,..) với x là list hoặc dataframe, FUN là hàm muốn áp dụng.-> Dùng để áp dụng một hàm cho từng phần tử trong list hoặc dataframe, kết quả luôn là dạng list.

Bằng cách lợi dụng tính chất chuyển vị của ma trận, em đã 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 em đã 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ư nhóm em mong muốn. Mặt khác khi tụi em cố đưa data về dạng matrix thì em đã vô tình đưa kiểu dữ liệu của bộ dữ liệu về dạng character. Vì vậy tụi em 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.

2.2. Xác định những cột có chứa giá trị bị thiếu

Vì có một số biến không được nhập hoặc do chính sách của công ty nên những biến đó có giá trị bằng 0. Do vậy xảy ra trong giai đoạn nghiên cứu, giá trị của các biến đó đều là . Để thuận tiện phân tích, nhóm em đã xác định lại các cột có dữ liệu đều là NA và loại bỏ các cột này đi.

d1 <- colSums(is.na(data))
head(d1)
##                                 TÀI SẢN                     A- TÀI SẢN NGẮN HẠN 
##                                      40                                       0 
##   I. Tiền và các khoản tương đương tiền                                 1. Tiền 
##                                       0                                       0 
##           2. Các khoản tương đương tiền II. Các khoản đầu tư tài chính ngắn hạn 
##                                      35                                      23

Ở câu lệnh này, em 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:

  • colSums(x) với x là một matrix, dataframe, array . Kết quả trả về sẽ là tổng của từng cột.
  • is.na(x) với x là vecto, matrix, array, dataframe và list. Kết quả trả về sẽ là một vecto logic hoặc một matrix logic hay một dataframe logic… (với giá trị bị thiếu sẽ là TRUE còn không có thì sẽ là FALSE)

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,Các khoản tương đương tiền … Ngoài ra có một số biến không có giá trị bị thiếu, ví dụ như TIền và các khoản tương đương tiền, Tiền,…

2.3. Xử lý các giá trị bị thiếu

2.3.1. Đối với các biến có số giá trị bị thiếu bằng 40 hoặc lớn hơn 20

Đối với các biến này, em quyết định loại bỏ chúng ra khỏi bộ dữ liệu.

data2 <- data[,d1 < 21]

Ở dòng lệnh này gán biến data2, em đã 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ì em sẽ bỏ đi.

2.3.2. Đối với các biến chỉ có vài giá trị bị thiếu

Đối với các biến này, em 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)}
kable(data3[1:6, 1:4], booktabs = FALSE,linesep = "",align = "c",escape = FALSE) %>%
  kable_styling(latex_options = c("striped", "hold_position"),font_size = 8,position = "center")
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
  1. Các khoản phải thu ngắn hạn
2015_Q1 457530508816 5402139458 5402139458 71695285341
2015_Q2 455477073220 7363850792 7363850792 111614058523
2015_Q3 496604609235 8178935850 8178935850 127034850435
2015_Q4 505064327772 4319012577 4319012577 158932625019
2016_Q1 531545294814 9260939793 9260939793 167309074680
2016_Q2 517672484471 6280471175 6280471175 106470036547

Ở dòng lệnh này em sử dụng hàm for kết hợp hàm names, is.na và hàm mean. Sau đó em dùng hàm kable để xuất 6 dòng và cột của dữ liệu thành dạng bảng. Cú pháp như sau:

  • for( i in x) trong đó i là biến tự chọn, x là một tập hợp bất lỳ -> Dùng để lặp các phần tử trong một tập hợp
  • names(x): trong đó x là một dataframe -> dùng để gọi tên tất cả các tên cột của biến data3
  • is.na(x): với x là vecto, matrix, array, dataframe và list. Kết quả trả về sẽ là một vecto logic hoặc một matrix logic hay một dataframe logic… (với giá trị bị thiếu sẽ là TRUE còn không có thì sẽ là FALSE)
  • mean(x, na.rm =): với x là vecto số hay vecto logic và na.rm = TRUE hoặc FALSE tùy thuộc vào cách tính. Nếu muốn tính trung bình luôn vị trí chứa giá trị bị thiếu thì TRUE còn không thì để FALSE.

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.

2.4. Thêm cột Quy (Quý) và Nam (Năm)

Trước khi tiến hành các bước xử lý và phân tích dữ liệu, nhóm em 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 đó, nhóm em 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))

Ở câu lệnh này, em sử dụng hàm rownames_to_column() từ gói tibble để chuyển phần tên hàng (rownames) 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 đó em dùng hàm mutate kết hợp hàm sub để tạo thêm hai cột mới QuyNam từ cột Thoi_gian. Cú pháp như sau:

  • Câu lệnh 1 dùng để tạo một bảng dữ liệu mới, trong đó các Quý
  • Câu lệnh 3 để tạo một dataframe có thêm các cột được tạo mới.

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.

2.5. Mã hóa dữ liệu

Sau khi đã xử lý các giá trị bị thiếu, em 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). Đây là các chỉ số quan trọng với doanh nghiệp nông 2015 đến 2024.

data3$QR <- (data3$`A- TÀI SẢN NGẮN HẠN`-data3$`IV. Hàng tồn kho`)/data3$`I. Nợ ngắn hạn`
data3$CR <- data3$`A- TÀI SẢN NGẮN HẠN`/data3$`I. Nợ ngắn hạn`
data3$DR <- data3$`C. NỢ PHẢI TRẢ`/data3$`TỔNG CỘNG TÀI SẢN`
data3$DE <- data3$`C. NỢ PHẢI TRẢ`/data3$`D.VỐN CHỦ SỞ HỮU`
data3$ER <- data3$`D.VỐN CHỦ SỞ HỮU`/data3$`TỔNG CỘNG TÀI SẢN`
data3$LDR <-  data3$`II. Nợ dài hạn`/data3$`TỔNG CỘNG NGUỒN VỐN`
data3$NWC <-  data3$`I. Nợ ngắn hạn`-data3$`II. Nợ dài hạn`
data3$FAR <-  data3$`II.Tài sản cố định`/data3$`TỔNG CỘNG TÀI SẢN`
data3$IR <- data3$`IV. Hàng tồn kho`/data3$`TỔNG CỘNG TÀI SẢN`
data3$ROA <- data3$`11. Lợi nhuận sau thuế chưa phân phối`/data3$`TỔNG CỘNG TÀI SẢN`

Ở các dòng lệnh trên, em đã tạo các cột mới liên quan đến các biến mà em muốn phân tích ở phần sau. Sau đó thực hiện công thức tính toán như sau:

CHƯƠNG 3: THỰC HIỆN CÁC THỐNG KÊ CƠ BẢN

3.1. Phân tổ các chỉ tiêu kinh tế theo mức độ

3.1.1. Phân tổ khả năng thanh toán hiện thời (CR)

data3$Muc_do_CR <- ifelse (data3$CR >= 0.8,1,0)

Ở dòng lệnh này em sử dụng hàm ifelse với cú pháp: ifelse(điều kiện, giá trị đúng, giá trị sai), với mục đích là phân tổ CR theo các mức mà tụi em mong muốn. Em đang phân tổ CR thành 2 mức 10 tương ứng với:

  • Nếu CR ≥ 0.8 thì khả năng thanh toán ngắn hạn ổn định (mức 1),
  • Nếu CR < 0.8 thì doanh nghiệp có thể gặp khó khăn trong thanh toán (mức 0).

3.1.2. Phân tổ khả năng thanh toán tức thời (QR)

data3$Muc_do_QR <- ifelse (data3$QR >= 0.5,1,0)

Ở dòng lệnh này em sử dụng hàm ifelse với cú pháp: ifelse(điều kiện, giá trị đúng, giá trị sai), với mục đích là phân tổ QR theo các mức mà tụi em mong muốn. Em đang phân tổ QR thành 2 mức 10 tương ứng với:

  • Nếu QR ≥ 0.5 thì thanh khoản thực tế tốt (mức 1),
  • Nếu QR < 0.5 thì khả năng thanh toán kém, phụ thuộc vào hàng tồn (mức 0).

3.1.3. Phân tổ tỷ số nợ trên đòn bẩy tài chính (DR)

data3$Muc_do_DR <- ifelse (data3$DR <= 0.7,1,0)

Ở dòng lệnh này em sử dụng hàm ifelse với cú pháp: ifelse(điều kiện, giá trị đúng, giá trị sai), với mục đích là phân tổ DR theo các mức mà tụi em mong muốn. Em đang phân tổ DR thành 2 mức 10 tương ứng với:

  • Nếu DR ≤ 0.7 thì cấu trúc vốn hợp lý (mức 1),
  • Nếu DR < 0.7 thì rủi ro tài chính cao, đòn bẩy lớn (mức 0).

3.1.4. Phân tổ hệ số nợ trên vốn chủ sở hữu (D/E)

data3$Muc_do_DE  <- ifelse (data3$"DE" <= 2.5,1,0)

Ở dòng lệnh này em sử dụng hàm ifelse với cú pháp: ifelse(điều kiện, giá trị đúng, giá trị sai), với mục đích là phân tổ D/E theo các mức mà tụi em mong muốn. Em đang phân tổ DR thành 2 mức 10 tương ứng với:

  • Nếu D/E ≤ 2.5 thì hệ số nợ trên vốn chủ sở hữu ở mức chấp nhận được (mức 1),
  • Nếu D/E < 2.5 thì doanh nghiệp phụ thuộc nhiều vào nợ vay (mức 0).

3.1.5. Phân tổ tỷ lệ vốn chủ sở hữu (ER)

data3$Muc_do_ER  <- ifelse (data3$ER >= 0.3,1,0)

Ở dòng lệnh này em sử dụng hàm ifelse với cú pháp: ifelse(điều kiện, giá trị đúng, giá trị sai), với mục đích là phân tổ ER theo các mức mà tụi em mong muốn. Em đang phân tổ ER thành 2 mức 10 tương ứng với:

  • Nếu ER ≥ 0.3 thì doanh nghiệp tự chủ tài chính tốt (mức 1),
  • Nếu ER < 0.3 thì doanh nghiệp phụ thuộc vào nợ vay (mức 0).

3.1.6. Phân tổ tỷ số nợ dài hạn (LDR)

data3$Muc_do_LDR  <- ifelse (data3$LDR <= 0.1,1,0)

Ở dòng lệnh này em sử dụng hàm ifelse với cú pháp: ifelse(điều kiện, giá trị đúng, giá trị sai), với mục đích là phân tổ LDR theo các mức mà tụi em mong muốn. Em đang phân tổ LDR thành 2 mức 10 tương ứng với:

  • Nếu LDR ≤ 0.1 thì doanh nghiệp có cấu trúc vốn ổn định (mức 1),
  • Nếu LDR > 0.1 thì doanh nghiệp có tính linh hoạt tài chính không tốt (mức 0).

3.1.7. Phân tổ vốn lưu động ròng (NWC)

data3$Muc_do_NWC  <- ifelse (data3$NWC >= 0,1,0)

Ở dòng lệnh này em sử dụng hàm ifelse với cú pháp: ifelse(điều kiện, giá trị đúng, giá trị sai), với mục đích là phân tổ NWC theo các mức mà tụi em mong muốn. Em đang phân tổ NWC thành 2 mức 10 tương ứng với:

  • Nếu NWC ≥ 0 thì doanh nghiệp có vốn lưu động ổn đinh (mức 1),
  • Nếu NWC < 0 thì doanh nghiệp thiếu vốn lưu động, rủi ro thanh khoản (mức 0).

3.1.8. Phân tổ tỷ lệ tài sản cố định (FAR)

data3$Muc_do_FAR <- ifelse (data3$FAR <= 0.6,1,0)

Ở dòng lệnh này em sử dụng hàm ifelse với cú pháp: ifelse(điều kiện, giá trị đúng, giá trị sai), với mục đích là phân tổ FAR theo các mức mà tụi em mong muốn. Em đang phân tổ FAR thành 2 mức 10 tương ứng với:

  • Nếu FAR ≤ 0.6 thì doanh nghiệp có tỷ lệ tài sản cố định hợp lý (mức 1),
  • Nếu FAR > 0.6 thì doanh nghiệp chú trọng đầu tư dài hạn (mức 0).

3.1.9. Phân tổ tỷ lệ hàng tồn kho (IR)

data3$Muc_do_IR <- ifelse (data3$IR <= 0.45,1,0)

Ở dòng lệnh này em sử dụng hàm ifelse với cú pháp: ifelse(điều kiện, giá trị đúng, giá trị sai), với mục đích là phân tổ IR theo các mức mà tụi em mong muốn. Em đang phân tổ IR thành 2 mức 10 tương ứng với:

  • Nếu IR ≤ 0.45 thì doanh nghiệp quản lý hàng tồn kho hiệu quả (mức 1),
  • Nếu IR > 0.45 thì doanh nghiệp có tồn kho cao, quay vòng chậm (mức 0).

3.1.10. Phân tổ tỷ suất sinh lời trên tài sản (ROA)

data3$Muc_do_ROA <- ifelse (data3$ROA >= 0.04,1,0)

Ở dòng lệnh này em sử dụng hàm ifelse với cú pháp: ifelse(điều kiện, giá trị đúng, giá trị sai), với mục đích là phân tổ ROA theo các mức mà tụi em mong muốn. Em đang phân tổ IR thành 2 mức 10 tương ứng với:

  • Nếu ROA ≥ 4% thì doanh nghiệp có hiệu quả sử dụng tài sản tốt (mức 1),
  • Nếu ROA < 4% thì doanh nghiệp sử dụng tài sản có hiệu quả tài chính thấp (mức 0).

3.2. Phân tổ các chỉ tiêu kinh tế theo quý

3.2.1. Phân tổ khả năng thanh toán hiện thời (CR) theo quý

data_CR0 <- data3 %>%filter(Muc_do_CR == 0) %>%select(Muc_do_CR, `Quy`) 
data_CR1 <- data3 %>%filter(Muc_do_CR == 1) %>%select(Muc_do_CR, `Quy`)

Ở đoạn lệnh này, em phân tách dữ liệu theo mức độ chỉ số CR thành hai nhóm: nhóm 0 (CR < 0.8) và nhóm 1 (CR ≥ 0.8), đồng thời chỉ giữ các cột cần thiết để quan sát theo quý. Cụ pháp như sau:

  • Câu lệnh 2 dùng để lọc các hàng có Muc_do_CR = 0, sau đó chọn cột Quy để giữ lại
  • Câu lệnh 7 dùng để lọc các hàng có Muc_do_CR = 1, sau đó chọn cột Quy để giữ lại

Kết quả thu được

  • data_CR0 là bảng chứa các doanh nghiệp có khả năng thanh toán thấp theo từng quý.
  • data_CR1 là bảng chứa các doanh nghiệp có khả năng thanh toán ổn định theo từng quý.

3.2.2. Phân tổ khả năng thanh toán tức thời (QR) theo quý

data_QR0 <- data3 %>%filter(Muc_do_QR == 0) %>%select(Muc_do_QR, `Quy`)
data_QR1 <- data3 %>%filter(Muc_do_QR == 1) %>%select(Muc_do_QR, `Quy`)

Ở đoạn lệnh này, em phân tách dữ liệu theo mức độ chỉ số QR thành hai nhóm: nhóm 0 (QR < 0.5) và nhóm 1 (QR ≥ 0.5), đồng thời chỉ giữ các cột cần thiết để quan sát theo quý. Cụ pháp như sau:

  • Câu lệnh 2 dùng để lọc các hàng có Muc_do_QR = 0, sau đó chọn cột Quy để giữ lại
  • Câu lệnh 7 dùng để lọc các hàng có Muc_do_QR = 1, sau đó chọn cột Quy để giữ lại

Kết quả thu được:

  • data_QR0 là bảng chứa các doanh nghiệp có khả năng thanh toán nhanh thấp theo từng quý.
  • data_QR1 là bảng chứa các doanh nghiệp có khả năng thanh toán nhanh ổn định theo từng quý.

3.2.3. Phân tổ tỷ lệ nợ (DR) theo quý

data_DR0 <- data3 %>%filter(Muc_do_DR == 0) %>%select(Muc_do_DR, `Quy`)
data_DR1 <- data3 %>%filter(Muc_do_DR == 1) %>%select(Muc_do_DR, `Quy`)

Ở đoạn lệnh này, em phân tách dữ liệu theo mức độ chỉ số DR thành hai nhóm: nhóm 0 (DR ≥ 2) và nhóm 1 (DR < 2), đồng thời chỉ giữ các cột cần thiết để quan sát theo quý. Cụ pháp như sau:

  • Câu lệnh 2 dùng để lọc các hàng có Muc_do_DR = 0, sau đó chọn cột Quy để giữ lại
  • Câu lệnh 7 dùng để lọc các hàng có Muc_do_DR = 1, sau đó chọn cột Quy để giữ lại

Kết quả thu được:

  • data_DR0 là bảng chứa các doanh nghiệp có mức nợ trên vốn chủ sở hữu cao, rủi ro theo từng quý.
  • data_DR1 là bảng chứa các doanh nghiệp có mức nợ trên vốn chủ sở hữu thấp, an toàn theo từng quý.

3.2.4. Phân tổ hệ số nợ trên vốn chủ sở hữu (D/E) theo quý

data_DE0 <- data3 %>%filter(Muc_do_DE == 0) %>%select(Muc_do_DE, `Quy`)
data_DE1 <- data3 %>%filter(Muc_do_DE == 1) %>%select(Muc_do_DE, `Quy`)

Ở đoạn lệnh này, em phân tách dữ liệu theo mức độ chỉ số DE thành hai nhóm: nhóm 0 (DE ≥ 2) và nhóm 1 (DE < 2), đồng thời chỉ giữ các cột cần thiết để quan sát theo quý. Cụ pháp như sau:

  • Câu lệnh 2 dùng để lọc các hàng có Muc_do_DE = 0, sau đó chọn cột Quy để giữ lại
  • Câu lệnh 7 dùng để lọc các hàng có Muc_do_DE = 1, sau đó chọn cột Quy để giữ lại

Kết quả thu được:

  • data_DE0 là bảng chứa các doanh nghiệp có mức nợ trên tổng tài sản cao theo từng quý.
  • data_DE1 là bảng chứa các doanh nghiệp có mức nợ trên tổng tài sản thấp theo từng quý.

3.2.5. Phân tổ tỷ lệ vốn chủ sở hữu (ER) theo quý

data_ER0 <- data3 %>%filter(Muc_do_ER == 0) %>%select(Muc_do_ER, `Quy`)
data_ER1 <- data3 %>%filter(Muc_do_ER == 1) %>%select(Muc_do_ER, `Quy`)

Ở đoạn lệnh này, em phân tách dữ liệu theo mức độ chỉ số ER thành hai nhóm: nhóm 0 (ER < 1) và nhóm 1 (ER ≥ 1), đồng thời chỉ giữ các cột cần thiết để quan sát theo quý. Cụ pháp như sau:

  • Câu lệnh 2 dùng để lọc các hàng có Muc_do_ER = 0, sau đó chọn cột Quy để giữ lại
  • Câu lệnh 7 dùng để lọc các hàng có Muc_do_ER = 1, sau đó chọn cột Quy để giữ lại

Kết quả thu được:

  • data_ER0 là bảng chứa các doanh nghiệp có khả năng trả lãi vay thấp theo từng quý.
  • data_ER1 là bảng chứa các doanh nghiệp có khả năng trả lãi vay tốt theo từng quý.

3.2.6. Phân tổ tỷ lệ nợ dài hạn (LDR) theo quý

data_LDR0 <- data3 %>%filter(Muc_do_LDR == 0) %>%select(Muc_do_LDR, Quy)
data_LDR1 <- data3 %>%filter(Muc_do_LDR == 1) %>%select(Muc_do_LDR, Quy)

Ở đoạn lệnh này, em phân tách dữ liệu theo mức độ chỉ số LDR thành hai nhóm: nhóm 0 (LDR > 0.8) và nhóm 1 (LDR ≤ 0.8), đồng thời chỉ giữ các cột cần thiết để quan sát theo quý. Cụ pháp như sau:

  • Câu lệnh 2 dùng để lọc các hàng có Muc_do_LDR = 0, sau đó chọn cột Quy để giữ lại
  • Câu lệnh 7 dùng để lọc các hàng có Muc_do_LDR = 1, sau đó chọn cột Quy để giữ lại

Kết quả thu được:

  • data_LDR0 là bảng chứa các doanh nghiệp có tỷ lệ cho vay/tiền gửi cao, rủi ro thanh khoản theo từng quý.
  • data_LDR1 là bảng chứa các doanh nghiệp có tỷ lệ cho vay/tiền gửi thấp, an toàn theo từng quý.

3.2.7. Phân tổ vốn lưu động ròng (NWC) theo quý

data_NWC0 <- data3 %>%filter(Muc_do_NWC == 0) %>%select(Muc_do_NWC, `Quy`)
data_NWC1 <- data3 %>%filter(Muc_do_NWC == 1) %>%select(Muc_do_NWC, `Quy`)

Ở đoạn lệnh này, em phân tách dữ liệu theo mức độ chỉ số NWC thành hai nhóm: nhóm 0 (NWC < 0) và nhóm 1 (NWC ≥ 0), đồng thời chỉ giữ các cột cần thiết để quan sát theo quý. Cụ pháp như sau:

  • Câu lệnh 2 dùng để lọc các hàng có Muc_do_NWC = 0, sau đó chọn cột Quy để giữ lại
  • Câu lệnh 7 dùng để lọc các hàng có Muc_do_NWC = 1, sau đó chọn cột Quy để giữ lại

Kết quả thu được:

  • data_NWC0 là bảng chứa các doanh nghiệp có vốn lưu động ròng âm theo từng quý.
  • data_NWC1 là bảng chứa các doanh nghiệp có vốn lưu động ròng dương theo từng quý.

3.2.8. Phân tổ tỷ lệ tài sản cố định (FAR) theo quý

data_FAR0 <- data3 %>%filter(Muc_do_FAR == 0) %>%select(Muc_do_FAR, `Quy`)
data_FAR1 <- data3 %>%filter(Muc_do_FAR == 1) %>%select(Muc_do_FAR, `Quy`)

Ở đoạn lệnh này, em phân tách dữ liệu theo mức độ chỉ số FAR thành hai nhóm: nhóm 0 (FAR > 0.6) và nhóm 1 (FAR ≤ 0.6), đồng thời chỉ giữ các cột cần thiết để quan sát theo quý. Cụ pháp như sau:

  • Câu lệnh 2 dùng để lọc các hàng có Muc_do_FAR = 0, sau đó chọn cột Quy để giữ lại
  • Câu lệnh 7 dùng để lọc các hàng có Muc_do_FAR = 1, sau đó chọn cột Quy để giữ lại

Kết quả thu được:

  • data_FAR0 là bảng chứa các doanh nghiệp có tỷ lệ tài sản cố định trên tổng tài sản cao theo từng quý.
  • data_FAR1 là bảng chứa các doanh nghiệp có tỷ lệ tài sản cố định trên tổng tài sản thấp theo từng quý.

3.2.9. Phân tổ tỷ lệ hàng tồn kho (IR) theo quý

data_IR0 <- data3 %>%filter(Muc_do_IR == 0) %>%select(Muc_do_IR, `Quy`)
data_IR1 <- data3 %>%filter(Muc_do_IR == 1) %>%select(Muc_do_IR, `Quy`)

Ở đoạn lệnh này, em phân tách dữ liệu theo mức độ chỉ số IR thành hai nhóm: nhóm 0 (IR < 1) và nhóm 1 (IR ≥ 1), đồng thời chỉ giữ các cột cần thiết để quan sát theo quý. Cụ pháp như sau:

  • Câu lệnh 2 dùng để lọc các hàng có Muc_do_IR = 0, sau đó chọn cột Quy để giữ lại
  • Câu lệnh 7 dùng để lọc các hàng có Muc_do_IR = 1, sau đó chọn cột Quy để giữ lại

Kết quả thu được:

  • data_IR0 là bảng chứa các doanh nghiệp có khả năng thanh toán lãi thấp theo từng quý.
  • data_IR1 là bảng chứa các doanh nghiệp có khả năng thanh toán lãi tốt theo từng quý.

3.2.10. Phân tổ tỷ suất sinh lời trên tài sản (ROA) theo quý

data_ROA0 <- data3 %>%filter(Muc_do_ROA == 0) %>%select(Muc_do_ROA, `Quy`)
data_ROA1 <- data3 %>%filter(Muc_do_ROA == 1) %>%select(Muc_do_ROA, `Quy`)

Ở đoạn lệnh này, em phân tách dữ liệu theo mức độ chỉ số ROA thành hai nhóm: nhóm 0 (ROA < 0.05) và nhóm 1 (ROA ≥ 0.05), đồng thời chỉ giữ các cột cần thiết để quan sát theo quý. Cụ pháp như sau:

  • Câu lệnh 2 dùng để lọc các hàng có Muc_do_ROA = 0, sau đó chọn cột Quy để giữ lại
  • Câu lệnh 7 dùng để lọc các hàng có Muc_do_ROA = 1, sau đó chọn cột Quy để giữ lại

Kết quả thu được:

  • data_ROA0 là bảng chứa các doanh nghiệp có tỷ suất lợi nhuận thấp theo từng quý.
  • data_ROA1 là bảng chứa các doanh nghiệp có tỷ suất lợi nhuận cao, ổn định theo từng quý.

3.3. Thống kê mô tả các biến

3.3.1. Khả năng thanh toán hiện thời (CR)

thongke_CR <- data3 %>%
  summarise(
    Trung_binh = mean(CR, na.rm = TRUE),
    Do_lech_chuan = sd(CR, na.rm = TRUE),
    Phuong_sai = var(CR, na.rm = TRUE),
    Q1 = quantile(CR, 0.25, na.rm = TRUE),
    Trung_vi = median(CR, na.rm = TRUE),
    Q3 = quantile(CR, 0.75, na.rm = TRUE),
    Do_lech = skewness(CR, na.rm = TRUE),
    Do_nhon = kurtosis(CR, na.rm = TRUE))
thongke_CR

Ở các câu lệnh này, em sử dụng Câu lệnh 2 để tính các chỉ tiêu thống kê cơ bản cho biến CR. Cụ thể như sau:

  • Câu lệnh 3 dùng để tính trung bình cộng của cột CR. Với na.rm = TRUE có nghĩa là bỏ qua các giá trị NA trong quá trình tính.
  • Câu lệnh 4, dùng để tính độ lệch chuẩn, đo mức độ phân tán dữ liệu CR quanh giá trị trung bình.
  • Câu lệnh 5 dùng để tính phương sai, phản ánh độ biến thiên của CR.
  • Câu lệnh 6 dùng để tính tứ phân vị thứ nhất (Q1), là giá trị CR sao cho 25% số quan sát nhỏ hơn hoặc bằng giá trị này.
  • Câu lệnh 7 dùng để tính trung vị (giá trị CR nằm ở giữa tập dữ liệu).
  • Câu lệnh 8 dùng để tính tứ phân vị thứ ba (Q3), là giá trị CR sao cho 75% số quan sát nhỏ hơn hoặc bằng giá trị này.
  • Câu lệnh 9 dùng để tính độ lệch (skewness) của phân phối CR, cho biết phân phối nghiêng về bên trái hay phải.
  • Câu lệnh 10 dùng để tính độ nhọn (kurtosis) của phân phối CR, phản ánh mức độ tập trung dữ liệu quanh trung bình.

Như vậy, sau khi chạy đoạn code trên, em thu được một bảng thongke_CR chỉ gồm một hàng chứa tất cả các chỉ tiêu thống kê cơ bản của CR. Từ kết quả trên, em rút ra các nhận xét sau:

  • Trung bình là 1.0968321. Cho thấy khả năng thanh toán hiện thời trung bình của một quý là 1.0968321.
  • Độ lệch chuẩn là 0.3951233. Cho thấy khả năng thanh toán hiện thời có mức độ phân tán là 0.3951233.
  • Phương sai là 0.1561224. Cho thấy độ biến động của khả năng thanh toán hiện thời là 0.1561224.
  • Tứ phân vị 1 (Q1) là 0.7637552. Cho thấy 25% số quan sát có khả năng thanh toán hiện thời ≤ 0.7637552.
  • Trung vị là 0.9685389. Cho thấy giá trị khả năng thanh toán hiện thời nằm ở giữa tập dữ liệu là 0.9685389.
  • Tứ phân vị 3 (Q3) là 1.4339664. Cho thấy 75% số quan sát có khả năng thanh toán hiện thời ≤ 1.4339664.
  • Độ lệch (skewness) là 0.8145174. Cho thấy phân phối khả năng thanh toán hiện thời nghiêng về một phía với độ lệch là 0.8145174.
  • Độ nhọn (kurtosis) là 2.6284074. Cho thấy mức độ tập trung của phân phối khả năng thanh toán hiện thời là 2.6284074.

3.3.2. Khả năng thanh toán tức thời (QR)

thongke_QR <- data3 %>%
  summarise(
    Trung_binh = mean(QR, na.rm = TRUE),
    Do_lech_chuan = sd(QR, na.rm = TRUE),
    Phuong_sai = var(QR, na.rm = TRUE),
    Q1 = quantile(QR, 0.25, na.rm = TRUE),
    Trung_vi = median(QR, na.rm = TRUE),
    Q3 = quantile(QR, 0.75, na.rm = TRUE),
    Do_lech = skewness(QR, na.rm = TRUE),
    Do_nhon = kurtosis(QR, na.rm = TRUE))
thongke_QR

Tương tự như phần trên, em dùng lệnh summarise để tính các đại lượng thống kê đại diện cho bộ dữ liệu em đang xét. Từ kết quả trên, em rút ra các nhận xét sau:

  • Trung bình là 0.4095636. Cho thấy khả năng thanh toán tức thời trung bình của một quý là 0.4095636.
  • Độ lệch chuẩn là 0.2226284. Cho thấy khả năng thanh toán tức thời có mức độ phân tán là 0.2226284.
  • Phương sai là 0.0495634. Cho thấy độ biến động của khả năng thanh toán tức thời là 0.0495634.
  • Tứ phân vị 1 (Q1) là 0.2225739. Cho thấy 25% số quan sát có khả năng thanh toán tức thời ≤ 0.2225739.
  • Trung vị là 0.3501892. Cho thấy giá trị khả năng thanh toán tức thời nằm ở giữa tập dữ liệu là 0.3501892.
  • Tứ phân vị 3 (Q3) là 0.5304932. Cho thấy 75% số quan sát có khả năng thanh toán tức thời ≤ 0.5304932.
  • Độ lệch (skewness) là 0.8141533. Cho thấy phân phối khả năng thanh toán tức thời nghiêng về một phía với độ lệch là 0.8141533.
  • Độ nhọn (kurtosis) là 2.5044118. Cho thấy mức độ tập trung của phân phối khả năng thanh toán tức thời là 2.5044118.

3.3.3. Tỷ số nợ (DR)

thongke_DR <- data3 %>%
  summarise(
    Trung_binh = mean(DR, na.rm = TRUE),
    Do_lech_chuan = sd(DR, na.rm = TRUE),
    Phuong_sai = var(DR, na.rm = TRUE),
    Q1 = quantile(DR, 0.25, na.rm = TRUE),
    Trung_vi = median(DR, na.rm = TRUE),
    Q3 = quantile(DR, 0.75, na.rm = TRUE),
    Do_lech = skewness(DR, na.rm = TRUE),
    Do_nhon = kurtosis(DR, na.rm = TRUE)
  )
thongke_DR

Tương tự như phần trên, em dùng lệnh summarise để tính các đại lượng thống kê đại diện cho bộ dữ liệu em đang xét. Từ kết quả trên, em rút ra các nhận xét sau:

  • Trung bình là 0.7249607. Cho thấy tỷ lệ nợ trung bình của một quý là 0.7249607.
  • Độ lệch chuẩn là 0.1707859. Cho thấy tỷ lệ nợ có mức độ phân tán là 0.1707859.
  • Phương sai là 0.0291678. Cho thấy độ biến động của tỷ lệ nợ là 0.0291678.
  • Tứ phân vị 1 (Q1) là 0.5638734. Cho thấy 25% số quan sát có tỷ lệ nợ ≤ 0.5638734.
  • Trung vị là 0.7170189. Cho thấy giá trị tỷ lệ nợ nằm ở giữa tập dữ liệu là 0.7170189.
  • Tứ phân vị 3 (Q3) là 0.9042747. Cho thấy 75% số quan sát có tỷ lệ nợ ≤ 0.9042747.
  • Độ lệch (skewness) là 0.0209367. Cho thấy phân phối tỷ lệ nợ nghiêng về một phía với độ lệch là 0.0209367.
  • Độ nhọn (kurtosis) là 1.3855506. Cho thấy mức độ tập trung của phân phối tỷ lệ nợ là 1.3855506.

3.3.4. Cấu trúc vốn (D/E)

thongke_DE <- data3 %>%
  summarise(
    Trung_binh = mean(DE, na.rm = TRUE),
    Do_lech_chuan = sd(DE, na.rm = TRUE),
    Phuong_sai = var(DE, na.rm = TRUE),
    Q1 = quantile(DE, 0.25, na.rm = TRUE),
    Trung_vi = median(DE, na.rm = TRUE),
    Q3 = quantile(DE, 0.75, na.rm = TRUE),
    Do_lech = skewness(DE, na.rm = TRUE),
    Do_nhon = kurtosis(DE, na.rm = TRUE)
  )
thongke_DE

Tương tự như phần trên, em dùng lệnh summarise để tính các đại lượng thống kê đại diện cho bộ dữ liệu em đang xét. Từ kết quả trên, em rút ra các nhận xét sau:

  • Trung bình là 6.1464381. Cho thấy hệ số nợ trên vốn chủ sở hữu trung bình của một quý là 6.1464381.
  • Độ lệch chuẩn là 7.8646499. Cho thấy hệ số nợ trên vốn chủ sở hữu có mức độ phân tán là 7.8646499.
  • Phương sai là 61.852718. Cho thấy độ biến động của hệ số nợ trên vốn chủ sở hữu là 61.852718.
  • Tứ phân vị 1 (Q1) là 1.2929123. Cho thấy 25% số quan sát có hệ số nợ trên vốn chủ sở hữu ≤ 1.2929123.
  • Trung vị là 2.5670109. Cho thấy giá trị hệ số nợ trên vốn chủ sở hữu nằm ở giữa tập dữ liệu là 2.5670109.
  • Tứ phân vị 3 (Q3) là 9.4535471. Cho thấy 75% số quan sát có hệ số nợ trên vốn chủ sở hữu ≤ 9.4535471.
  • Độ lệch (skewness) là 2.7822516. Cho thấy phân phối hệ số nợ trên vốn chủ sở hữu nghiêng về một phía với độ lệch là 2.7822516.
  • Độ nhọn (kurtosis) là 12.4924029. Cho thấy mức độ tập trung của phân phối hệ số nợ trên vốn chủ sở hữu là 12.4924029.

3.3.5. Khả năng sinh lời (ER)

thongke_ER <- data3 %>%
  summarise(
    Trung_binh = mean(ER, na.rm = TRUE),
    Do_lech_chuan = sd(ER, na.rm = TRUE),
    Phuong_sai = var(ER, na.rm = TRUE),
    Q1 = quantile(ER, 0.25, na.rm = TRUE),
    Trung_vi = median(ER, na.rm = TRUE),
    Q3 = quantile(ER, 0.75, na.rm = TRUE),
    Do_lech = skewness(ER, na.rm = TRUE),
    Do_nhon = kurtosis(ER, na.rm = TRUE))
thongke_ER

Tương tự như phần trên, em dùng lệnh summarise để tính các đại lượng thống kê đại diện cho bộ dữ liệu em đang xét. Từ kết quả trên, em rút ra các nhận xét sau:

  • Trung bình là 0.2750393. Cho thấy tỷ lệ vốn chủ sở hữu trung bình của một quý là 0.2750393.
  • Độ lệch chuẩn là 0.1707859. Cho thấy tỷ lệ vốn chủ sở hữu có mức độ phân tán là 0.1707859.
  • Phương sai là 0.0291678. Cho thấy độ biến động của tỷ lệ vốn chủ sở hữu là 0.0291678.
  • Tứ phân vị 1 (Q1) là 0.0957253. Cho thấy 25% số quan sát có tỷ lệ vốn chủ sở hữu ≤ 0.0957253.
  • Trung vị là 0.2829811. Cho thấy giá trị tỷ lệ vốn chủ sở hữu nằm ở giữa tập dữ liệu là 0.2829811.
  • Tứ phân vị 3 (Q3) là 0.4361266. Cho thấy 75% số quan sát có tỷ lệ vốn chủ sở hữu ≤ 0.4361266.
  • Độ lệch (skewness) là -0.0209367. Cho thấy phân phối tỷ lệ vốn chủ sở hữu nghiêng về một phía với độ lệch là -0.0209367.
  • Độ nhọn (kurtosis) là 1.3855506. Cho thấy mức độ tập trung của phân phối tỷ lệ vốn chủ sở hữu là 1.3855506.

3.3.6. Tỷ lệ nợ dài hạn (LDR)

thongke_LDR <- data3 %>%
  summarise(
    Trung_binh = mean(LDR, na.rm = TRUE),
    Do_lech_chuan = sd(LDR, na.rm = TRUE),
    Phuong_sai = var(LDR, na.rm = TRUE),
    Q1 = quantile(LDR, 0.25, na.rm = TRUE),
    Trung_vi = median(LDR, na.rm = TRUE),
    Q3 = quantile(LDR, 0.75, na.rm = TRUE),
    Do_lech = skewness(LDR, na.rm = TRUE),
    Do_nhon = kurtosis(LDR, na.rm = TRUE))
thongke_LDR

Tương tự như phần trên, em dùng lệnh summarise để tính các đại lượng thống kê đại diện cho bộ dữ liệu em đang xét. Từ kết quả trên, em rút ra các nhận xét sau:

  • Trung bình là 0.0437964. Cho thấy tỷ số nợ dài hạn trung bình của một quý là 0.0437964.
  • Độ lệch chuẩn là 0.050938. Cho thấy tỷ số nợ dài hạn có mức độ phân tán là 0.050938.
  • Phương sai là 0.0025947. Cho thấy độ biến động của tỷ số nợ dài hạn là 0.0025947.
  • Tứ phân vị 1 (Q1) là 0.0125215. Cho thấy 25% số quan sát có tỷ số nợ dài hạn ≤ 0.0125215.
  • Trung vị là 0.0237721. Cho thấy giá trị tỷ số nợ dài hạn nằm ở giữa tập dữ liệu là 0.0237721.
  • Tứ phân vị 3 (Q3) là 0.038463. Cho thấy 75% số quan sát có tỷ số nợ dài hạn ≤ 0.038463.
  • Độ lệch (skewness) là 1.8272406. Cho thấy phân phối tỷ số nợ dài hạn nghiêng về một phía với độ lệch là 1.8272406.
  • Độ nhọn (kurtosis) là 5.7791261. Cho thấy mức độ tập trung của phân phối tỷ số nợ dài hạn là 5.7791261.

3.3.7. Tỷ lệ tài sản cố định (FAR)

thongke_FAR <- data3 %>%
  summarise(
    Trung_binh = mean(FAR, na.rm = TRUE),
    Do_lech_chuan = sd(FAR, na.rm = TRUE),
    Phuong_sai = var(FAR, na.rm = TRUE),
    Q1 = quantile(FAR, 0.25, na.rm = TRUE),
    Trung_vi = median(FAR, na.rm = TRUE),
    Q3 = quantile(FAR, 0.75, na.rm = TRUE),
    Do_lech = skewness(FAR, na.rm = TRUE),
    Do_nhon = kurtosis(FAR, na.rm = TRUE))
thongke_FAR

Tương tự như phần trên, em dùng lệnh summarise để tính các đại lượng thống kê đại diện cho bộ dữ liệu em đang xét. Từ kết quả trên, em rút ra các nhận xét sau:

  • Trung bình là 0.152074. Cho thấy tỷ lệ tài sản cố định trung bình của một quý là 0.152074.
  • Độ lệch chuẩn là 0.034954. Cho thấy tỷ lệ tài sản cố định có mức độ phân tán là 0.034954.
  • Phương sai là 0.0012218. Cho thấy độ biến động của tỷ lệ tài sản cố định là 0.0012218.
  • Tứ phân vị 1 (Q1) là 0.1347092. Cho thấy 25% số quan sát có tỷ lệ tài sản cố định ≤ 0.1347092.
  • Trung vị là 0.1533672. Cho thấy giá trị tỷ lệ tài sản cố định nằm ở giữa tập dữ liệu là 0.1533672.
  • Tứ phân vị 3 (Q3) là 0.1664444. Cho thấy 75% số quan sát có tỷ lệ tài sản cố định ≤ 0.1664444.
  • Độ lệch (skewness) là -1.51301. Cho thấy phân phối tỷ lệ tài sản cố định nghiêng về một phía với độ lệch là -1.51301.
  • Độ nhọn (kurtosis) là 9.6885896. Cho thấy mức độ tập trung của phân phối tỷ lệ tài sản cố định là 9.6885896.

3.3.8. Khả năng thanh toán lãi vay (IR)

thongke_IR <- data3 %>%
  summarise(
    Trung_binh = mean(IR, na.rm = TRUE),
    Do_lech_chuan = sd(IR, na.rm = TRUE),
    Phuong_sai = var(IR, na.rm = TRUE),
    Q1 = quantile(IR, 0.25, na.rm = TRUE),
    Trung_vi = median(IR, na.rm = TRUE),
    Q3 = quantile(IR, 0.75, na.rm = TRUE),
    Do_lech = skewness(IR, na.rm = TRUE),
    Do_nhon = kurtosis(IR, na.rm = TRUE))
thongke_IR

Tương tự như phần trên, em dùng lệnh summarise để tính các đại lượng thống kê đại diện cho bộ dữ liệu em đang xét. Từ kết quả trên, em rút ra các nhận xét sau:

  • Trung bình là 0.4322605. Cho thấy tỷ lệ hàng tồn kho trung bình của một quý là 0.4322605.
  • Độ lệch chuẩn là 0.0589845. Cho thấy tỷ lệ hàng tồn kho có mức độ phân tán là 0.0589845.
  • Phương sai là 0.0034792. Cho thấy độ biến động của tỷ lệ hàng tồn kho là 0.0034792.
  • Tứ phân vị 1 (Q1) là 0.3877432. Cho thấy 25% số quan sát có tỷ lệ hàng tồn kho ≤ 0.3877432.
  • Trung vị là 0.4446334. Cho thấy giá trị tỷ lệ hàng tồn kho nằm ở giữa tập dữ liệu là 0.4446334.
  • Tứ phân vị 3 (Q3) là 0.4800563. Cho thấy 75% số quan sát có tỷ lệ hàng tồn kho ≤ 0.4800563.
  • Độ lệch (skewness) là -0.1979693. Cho thấy phân phối tỷ lệ hàng tồn kho nghiêng về một phía với độ lệch là -0.1979693.
  • Độ nhọn (kurtosis) là 2.3561357. Cho thấy mức độ tập trung của phân phối tỷ lệ hàng tồn kho là 2.3561357.

3.3.9. Tỷ suất sinh lời trên tài sản (ROA)

thongke_ROA <- data3 %>%
  summarise(
    Trung_binh = mean(ROA, na.rm = TRUE),
    Do_lech_chuan = sd(ROA, na.rm = TRUE),
    Phuong_sai = var(ROA, na.rm = TRUE),
    Q1 = quantile(ROA, 0.25, na.rm = TRUE),
    Trung_vi = median(ROA, na.rm = TRUE),
    Q3 = quantile(ROA, 0.75, na.rm = TRUE),
    Do_lech = skewness(ROA, na.rm = TRUE),
    Do_nhon = kurtosis(ROA, na.rm = TRUE))
thongke_ROA

Tương tự như phần trên, em dùng lệnh summarise để tính các đại lượng thống kê đại diện cho bộ dữ liệu em đang xét.Từ kết quả trên, em rút ra các nhận xét sau:

  • Trung bình là -0.0088436. Cho thấy tỷ suất sinh lời trên tài sản trung bình của một quý là -0.0088436.
  • Độ lệch chuẩn là 0.075482. Cho thấy tỷ suất sinh lời trên tài sản có mức độ phân tán là 0.075482.
  • Phương sai là 0.0056975. Cho thấy độ biến động của tỷ suất sinh lời trên tài sản là 0.0056975.
  • Tứ phân vị 1 (Q1) là -0.080074. Cho thấy 25% số quan sát có tỷ suất sinh lời trên tài sản ≤ -0.080074.
  • Trung vị là 0.0350306. Cho thấy giá trị tỷ suất sinh lời trên tài sản nằm ở giữa tập dữ liệu là 0.0350306.
  • Tứ phân vị 3 (Q3) là 0.0486927. Cho thấy 75% số quan sát có tỷ suất sinh lời trên tài sản ≤ 0.0486927.
  • Độ lệch (skewness) là -0.7589606. Cho thấy phân phối tỷ suất sinh lời trên tài sản nghiêng về một phía với độ lệch là -0.7589606.
  • Độ nhọn (kurtosis) là 2.008054. Cho thấy mức độ tập trung của phân phối tỷ suất sinh lời trên tài sản là 2.008054.

CHƯƠNG 4: TRỰC QUAN HÓA DỮ LIỆU

4.1. Biểu đồ xu hướng của các chỉ tiêu ROA và FAR

roa_year <- data3 %>%
  group_by(Nam) %>% summarise(ROA = mean(ROA, na.rm = TRUE)) %>%
  mutate(Nam = as.numeric(Nam)) %>% drop_na(Nam, ROA) %>%
  mutate(nudge_x = ifelse(Nam %in% c(2018, 2019), -0.55,
              ifelse(Nam %in% c(2021, 2022, 2023), 0,
                     -0.3)),nudge_y = ifelse(Nam %in% c(2018, 2019), 0.006,
              ifelse(Nam %in% c(2021, 2022, 2023), 0.015,0.001)))
mean_roa <- mean(data3$ROA, na.rm = TRUE)
p_roa <- ggplot(roa_year, aes(Nam, ROA)) +
  geom_line(color = "#1f77b4", linewidth = 1) +geom_point(
    size = 1.5, color = "#1f77b4") + geom_text(aes(label = round(ROA, 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_roa, color = "red", linetype = "dashed", linewidth = 0.8) +
  annotate("text",x = max(roa_year$Nam) - 0.5, y = mean_roa + 0.005,label = paste0(
    "ROA trung bình = ", round(mean_roa, 3)),
           color = "red", fontface = "bold", size = 2.5,hjust = 3, vjust = 0) +
  geom_rug(alpha = 0.2) + labs(
    title = "Tỷ lệ sinh lời trung bình", x = "Năm", y = "ROA") + scale_x_continuous(
      breaks = unique(roa_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())
far_year <- data3 %>% group_by(Nam) %>% summarise(FAR = mean(FAR, na.rm = TRUE)) %>%
  mutate(Nam = as.numeric(Nam)) %>% drop_na(Nam, FAR) %>%
  mutate(nudge_x = ifelse(Nam %in% c(2016, 2017,2018, 2020), -0.41,   
              ifelse(Nam %in% c(2021, 2024), 0, 0)),nudge_y = ifelse(
                Nam %in% c(2016, 2017,2018, 2020), 0,      
              ifelse(Nam %in% c(2021, 2024), 0.003,-0.001)))
mean_far <- mean(data3$FAR, na.rm = TRUE)
p_far <- ggplot(far_year, aes(Nam, FAR)) +
  geom_line(color = "#1f77b4", linewidth = 1) +
  geom_point(size = 1.5, color = "#1f77b4") +
  geom_text(aes(label = round(FAR, 3),nudge_x = nudge_x,nudge_y = nudge_y),  
    vjust = 1,size = 3,fontface = "bold",check_overlap = TRUE) + 
  geom_smooth(se = FALSE, color = "#2ca02c", linetype = "dashed", linewidth = 0.7) +
  geom_hline(
    yintercept = mean_far, color = "red", linetype = "dashed", linewidth = 0.8) +
  annotate("text", x = max(far_year$Nam) - 0.5, y = mean_far + 0.001,
           label = paste0("FAR trung bình = ", round(mean_far, 3)),
           color = "red", fontface = "bold", size = 2.5,hjust = 0.7, vjust = 0) + 
  geom_rug(alpha = 0.2) + labs(
    title = "Tỷ lệ tài sản cố định trung bình", x = "Năm", y = "FAR") +
  scale_x_continuous(breaks = unique(far_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_roa + p_far + plot_layout(ncol = 2) + plot_annotation(
    title = "Biểu đồ các chỉ số tài chính (ROA, FAR) 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

  • Câu lệnh 1 và 26 dùng để chuẩn bị dữ liệu ở định dạng phù hợp với biểu đồ đường theo các năm. Ngoài ra, câu lệnh còn dùng chỉnh vị trí của các số liệu tại những năm bị trùng lắp với đồ thị.
  • Câu lệnh 8 và 32 dùng để tính ROA, FAR trung bình theo từng năm.
  • Câu lệnh 9 và 33 dùng để tạo biểu đồ đường thẳng kết hợp với mức ROA, FAR trung bình nhằm mục đích xác định xu hướng của hai chỉ tiêu CR và FAR.
  • Câu lệnh 51 dùng để tạo nhóm chứa các biểu đồ đường thẳng kết hợp với mức ROA, FAR trung bình theo bố cục mà em mong muốn.

Nhận xét

Giai đoạn 2015–2024 cho thấy biến động mạnh mẽ trong hiệu quả hoạt động của doanh nghiệp CMX. Từ chỉ số ROA phản ánh sự thua lỗ nghiêm trọng (đỉnh điểm -0,127 năm 2017) trước khi chuyển sang giai đoạn phục hồi ấn tượng và đạt đỉnh 0,054 vào năm 2023. Tuy nhiên, sự sụt giảm mạnh của ROA xuống còn 0,01 trong năm 2024 đã làm gián đoạn đà cải thiện hiệu suất và là một tín hiệu cảnh báo về khả năng duy trì lợi nhuận trong tương lai của doanh nghiệp Camimex Group.

Về cơ cấu tài sản, CMX duy trì tỷ lệ tài sản cố định (FAR) linh hoạt (trung bình 15,2%). Việc FAR giảm trong giai đoạn 2022–2023, trùng với lúc ROA đạt đỉnh, cho thấy doanh nghiệp đã tối ưu hóa tài sản cố định để nâng cao hiệu suất. Tuy nhiên, sự gia tăng nhẹ của FAR năm 2024 mà không đi kèm với cải thiện ROA cho thấy nguy cơ về sự kém hiệu quả của các khoản đầu tư mới. Do vậy, doanh nghiệp cần rà soát nguyên nhân sụt giảm lợi nhuận và đánh giá lại các quyết định tài sản gần đây để đảm bảo chúng đóng góp tích cực vào khả năng sinh lời trong tương lai.

4.2. Biểu đồ Histogram và đường mật độ của các chỉ tiêu kinh tế

p1 <- ggplot(data3, aes(x = CR)) +geom_histogram(
  aes(y = ..density..), fill = "lightblue", color = "black", bins = 7, alpha = 0.6) + 
  geom_density(color = "red", linewidth = 1.2) +
  labs(title = "CR",x = "Giá trị CR", 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 = QR)) + geom_histogram(
  aes(y = ..density..),fill = "lightblue",color = "black",bins = 7,alpha = 0.6) +
  geom_density(color = "red", linewidth = 1.2) +
  labs(title = "QR",x = "Giá trị QR",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 = DR)) + geom_histogram(
  aes(y = ..density..),fill = "lightblue",color = "black",bins = 7,alpha = 0.6) +
  geom_density(color = "red", linewidth = 1.2) +
  labs(title = "DR",x = "Giá trị DR",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 = DE)) + geom_histogram(
  aes(y = ..density..),fill = "lightblue",color = "black",bins = 7,alpha = 0.6) +
  geom_density(color = "red", linewidth = 1.2) +
  labs(title = "DE",x = "Giá trị DE",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 = "Biểu đồ Histogram và đường mật độ của CR, QR, DR và DE theo thời gian",
    theme = theme(
      plot.title = element_text(hjust = 0.5, face = "bold", size = 14)))
p_his

Giải thích

  • Câu lệnh 1,6,11 và 16 dùng để tạo biểu đồ Histogramđường mật độ lần lượt của các chỉ tiêu CR, QR, DR và DE
  • Câu lệnh 21 dùng để tạo nhóm chứa các biểu đồ Histogramđường mật độ lần lượt của các chỉ tiêu kinh tế theo bố cục mà em mong muốn.

Nhận xét

Từ biểu đồ mật độ của các chỉ số tài chính cho thấy cấu trúc vốn của CMX linh hoạt nhưng không ổn định. CR tập trung quanh mức 1,0 điều này thể hiện thanh khoản hiện hành ở mức vừa phải. Ngược lại, QR dồn về 0,3, báo hiệu rủi ro thanh khoản nhanh cao do khả năng thanh toán ngắn hạn bị hạn chế khi loại bỏ hàng tồn kho. Mặt khác, DR có sự phân bố hai đỉnh rõ rệt tại 0,6 và 0,9, phản ánh sự biến động lớn về mức độ đòn bẩy và tiềm ẩn rủi ro tài chính cao trong các giai đoạn phụ thuộc nhiều vào nợ. Mặc dù DE thường giữ dưới 10 nhưng doanh nghiệp cần kiểm soát chặt chẽ tỷ lệ nợ cao và cải thiện QR để tăng cường tính an toàn và ổn định tài chính trong dài hạn.

4.3. Biểu đồ Box - plot kết hợp Violin của các chỉ tiêu kinh tế

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_CR  <- plot_violin_box("CR")
p_QR  <- plot_violin_box("QR")
p_DR  <- plot_violin_box("DR")
p_DE  <- plot_violin_box("DE")
final_plot <- (p_CR + p_QR) / (p_DR + p_DE) +
  plot_annotation(
    title = "Phân phối các chỉ tiêu 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

  • Câu lệnh 1 dùng để tạo hàm vẽ đồ thị mà tụi em mong muốn
  • Câu lệnh 4 dùng để tạo biểu đồ Box - plotbiểu đồ Violin
  • Câu lệnh 5 dùng để vẽ biểu đồ Violin
  • Câu lệnh 7 dùng để vẽ biểu đồ Box - plot
  • Câu lệnh 17 dùng để tạo nhóm chứa các biểu đồ Box - plotbiểu đồ Violin của các chỉ tiêu kinh tế theo bố cục mà em mong muốn.

Nhận xét

Từ biểu đồ Violin kết hợp Box plot cho thấy CR và QR của CMX dao động ở mức trung bình (với trung vị lần lượt khoảng 1,0 và 0,4), phản ánh khả năng thanh toán ngắn hạn ổn định nhưng chưa thực sự dồi dào, đặc biệt khi loại bỏ hàng tồn kho. Ngược lại, DR và DE cho thấy mức độ đòn bẩy tài chính cao, với trung vị DR khoảng 0,7 và sự xuất hiện của hai điểm ngoại lai ở DE (lên tới 40), cho thấy những giai đoạn doanh nghiệp phụ thuộc mạnh vào nợ vay. Điều này tiềm ẩn rủi ro tài chính nếu dòng tiền bị gián đoạn hoặc chi phí vốn gia tăng, đòi hỏidoanh nghiệp cần tối ưu cơ cấu nguồn vốn và tăng cường quản trị thanh khoản trong dài hạn.

4.4. Biểu đồ Pie - chart đạt ngưỡng an toàn của các chỉ tiêu kinh tế theo quý

Như ở phần mã hóa dữ liệu em đã trình bày, em đã phân loại các chỉ tiêu tài chính them một ngưỡng. Với mức 1 là mức Đạt, và mức 0 là mức không đạt.

data3$Quy <- as.numeric(data3$Quy)
muc_do_cols <- c("Muc_do_CR", "Muc_do_QR", "Muc_do_DR", "Muc_do_DE")
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 = 3) +
    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_CR <- plot_pie_for_col("Muc_do_CR")
p_QR <- plot_pie_for_col("Muc_do_QR")
p_DR <- plot_pie_for_col("Muc_do_DR")
p_DE <- plot_pie_for_col("Muc_do_DE")
final_plot <- (p_CR + p_QR) / (p_DR + p_DE) + 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ệ an toàn tài chính theo quý",
    theme = theme(plot.title = element_text(hjust = 0.5, face = "bold", size = 12)))

Giải thích

  • Câu lệnh 1 chuyển kiểu dữ liệu sang dạng numberic
  • Câu lệnh 3 dùng để tạo hàm vẽ đồ thị của dữ liệu mà tụi em mong muốn
  • Câu lệnh 14 dùng để tạo biểu đồ Pie - chart
  • Câu lệnh 16 dùng để tạo chú giải chung cho cả 4 đồ thị khi nhóm lại
  • Câu lệnh 28 dùng để tạo nhóm chứa các biểu đồ Pie - chart của các chỉ tiêu kinh tế theo bố cục mà em mong muốn.

Nhận xét

Biểu đồ tỷ lệ an toàn tài chính theo quý cho thấy Camimex Group duy trì sự ổn định tương đối trong cơ cấu thanh khoản và đòn bẩy. Trong đó, CR và QR có sự biến động nhẹ giữa các quý, phản ánh khả năng thanh toán ngắn hạn được kiểm soát linh hoạt nhưng chưa đồng đều. Trong khi đó, DR và DE phân bổ đều ở mức 25% mỗi quý, cho thấy doanh nghiệp áp dụng chiến lược tài chính cân bằng, không tập trung rủi ro vào một quý cụ thể. Tuy nhiên, việc thiếu sự khác biệt rõ rệt giữa các quý cho thấy khả năng quản trị tài chính còn mang tính chu kỳ hoặc thiếu điều chỉnh chủ động theo biến động thị trường. Chính vì vậy, doanh nghiệp cần tăng cường phân tích theo mùa vụ để tối ưu hóa dòng tiền và giảm thiểu rủi ro tài chính.

4.5. Ma trận hệ số tương quan của các chỉ tiêu tài chính

chi_tieu_list <- c("IR", "ER", "ROA", "FAR")
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

  • Câu lệnh 2 chuẩn bị dữ liệu về các chỉ tiêu
  • Câu lệnh 3 dùng để tính ma trận hệ số tương quan
  • Câu lệnh 4 dùng để chuyển ma trận về dạng dài để ggplot vẽ được
  • Câu lệnh 5 dùng để vẽ ma trận hệ số tương quan
  • Câu lệnh 16 dùng để đảm bảo ô vuông có tỷ lệ 1:1

Nhận xét

Từ ma trận tương quan trên, cho thấy ER có mối tương quan thuật rất mạnh với ROA là 0,81 điều này cho thấy hiệu quả sinh lời của CMX tăng khi doanh nghiệp dựa nhiều hơn vào vốn chủ sở hữu thay vì nợ. Từ đó, ta có thể thấy cấu trúc tài chính của doanh nghiệp này rất tốt. Ngược lại, IRvà ROA tương quan âm với nhau với hệ số tương quan là –0,53, điều này ngụ ý rằng các giai đoạn đầu tư mở rộng thường đi kèm suy giảm lợi nhuận ngắn hạn. Ngoài ra, FAR cũng có tương quan âm với cả ROA và ER, cho thấy việc gia tăng tài sản cố định có thể làm giảm hiệu suất sử dụng vốn và gia tăng đòn bẩy. Từ kết quả này, ta có thể nhấn mạnh nhu cầu cân đối giữa đầu tư, cơ cấu vốn và hiệu quả sinh lời trong chiến lược tài chính dài hạn của doanh nghiệp Camimex Group.

4.6. Biểu đồ cột đôi số lần đạt và không đạt ngưỡng của các chỉ tiêu tài chính

muc_do_vars <- c("Muc_do_IR", "Muc_do_ER", "Muc_do_ROA", "Muc_do_FAR")
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_ROA" = "ROA","Muc_do_IR"  = "IR",
      "Muc_do_FAR" = "FAR","Muc_do_ER"  = "ER")) %>%
  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

  • Câu lệnh 2 chuẩn bị dữ liệu về các chỉ tiêu
  • Câu lệnh 7 dùng để bổ sung các tổ hợp bị thiếu, tránh chồng lấp cột
  • Câu lệnh 8 dùng để vẽ biểu đồ cột đôi cho số lần đạt và không đạt của các chỉ tiêu tài chính

Nhận xét

Từ biểu đồ cột đôi trên, ta thấy số lần đạt và không đạt ngưỡng của FAR dẫn đầu với 40 lần đạt, điều này cho thấy doanh nghiệp duy trì ổn định tỷ lệ tài sản cố định. Trong khi đó, ROA và IR có số lần không đạt cao hơn đạt (23 và 17; 19 và 21), phản ánh hiệu quả sinh lời và đầu tư chưa đồng đều qua thời gian. Mặt khác, ER cân bằng giữa số lần đạt và không đạt (20/20), cho thấy cơ cấu vốn chủ sở hữu được kiểm soát ổn định. Điều này cho thấy Camimex Group có nền tảng tài sản vững chắc. Do đó, doanh nghiệp này cần cải thiện hiệu suất hoạt động và chiến lược đầu tư để nâng cao khả năng sinh lời trong tương lai.

4.7. Biểu đồ mật độ của các chỉ tiêu tài chính theo quý

indicators <- c("IR", "ER", "ROA", "FAR")
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 = c(1, 2, 3, 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_IR  <- plot_density_for_indicator("IR")
p_ER  <- plot_density_for_indicator("ER")
p_ROA <- plot_density_for_indicator("ROA")
p_FAR <- plot_density_for_indicator("FAR")
final_plot <- (p_IR + p_ER) / (p_ROA + p_FAR) + plot_layout(guides = "collect") +          
  plot_annotation(title = "Phân bố mật độ các chỉ tiêu 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

  • Câu lệnh 2 chuẩn bị dữ liệu về các chỉ tiêu
  • Câu lệnh 6 dùng để tạo hàm vẽ biểu đồ mật độ theo quý cho các chỉ tiêu.
  • Câu lệnh 8 dùng để vẽ biểu đồ mật độ cho các chỉ tiêu tài chính
  • Câu lệnh 22 dùng để tạo nhóm chứa các biểu đồ mật độ của các chỉ tiêu kinh tế theo bố cục mà em mong muốn

Nhận xét

Từ đồ thị phân bố mật độ theo quý, ta có thể thấy IR và ROA có sự biến động rõ rệt giữa các quý, đặc biệt tập trung cao ở quý 3, điều này phản ánh khả năng sinh lời và hiệu quả đầu tư của CMX đạt đỉnh vào giai đoạn này. Ngược lại, ER duy trì phân bố ổn định qua các quý, cho thấy chiến lược tài chính thận trọng với cơ cấu vốn chủ sở hữu được kiểm soát bền vững.Mặt khác, FAR thường dao động trong khoảng từ 0.1 đến 0.2, với đỉnh mật độ nổi bật ở quý 1, điều này ngụ ý rằng doanh nghiệp thường triển khai đầu tư tài sản cố định vào đầu năm. Sự không đồng đều trong phân bố theo quý giữa các chỉ số cho thấy CMX cần chủ động hơn trong việc điều chỉnh kế hoạch tài chính theo chu kỳ mùa vụ, đồng thời tăng cường giám sát hiệu suất ở các quý có hoạt động đầu tư cao để đảm bảo cân bằng giữa tăng trưởng và ổn định tài chính dài hạn.