1 CHƯƠNG 1: PHÂN TÍCH HÀNH VI MUA SẮM TRỰC TUYẾN CỦA KHÁCH HÀNG

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

1.1.1 Bối cảnh & câu hỏi nghiên cứu

Bối cảnh Bộ dữ liệu ghi nhận hệ sinh thái hành vi mua sắm trên một nền tảng bán lẻ trực tuyến ở cấp mục giao dịch (item-level). Mỗi bản ghi gắn người mua (UserID, nhân khẩu học, quốc gia), sản phẩm (ProductID, tên, ngành hàng, giá), diễn biến giao dịch (thời điểm mua, số lượng, chiết khấu, thành tiền) cùng các tín hiệu trải nghiệm số (thời lượng phiên, thiết bị, nguồn giới thiệu) và phản hồi sau mua (điểm và văn bản đánh giá). Cấu trúc này cho phép chúng ta quan sát đồng thời động lực doanh thu, hiệu quả khuyến mãi, khác biệt hành vi đa kênh, và cảm nhận khách hàng trên một dòng thời gian đủ dài.

Mục tiêu kinh doanh

-Phác họa chân dung khách hàng theo giới tính, độ tuổi, quốc gia và thiết bị — để nhận diện phân khúc có giá trị cao.

-Giải phẫu cấu phần doanh thu: vai trò của Price, Quantity, danh mục (Category) và mức ưu đãi (DiscountRate, HasDiscountApplied) đối với TotalAmount.

-Đo lường hiệu quả khuyến mãi: chiết khấu có tăng doanh thu theo đơn hay chỉ chuyển dịch từ biên lợi nhuận? Sự khác biệt theo ngành hàng/kênh?

-Phân tích theo thời gian: mùa vụ, nhịp mua theo tháng/quý/ngày trong tuần/giờ trong ngày, và hành vi theo cohort đăng ký (SignUpDate).

-Góc nhìn trải nghiệm & cảm nhận: liên hệ giữa điểm đánh giá (ReviewScore) và sắc thái văn bản (ReviewText), kiểm tra bất nhất và tác động đến quay lại mua.

-Khuyến nghị vận hành: chiến lược giá–khuyến mãi, ưu tiên danh mục, tối ưu kênh/thiết bị, và gợi ý phân bổ ngân sách marketing.

Giá trị kỳ vọng

Kết quả không chỉ dừng ở mô tả, mà hướng tới bằng chứng định lượng cho quyết định kinh doanh: xác định nhóm khách hàng trọng tâm, khung ưu đãi hiệu quả theo ngành hàng, khung giờ “vàng” cho chuyển đổi, và những nút thắt trải nghiệm cần cải thiện.

1.1.2 Mô tả & chất lượng dữ liệu

1.1.2.1 Thống kê kích thước

library(readr)
dataecommerce <- read_csv(file.choose())
## Rows: 100000 Columns: 21
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## chr  (8): UserName, Gender, Country, ProductName, Category, ReviewText, Devi...
## dbl  (9): UserID, Age, ProductID, Price, Quantity, TotalAmount, DiscountRate...
## lgl  (1): HasDiscountApplied
## dttm (1): LastLogin
## date (2): SignUpDate, PurchaseDate
## 
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
dim(dataecommerce)
## [1] 100000     21

Giải thích kỹ thuật:

library(readr) giúp sử dụng hàm read_csv() của gói readr – công cụ nhanh và an toàn để đọc file CSV.

file.choose() cho phép người dùng chọn trực tiếp file dữ liệu trên máy tính (trực quan, dễ dùng trong tiểu luận).

Hàm dim() trả về một vector gồm 2 phần tử:

Phần tử thứ nhất: số lượng quan sát (rows)

Phần tử thứ hai: số lượng biến (columns)

Ý nghĩa thống kê:

Bộ dữ liệu dataecommerce gồm 100.000 quan sát và 21 biến, phản ánh quy mô lớn, đủ để thực hiện các phân tích thống kê mô tả và đa biến với độ tin cậy cao.

1.1.2.2 Danh mục biến

library(dplyr)
## 
## Attaching package: 'dplyr'
## The following objects are masked from 'package:stats':
## 
##     filter, lag
## The following objects are masked from 'package:base':
## 
##     intersect, setdiff, setequal, union
colnames(dataecommerce)
##  [1] "UserID"             "UserName"           "Age"               
##  [4] "Gender"             "Country"            "SignUpDate"        
##  [7] "ProductID"          "ProductName"        "Category"          
## [10] "Price"              "PurchaseDate"       "Quantity"          
## [13] "TotalAmount"        "HasDiscountApplied" "DiscountRate"      
## [16] "ReviewScore"        "ReviewText"         "LastLogin"         
## [19] "SessionDuration"    "DeviceType"         "ReferralSource"

Giải thích kỹ thuật:

library(dplyr): dùng để nạp gói dplyr – một trong những gói quan trọng nhất của hệ sinh thái tidyverse, hỗ trợ thao tác dữ liệu như chọn cột, lọc hàng, nhóm, sắp xếp, tóm tắt... rất tiện lợi và trực quan.

colnames(): là hàm cơ bản trong R giúp liệt kê tất cả các tên biến (cột) của bộ dữ liệu.

Ý nghĩa:

Bộ dữ liệu dataecommerce gồm 21 biến, thể hiện đầy đủ các khía cạnh chính của hoạt động thương mại điện tử: thông tin người dùng, sản phẩm, giao dịch, hành vi truy cập, và đánh giá sau mua.
library(dplyr)
library(knitr)
library(kableExtra)

data_dict <- tibble(
  Tên_biến = c("UserID", "UserName", "Age", "Gender", "Country", "SignUpDate", 
                "ProductID", "ProductName", "Category", "Price", "PurchaseDate", 
                "Quantity", "TotalAmount", "HasDiscountApplied", "DiscountRate", 
                "ReviewScore", "ReviewText", "LastLogin", "SessionDuration", 
                "DeviceType", "ReferralSource"),
  Ý_nghĩa = c(
    "Mã định danh của người dùng",
    "Tên hiển thị của người dùng",
    "Tuổi của người dùng (năm)",
    "Giới tính của người dùng",
    "Quốc gia cư trú",
    "Ngày người dùng đăng ký tài khoản",
    "Mã định danh của sản phẩm",
    "Tên sản phẩm",
    "Ngành hàng hoặc nhóm sản phẩm",
    "Giá niêm yết của sản phẩm",
    "Ngày diễn ra giao dịch mua hàng",
    "Số lượng sản phẩm được mua",
    "Tổng giá trị của giao dịch",
    "Có áp dụng khuyến mãi hay không",
    "Tỷ lệ chiết khấu ",
    "Điểm đánh giá sản phẩm ",
    "Nội dung đánh giá bằng văn bản",
    "Thời điểm người dùng đăng nhập gần nhất",
    "Thời lượng phiên truy cập (giây hoặc phút)",
    "Loại thiết bị truy cập (Mobile, Desktop, Tablet, …)",
    "Nguồn giới thiệu hoặc kênh truy cập"
  )
)

# Bảng đẹp, có màu và căn giữa
kable(data_dict, format = "html", caption = "Bảng 1. Danh mục biến trong bộ dữ liệu e-commerce", align = "cc") %>%
  kable_styling(
    bootstrap_options = c("striped", "hover", "condensed"),
    full_width = FALSE,
    position = "center",
    font_size = 13
  ) %>%
  row_spec(0, bold = TRUE, color = "white", background = "#3c8dbc") %>%  # Dòng tiêu đề
  column_spec(1, width = "20em", bold = TRUE, color = "#2c3e50", background = "#f2f6fc") %>%
  column_spec(2, width = "40em", color = "#2c3e50") %>%
  kable_classic(full_width = FALSE, html_font = "Times New Roman")
Bảng 1. Danh mục biến trong bộ dữ liệu e-commerce
Tên_biến Ý_nghĩa
UserID Mã định danh của người dùng
UserName Tên hiển thị của người dùng
Age Tuổi của người dùng (năm)
Gender Giới tính của người dùng
Country Quốc gia cư trú
SignUpDate Ngày người dùng đăng ký tài khoản
ProductID Mã định danh của sản phẩm
ProductName Tên sản phẩm
Category Ngành hàng hoặc nhóm sản phẩm
Price Giá niêm yết của sản phẩm
PurchaseDate Ngày diễn ra giao dịch mua hàng
Quantity Số lượng sản phẩm được mua
TotalAmount Tổng giá trị của giao dịch
HasDiscountApplied Có áp dụng khuyến mãi hay không
DiscountRate Tỷ lệ chiết khấu
ReviewScore Điểm đánh giá sản phẩm
ReviewText Nội dung đánh giá bằng văn bản
LastLogin Thời điểm người dùng đăng nhập gần nhất
SessionDuration Thời lượng phiên truy cập (giây hoặc phút)
DeviceType Loại thiết bị truy cập (Mobile, Desktop, Tablet, …)
ReferralSource Nguồn giới thiệu hoặc kênh truy cập

1.1.2.3 Kiểu dữ liệu từng biến

str(dataecommerce)
## spc_tbl_ [100,000 × 21] (S3: spec_tbl_df/tbl_df/tbl/data.frame)
##  $ UserID            : num [1:100000] 1 2 3 4 5 6 7 8 9 10 ...
##  $ UserName          : chr [1:100000] "User_1" "User_2" "User_3" "User_4" ...
##  $ Age               : num [1:100000] 39 25 43 44 23 54 64 59 50 47 ...
##  $ Gender            : chr [1:100000] "Male" "Female" "Male" "Male" ...
##  $ Country           : chr [1:100000] "UK" "Canada" "Canada" "Germany" ...
##  $ SignUpDate        : Date[1:100000], format: "2021-02-01" "2020-12-04" ...
##  $ ProductID         : num [1:100000] 8190 9527 3299 8795 1389 ...
##  $ ProductName       : chr [1:100000] "Shoes" "T-shirt" "Headphones" "T-shirt" ...
##  $ Category          : chr [1:100000] "Books" "Accessories" "Apparel" "Apparel" ...
##  $ Price             : num [1:100000] 532.4 848.8 64.9 465.1 331.8 ...
##  $ PurchaseDate      : Date[1:100000], format: "2021-02-25" "2021-09-22" ...
##  $ Quantity          : num [1:100000] 1 1 2 2 1 1 2 4 1 1 ...
##  $ TotalAmount       : num [1:100000] 532 849 130 930 332 ...
##  $ HasDiscountApplied: logi [1:100000] FALSE TRUE FALSE FALSE FALSE FALSE ...
##  $ DiscountRate      : num [1:100000] 0.02 0.29 0.03 0.23 0.02 0.21 0.06 0.34 0.48 0.25 ...
##  $ ReviewScore       : num [1:100000] 5.1 5.1 3.2 4.3 5.1 4.6 3.7 3.6 4.8 5.2 ...
##  $ ReviewText        : chr [1:100000] "Excellent" "Excellent" "Good" "Good" ...
##  $ LastLogin         : POSIXct[1:100000], format: "2024-05-03 04:04:27" "2024-08-31 04:04:27" ...
##  $ SessionDuration   : num [1:100000] 45 13.8 59.1 55.4 15 ...
##  $ DeviceType        : chr [1:100000] "Mobile" "Mobile" "Tablet" "Desktop" ...
##  $ ReferralSource    : chr [1:100000] "Social Media" "Social Media" "Organic Search" "Email Marketing" ...
##  - attr(*, "spec")=
##   .. cols(
##   ..   UserID = col_double(),
##   ..   UserName = col_character(),
##   ..   Age = col_double(),
##   ..   Gender = col_character(),
##   ..   Country = col_character(),
##   ..   SignUpDate = col_date(format = ""),
##   ..   ProductID = col_double(),
##   ..   ProductName = col_character(),
##   ..   Category = col_character(),
##   ..   Price = col_double(),
##   ..   PurchaseDate = col_date(format = ""),
##   ..   Quantity = col_double(),
##   ..   TotalAmount = col_double(),
##   ..   HasDiscountApplied = col_logical(),
##   ..   DiscountRate = col_double(),
##   ..   ReviewScore = col_double(),
##   ..   ReviewText = col_character(),
##   ..   LastLogin = col_datetime(format = ""),
##   ..   SessionDuration = col_double(),
##   ..   DeviceType = col_character(),
##   ..   ReferralSource = col_character()
##   .. )
##  - attr(*, "problems")=<externalptr>

Giải thích kỹ thuật:

library(dplyr): nạp gói hỗ trợ xử lý dữ liệu dạng bảng, cho phép xem, chọn và biến đổi dữ liệu dễ dàng.

str() là hàm cơ bản trong R dùng để hiển thị cấu trúc của đối tượng dữ liệu.

Nó cho biết:

  -Tên từng biến

  -Kiểu dữ liệu của biến (numeric, character, date, logical, factor, …)

  -Một vài giá trị mẫu để nhận diện nội dung.

Ý nghĩa:

Nhìn chung, dữ liệu bao gồm cả biến định lượng, định tính, nhị phân và thời gian, phản ánh đầy đủ các khía cạnh của hành vi mua sắm trực tuyến.

-chr (8 biến): chứa thông tin định tính như tên, giới tính, quốc gia, loại thiết bị, nội dung đánh giá.

-dbl (9 biến): là các biến số như tuổi, giá, số lượng, tổng tiền, điểm đánh giá, thời lượng phiên.

-lgl (1 biến): biến nhị phân HasDiscountApplied, cho biết giao dịch có áp dụng giảm giá hay không.

-dttm (1 biến): biến thời gian chi tiết LastLogin, ghi lại thời điểm đăng nhập gần nhất.

-date (2 biến): SignUpDate và PurchaseDate, thể hiện ngày đăng ký tài khoản và ngày mua hàng.

1.1.2.4 Kiểm tra dữ liệu thiếu

colSums(is.na(dataecommerce))
##             UserID           UserName                Age             Gender 
##                  0                  0                  0                  0 
##            Country         SignUpDate          ProductID        ProductName 
##                  0                  0                  0                  0 
##           Category              Price       PurchaseDate           Quantity 
##                  0                  0                  0                  0 
##        TotalAmount HasDiscountApplied       DiscountRate        ReviewScore 
##                  0                  0                  0                  0 
##         ReviewText          LastLogin    SessionDuration         DeviceType 
##                  0                  0                  0                  0 
##     ReferralSource 
##                  0

Giải thích kỹ thuật:

Hàm is.na() xác định các giá trị bị thiếu trong từng cột, còn colSums() tính tổng số giá trị NA của mỗi biến.

Nhận xét:

Kết quả cho thấy không có giá trị bị thiếu (NA) trong toàn bộ bộ dữ liệu dataecommerce.

1.1.2.5 Kiểm tra dữ liệu trùng lặp

# Kiểm tra số lượng dòng trùng lặp hoàn toàn
sum(duplicated(dataecommerce))
## [1] 0
# Kiểm tra trùng lặp theo UserID + PurchaseDate + ProductID
sum(duplicated(dataecommerce[, c("UserID", "PurchaseDate", "ProductID")]))
## [1] 0

Giải thích kỹ thuật:

duplicated(): hàm trong R dùng để xác định các dòng trùng lặp.

sum(duplicated(dataecommerce)): đếm tổng số dòng trùng lặp hoàn toàn giống nhau.

duplicated(dataecommerce[, c("UserID", "PurchaseDate", "ProductID")]): kiểm tra các trường hợp một người dùng mua cùng một sản phẩm trong cùng ngày, được xem là trùng khóa giao dịch.

Cách làm này giúp nhận biết xem dữ liệu có bị nhập trùng hay không.

Nhận xét:

Kết quả cho thấy không có dòng trùng lặp trong bộ dữ liệu.

Điều này chứng tỏ dữ liệu được ghi nhận duy nhất cho mỗi giao dịch, phản ánh chính xác hoạt động mua sắm thực tế của người dùng và đảm bảo tính toàn vẹn dữ liệu trước khi thực hiện các phân tích thống kê ở phần sau.

1.1.2.6 Kiểm tra giá trị bất thường

Sau khi xác định các giá trị nằm ngoài miền hợp lệ, bước tiếp theo là kiểm tra tính logic giữa các biến nhằm đảm bảo dữ liệu được ghi nhận đúng theo mối quan hệ kinh tế và quy trình giao dịch thực tế, từ đó tăng độ tin cậy cho các phân tích ở các phần sau.

summary(dataecommerce[, c("Age", "Price", "Quantity", "DiscountRate")])
##       Age            Price           Quantity      DiscountRate   
##  Min.   :18.00   Min.   :  10.0   Min.   :1.000   Min.   :0.0000  
##  1st Qu.:31.00   1st Qu.: 257.1   1st Qu.:1.000   1st Qu.:0.1300  
##  Median :43.00   Median : 505.9   Median :2.000   Median :0.2500  
##  Mean   :43.46   Mean   : 505.6   Mean   :2.496   Mean   :0.2498  
##  3rd Qu.:56.00   3rd Qu.: 753.7   3rd Qu.:4.000   3rd Qu.:0.3700  
##  Max.   :69.00   Max.   :1000.0   Max.   :4.000   Max.   :0.5000

Giải thích kỹ thuật:

summary(): hiển thị thống kê tóm tắt (min, median, mean, max, quartiles) cho các biến định lượng được chọn.

Việc xem các giá trị tối thiểu và tối đa (min–max) giúp phát hiện những điểm ngoài miền hợp lệ, chẳng hạn:

Age < 0 hoặc > 100 → dữ liệu sai.

Price hoặc Quantity = 0 → cần kiểm tra lại logic kinh doanh.

DiscountRate > 1 hoặc < 0 → tỷ lệ chiết khấu không hợp lệ.

Ý nghĩa thống kê:

Kết quả cho thấy các biến đều nằm trong khoảng giá trị hợp lý.

Không xuất hiện trường hợp nào vượt ngoài miền hợp lệ, chứng tỏ dữ liệu được ghi nhận chính xác và ổn định.

Điều này giúp đảm bảo độ tin cậy cho các phép thống kê tiếp theo, đặc biệt là khi tính trung bình, phương sai và xây dựng mô hình hồi quy.

1.1.2.7 Kiểm tra tính ràng buộc logic

Trước khi tiến hành các phân tích thống kê chi tiết, cần thực hiện kiểm tra tính logic và tính nhất quán của dữ liệu nhằm bảo đảm rằng các giá trị được ghi nhận phù hợp với mối quan hệ kinh tế thực tế, không vi phạm các quy tắc tính toán hay quy luật thời gian.

# Kiểm tra ràng buộc giữa TotalAmount và Price * Quantity * (1 - DiscountRate)
check_amount <- dataecommerce %>%
  mutate(ExpectedAmount = Price * Quantity * (1 - DiscountRate),
         Sai_số = TotalAmount - ExpectedAmount)

summary(check_amount$Sai_số)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##    0.00   71.49  198.41  314.88  443.26 1999.00
# Kiểm tra tính nhất quán giữa HasDiscountApplied và DiscountRate > 0
table(dataecommerce$HasDiscountApplied, dataecommerce$DiscountRate > 0)
##        
##         FALSE  TRUE
##   FALSE   486 49735
##   TRUE    529 49250

Giải thích kỹ thuật:

mutate() tạo biến mới ExpectedAmount tính tổng tiền lý thuyết dựa trên giá, số lượng và tỷ lệ chiết khấu.

So sánh TotalAmount với ExpectedAmount để phát hiện sai lệch do làm tròn hoặc lỗi nhập liệu.

summary() cho biết mức độ sai lệch trung bình và cực trị.

table() đối chiếu biến nhị phân HasDiscountApplied với điều kiện DiscountRate > 0 nhằm kiểm tra tính logic của việc gắn nhãn “có khuyến mãi”.

Nhận xét:

  Sai số giữa TotalAmount và công thức Price × Quantity × (1 – DiscountRate) dao động khá lớn, trung bình khoảng 315, cho thấy dữ liệu có thể chịu ảnh hưởng của phí phụ, thuế hoặc sai số làm tròn trong quá trình ghi nhận.

  Mối quan hệ giữa HasDiscountApplied và DiscountRate cũng chưa nhất quán, khi một số giao dịch được gắn nhãn “không khuyến mãi” nhưng vẫn có tỷ lệ giảm giá, và ngược lại.

  Vì vậy, trong Phần 2 - Phương pháp và xử lý dữ liệu, cần chuẩn hóa lại các biến liên quan đến giá trị và khuyến mãi để đảm bảo tính logic và độ tin cậy cho các phân tích tiếp theo.

1.2 PHƯƠNG PHÁP VÀ XỬ LÝ DỮ LIỆU

1.2.1 Làm sạch dữ liệu

1.2.1.1 Xử lý không ăn khớp giữa các biến

1.2.1.1.1 Xử lý không ăn khớp giữa ProductName và Category**

Việc chuẩn hóa mối quan hệ giữa ProductName và Category giúp đảm bảo tính nhất quán trong phân loại sản phẩm, tránh tình trạng cùng một mặt hàng bị gán vào nhiều nhóm khác nhau.

Nhờ đó, các phân tích thống kê sau này như doanh thu theo danh mục, hành vi mua sắm theo ngành hàng sẽ trở nên chính xác và có ý nghĩa hơn.

library(dplyr)
# Tạo bảng ánh xạ (mapping)
category_map <- data.frame(
  ProductName = c("Laptop", "Smartphone", "Headphones", "Shoes", "T-shirt", "Book", "Watch"),
  CorrectCategory = c("Electronics", "Electronics", "Electronics", "Apparel", "Apparel", "Books", "Accessories")
)

# Kết hợp (join) để thay thế Category cũ bằng Category đúng
dataecommerce <- dataecommerce %>%
  left_join(category_map, by = "ProductName") %>%
  mutate(Category = CorrectCategory) %>%
  select(-CorrectCategory)

head(dataecommerce, 10) #xem 10 cái đầu
dim(dataecommerce)
## [1] 100000     21

Giải thích kỹ thuật:

category_map là bảng ánh xạ chuẩn, chứa cột ProductName và CorrectCategory, dùng làm cơ sở đối chiếu giữa tên sản phẩm và danh mục đúng.

left_join() dùng để nối bảng chính dataecommerce với category_map theo khóa ProductName, giúp lấy ra danh mục chuẩn.

mutate(Category = CorrectCategory) cập nhật lại giá trị trong cột Category bằng danh mục đúng.

select(-CorrectCategory) loại bỏ cột tạm sau khi cập nhật để dữ liệu gọn gàng.

Cuối cùng, head() xem 10 dòng đầu và dim() xác nhận số quan sát, đảm bảo không bị mất dữ liệu trong quá trình nối.
1.2.1.1.2 Xử lý không ăn khớp giữa ReviewScore và ReviewText

Vì bộ dữ liệu ban đầu có sự không trùng khớp giữa ReviewScore và ReviewText, cụ thể như các số điểm mà khách hàng đánh giá không đồng nhất với mức đánh giá (ví dụ 5 điểm sẽ cho là average nhưng chỗ khác sẽ là poor). Chính vì vậy nhóm em đã phân tổ số điểm như sau để quy ra các mức đánh giá, thuận tiện trong việc phân tích hành vi khách hàng dễ hơn. - Dưới 4 điểm: “Poor” - Từ 4 điểm đến dưới 7 điểm: “Average” - Từ 7 điểm đến dưới 9 điểm: “Good” - Trên 9 điểm: “Exellent”

library(dplyr)

# Chuẩn hóa và phân loại lại ReviewScore theo mức cảm xúc tương ứng của ReviewText
dataecommerce <- dataecommerce %>%
  mutate(
    ReviewText = case_when(
      ReviewScore < 4 ~ "Poor",
      ReviewScore >= 4 & ReviewScore < 7 ~ "Average",
      ReviewScore >= 7 & ReviewScore < 9 ~ "Good",
      ReviewScore >= 9 ~ "Excellent",
      TRUE ~ "Unknown"
    )
  )

# Kiểm tra kết quả sau khi phân tổ
table(dataecommerce$ReviewText)
## 
## Average    Good    Poor 
##   52145     159   47696
summary(dataecommerce$ReviewScore)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##  -0.600   3.300   4.000   4.006   4.700   8.600

Giải thích kỹ thuật

mutate() tạo hoặc cập nhật biến ReviewText dựa vào giá trị ReviewScore.

case_when() chia ReviewScore thành 4 mức:

  < 4 → Poor

  4–6.9 → Average

  7–8.9 → Good

  ≥ 9 → Excellent

Dòng TRUE ~ "Unknown" đóng vai trò “điều kiện bắt tất cả”, giúp gán nhãn cho các giá trị ngoài khoảng hợp lệ hoặc NA, tránh phát sinh giá trị trống khi xử lý.

Giữ dòng này giúp kiểm soát ngoại lệ tốt hơn và đảm bảo không mất dữ liệu khi phân tổ.

1.2.2 Chuẩn hóa ràng buộc logic

Sau khi đã làm sạch và chuẩn hóa các biến mô tả, bước tiếp theo là kiểm tra và hiệu chỉnh ràng buộc logic giữa các biến giá trị trong giao dịch.

Mục tiêu là đảm bảo công thức tính toán và trạng thái khuyến mãi trong dữ liệu phản ánh đúng thực tế kinh doanh, không sai lệch do nhập liệu hay làm tròn giá trị.

library(dplyr)

# Tính lại tổng tiền lý thuyết dựa trên Price, Quantity và DiscountRate
dataecommerce <- dataecommerce %>%
  mutate(ExpectedAmount = Price * Quantity * (1 - DiscountRate),
         Difference = TotalAmount - ExpectedAmount)

# Tạo cờ sai lệch nếu chênh lệch lớn hơn 2%
dataecommerce <- dataecommerce %>%
  mutate(LogicFlag = ifelse(abs(Difference) / ExpectedAmount > 0.02, 1, 0))

# Chuẩn hóa lại biến HasDiscountApplied
dataecommerce <- dataecommerce %>%
  mutate(HasDiscountApplied = ifelse(DiscountRate > 0, 1, 0))

# Kiểm tra kết quả
summary(dataecommerce$Difference)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##    0.00   71.49  198.41  314.88  443.26 1999.00
table(dataecommerce$LogicFlag)
## 
##     0     1 
##  3011 96989
table(dataecommerce$HasDiscountApplied)
## 
##     0     1 
##  1015 98985

Giải thích kỹ thuật:

mutate() được dùng để tính lại giá trị ExpectedAmount theo công thức chuẩn:

TotalAmount=Price×Quantity×(1−DiscountRate)

Tạo biến Difference để đo chênh lệch giữa TotalAmount thực tế và giá trị tính toán.

LogicFlag gắn cờ (1) nếu sai lệch > 2% — nhằm phát hiện các dòng có khả năng bị lỗi hoặc chịu ảnh hưởng của thuế, phí vận chuyển, hoặc phiếu giảm giá đặc biệt.

HasDiscountApplied được cập nhật lại để đồng nhất với DiscountRate:

DiscountRate > 0 → 1 (có khuyến mãi),

DiscountRate = 0 → 0 (không khuyến mãi).

Các bảng summary() và table() giúp kiểm tra mức độ sai lệch và tần suất lỗi logic.

Ý nghĩa thống kê

Kết quả cho thấy chênh lệch giữa TotalAmount thực tế và giá trị tính theo công thức chuẩn có trung vị khoảng 198.41 và trung bình khoảng 314.88, với sai lệch lớn nhất lên tới 1.999.

Điều này cho thấy dữ liệu vẫn tồn tại sai số đáng kể trong việc ghi nhận tổng tiền, có thể do làm tròn giá trị, thêm phụ phí hoặc chiết khấu đặc biệt chưa được lưu trong bộ dữ liệu.

Bên cạnh đó, cột LogicFlag cho thấy có 3.011 dòng (≈3%) bị gắn cờ sai lệch logic, trong khi 96.989 dòng (≈97%) tuân thủ công thức tính.

Đối với HasDiscountApplied, có 1.015 dòng ghi nhận không nhất quán với điều kiện DiscountRate > 0.

Điều này chứng tỏ cần chuẩn hóa lại các biến giá trị và trạng thái khuyến mãi để đảm bảo tính thống nhất trước khi phân tích sâu hơn về doanh thu và hành vi khách hàng ở các phần tiếp theo: 
library(dplyr)

# Tính lại TotalAmount theo công thức chuẩn
dataecommerce <- dataecommerce %>%
  mutate(TotalAmount = Price * Quantity * (1 - DiscountRate))

# Cập nhật lại biến HasDiscountApplied về dạng logic TRUE/FALSE
dataecommerce <- dataecommerce %>%
  mutate(HasDiscountApplied = ifelse(DiscountRate > 0, TRUE, FALSE))

# Kiểm tra lại kết quả
summary(dataecommerce$TotalAmount)
##     Min.  1st Qu.   Median     Mean  3rd Qu.     Max. 
##    5.228  356.311  730.275  945.968 1378.670 3977.000
table(dataecommerce$HasDiscountApplied)
## 
## FALSE  TRUE 
##  1015 98985

Giải thích kỹ thuật:

mutate() được dùng để ghi đè giá trị TotalAmount bằng công thức chuẩn:

TotalAmount=Price×Quantity×(1−DiscountRate)

nhằm loại bỏ sai lệch do làm tròn, thuế, hoặc lỗi nhập liệu.

Biến HasDiscountApplied được chuyển về dạng logic (TRUE/FALSE):

TRUE nếu có giảm giá (DiscountRate > 0),

FALSE nếu không có giảm giá (DiscountRate = 0).

Việc kiểm tra bằng summary() và table() giúp đảm bảo giá trị được cập nhật đúng và không mất dữ liệu.

Ý nghĩa thống kê

Sau khi hiệu chỉnh, biến TotalAmount có giá trị dao động từ 5.23 đến 3,977, với trung vị khoảng 730.28 và trung bình khoảng 945.97, phản ánh hợp lý quy mô thanh toán trong các giao dịch thương mại điện tử.

Cột HasDiscountApplied cho thấy 98,985 giao dịch (≈99%) có khuyến mãi và 1,015 giao dịch (≈1%) không áp dụng giảm giá, chứng tỏ chương trình khuyến mãi được triển khai rộng rãi trong dữ liệu.

1.3 THỰC HIỆN CÁC THỐNG KÊ CƠ BẢN

Phần này trình bày các bước thông kê mô tả và phân tích dữ liệu dựa trên bộ dữ liệu thương mại điện tử và được chuẩn hóa ở các chương trước.

Mục tiêu nhằm mô tả đặc điểm chung của khách hàng, sản phẩm và hành vi mua sắm và phát hiện các xu hướng, mối quan hệ và sự khác biệt giữa các nhóm biến. Từ đó làm cơ sở cho các mô hình phân tích nâng cao hơn ở phần sau.

1.3.1 Phân tổ các biến

1.3.1.1 Phân tổ quốc gia theo danh mục sản phẩm

library(dplyr)

# Bảng tần số 2 chiều: Country × Category
tab_country_category <- table(dataecommerce$Country, dataecommerce$Category)

# Tính tỷ lệ theo hàng (tỷ trọng danh mục trong từng quốc gia)
prop_country_category <- round(prop.table(tab_country_category, 1) * 100, 2)

# Hiển thị kết quả
tab_country_category
##            
##             Accessories Apparel Books Electronics
##   Australia        2362    4764  2370        7074
##   Canada           2404    4746  2440        7177
##   Germany          2373    4755  2391        7063
##   India            2401    4776  2358        7003
##   UK               2401    4817  2332        7149
##   USA              2346    4804  2469        7225
prop_country_category
##            
##             Accessories Apparel Books Electronics
##   Australia       14.25   28.75 14.30       42.69
##   Canada          14.34   28.31 14.55       42.80
##   Germany         14.31   28.68 14.42       42.59
##   India           14.52   28.88 14.26       42.34
##   UK              14.38   28.85 13.96       42.81
##   USA             13.93   28.52 14.66       42.89

Giải thích kỹ thuật:

table() tạo bảng tần số chéo giữa hai biến định tính: quốc gia (Country) và danh mục sản phẩm (Category).

prop.table(..., 1) tính tỷ lệ phần trăm theo hàng, tức là cơ cấu danh mục trong từng quốc gia.

Kết quả cho phép so sánh cấu trúc hàng hóa giữa các thị trường – một cách phân tổ rất quan trọng trong phân tích thương mại điện tử.

Nhận xét:

Kết quả cho thấy cơ cấu sản phẩm tại các quốc gia rất đồng đều, với Electronics luôn chiếm tỷ trọng cao nhất (~42–43%) trong tổng số giao dịch.

Các nhóm Apparel và Books có tỷ trọng tương đối ổn định, dao động quanh 28% và 14%, trong khi Accessories chỉ chiếm khoảng 14%, phản ánh nhóm hàng phụ trợ.

Mặc dù tỷ trọng giữa các quốc gia chênh lệch không lớn (dưới 1%), nhưng có thể nhận thấy Canada và USA có xu hướng tiêu thụ sản phẩm công nghệ cao hơn, còn India và UK có tỷ trọng hàng thời trang và sách cao hơn đôi chút.

Điều này cho thấy thói quen mua sắm mang đặc trưng vùng văn hóa, dù quy mô thị trường tương đối đồng nhất.

1.3.1.2 Phân tổ giới tính theo sản phẩm giảm giá

Sau khi xem xét sự phân bố sản phẩm giữa các quốc gia, bước tiếp theo là phân tổ theo giới tính và mức khuyến mãi để đánh giá sự khác biệt trong hành vi tiêu dùng giữa các nhóm giới.

Mối quan hệ giữa hai biến này phản ánh mức độ nhạy cảm với khuyến mãi, từ đó hỗ trợ định hướng chiến lược tiếp thị và ưu đãi phù hợp hơn.

library(dplyr)

# Tạo biến DiscountIntensity dựa trên DiscountRate
dataecommerce <- dataecommerce %>%
  mutate(DiscountIntensity = case_when(
    DiscountRate == 0 ~ "No Discount",
    DiscountRate > 0 & DiscountRate <= 0.1 ~ "Low",
    DiscountRate > 0.1 & DiscountRate <= 0.3 ~ "Medium",
    DiscountRate > 0.3 ~ "High"
  ))

# Tạo bảng tần số chéo: Gender × DiscountIntensity
tab_gender_discount <- table(dataecommerce$Gender, dataecommerce$DiscountIntensity)

# Tính tỷ lệ theo hàng (tỷ trọng Discount trong từng giới)
pct_gender_discount <- round(prop.table(tab_gender_discount, 1) * 100, 2)

tab_gender_discount
##             
##               High   Low Medium No Discount
##   Female     12825  6683  13331         354
##   Male       13119  6537  13277         336
##   Non-Binary 13109  6809  13295         325
pct_gender_discount
##             
##               High   Low Medium No Discount
##   Female     38.64 20.13  40.16        1.07
##   Male       39.43 19.65  39.91        1.01
##   Non-Binary 39.09 20.30  39.64        0.97

Giải thích kỹ thuật:

mutate() kết hợp với case_when() để tạo biến mới DiscountIntensity phản ánh cường độ khuyến mãi theo bốn mức độ.

table() tạo bảng tần số chéo giữa giới tính (Gender) và mức khuyến mãi (DiscountIntensity).

prop.table(..., 1) tính tỷ lệ phần trăm theo hàng, cho phép so sánh mức độ khuyến mãi trong từng nhóm giới tính.

Phân tổ này cho phép phát hiện xu hướng tiêu dùng khác biệt giữa các giới, hỗ trợ cho việc xây dựng chính sách giá linh hoạt hơn.

Nhận xét:

Tỷ lệ khuyến mãi giữa ba nhóm giới tính khá cân bằng, với hai mức Medium và High chiếm chủ yếu (khoảng 39–40% mỗi nhóm).

Nam giới có xu hướng nhận khuyến mãi High cao nhất (39.43%), trong khi nữ giới nghiêng nhiều hơn về mức Medium (40.16%).

Sự chênh lệch nhỏ này cho thấy chính sách ưu đãi được áp dụng đồng đều, nhưng hành vi phản ứng với khuyến mãi có khác biệt nhẹ giữa các giới.

1.3.1.3 Phân tổ nhóm tuổi theo chi tiêu của khách hàng

Sau khi xem xét ảnh hưởng của giới tính đến hành vi khuyến mãi, bước tiếp theo tập trung vào mối quan hệ giữa độ tuổi và mức chi tiêu.

Mục tiêu là xác định nhóm khách hàng chi tiêu mạnh nhất trong thương mại điện tử, qua đó gợi ý phân khúc khách hàng mục tiêu cho các chiến dịch marketing.

library(dplyr)

# Tạo nhóm tuổi và nhóm chi tiêu theo tứ phân vị
dataecommerce <- dataecommerce %>%
  mutate(
    AgeGroup = case_when(
      Age < 18 ~ "Under 18",
      Age >= 18 & Age < 25 ~ "18-24",
      Age >= 25 & Age < 35 ~ "25-34",
      Age >= 35 & Age < 45 ~ "35-44",
      Age >= 45 & Age < 55 ~ "45-54",
      TRUE ~ "55+"
    ),
    SpendingGroup = ntile(TotalAmount, 4)  # chia 4 nhóm theo tứ phân vị chi tiêu
  )

# Gán nhãn chi tiêu
dataecommerce <- dataecommerce %>%
  mutate(SpendingGroup = factor(SpendingGroup,
                                levels = 1:4,
                                labels = c("Low", "Medium-Low", "Medium-High", "High")))

# Tạo bảng chéo và tỷ lệ
tab_age_spending <- table(dataecommerce$AgeGroup, dataecommerce$SpendingGroup)
pct_age_spending <- round(prop.table(tab_age_spending, 1) * 100, 2)

tab_age_spending
##        
##          Low Medium-Low Medium-High High
##   18-24 3381       3426        3348 3337
##   25-34 4849       4692        4827 4877
##   35-44 4792       4810        4883 4814
##   45-54 4815       4825        4812 4869
##   55+   7163       7247        7130 7103
pct_age_spending
##        
##           Low Medium-Low Medium-High  High
##   18-24 25.06      25.39       24.81 24.73
##   25-34 25.20      24.38       25.08 25.34
##   35-44 24.83      24.92       25.30 24.94
##   45-54 24.92      24.97       24.91 25.20
##   55+   25.01      25.30       24.89 24.80

Giải thích kỹ thuật:

ntile(TotalAmount, 4) chia dữ liệu thành 4 nhóm chi tiêu theo tứ phân vị (quartiles).

table() tạo bảng tần số giữa nhóm tuổi (AgeGroup) và mức chi tiêu (SpendingGroup).

prop.table(..., 1) cho biết tỷ lệ từng mức chi tiêu trong mỗi nhóm tuổi.

Phân tổ này cho phép đánh giá sức chi tiêu trung bình và hành vi tài chính theo độ tuổi, rất hữu ích cho việc định vị khách hàng tiềm năng.

Nhận xét:

Kết quả cho thấy tỷ lệ chi tiêu giữa các nhóm tuổi gần như cân bằng, mỗi mức chi tiêu (Low → High) chiếm khoảng 25% trong tổng giao dịch.

Sự khác biệt nhỏ giữa các nhóm (chỉ ~0.5%) cho thấy chi tiêu thương mại điện tử có tính phổ quát, không tập trung rõ rệt ở độ tuổi nào.

Điều này phản ánh sự phổ cập và đồng đều trong hành vi mua sắm trực tuyến giữa các thế hệ người dùng.

1.3.1.4 Phân tổ theo thiết bị truy cập và nguồn giới thiệu

Trong thương mại điện tử, hành vi mua sắm của khách hàng phụ thuộc mạnh vào thiết bị truy cập (DeviceType) và nguồn giới thiệu (ReferralSource).

Phân tổ hai biến này giúp xác định thiết bị nào được dùng phổ biến nhất cho từng kênh truy cập, từ đó hỗ trợ doanh nghiệp tối ưu hóa giao diện, quảng cáo và chiến lược trải nghiệm người dùng.

# Tạo bảng chéo DeviceType × ReferralSource
tab_device_referral <- table(dataecommerce$DeviceType, dataecommerce$ReferralSource)

# Tính tỷ lệ phần trăm theo hàng (thiết bị)
pct_device_referral <- round(prop.table(tab_device_referral, 1) * 100, 2)

tab_device_referral
##          
##           Ad Campaign Email Marketing Organic Search Social Media
##   Desktop        8190            8325           8241         8220
##   Mobile         8427            8511           8384         8246
##   Tablet         8470            8318           8396         8272
pct_device_referral
##          
##           Ad Campaign Email Marketing Organic Search Social Media
##   Desktop       24.84           25.25          24.99        24.93
##   Mobile        25.10           25.35          24.98        24.57
##   Tablet        25.32           24.86          25.10        24.73

Giải thích kỹ thuật:

table() tạo bảng tần số 2 chiều giữa DeviceType và ReferralSource.

prop.table(..., 1) chuẩn hóa theo hàng, cho biết tỷ trọng từng nguồn truy cập trong mỗi loại thiết bị.

Cách tính này giúp đánh giá mức độ phụ thuộc giữa kênh giới thiệu và thiết bị sử dụng (ví dụ: quảng cáo trả phí chủ yếu trên mobile hay desktop)

Nhận xét:

Kết quả cho thấy tỷ lệ người dùng giữa các thiết bị rất cân bằng ở mọi kênh truy cập, dao động quanh 24.8–25.4%.

Các kênh Email Marketing và Ad Campaign chiếm tỷ trọng nhỉnh hơn một chút ở cả ba thiết bị, phản ánh vai trò nổi bật của quảng cáo trực tiếp và tiếp thị qua email trong thương mại điện tử.

Sự phân bố gần như đồng đều này cho thấy người dùng đa nền tảng, và các chiến dịch marketing hiện nay đã phủ đều trên desktop, mobile và tablet thay vì tập trung vào một kênh riêng biệt.

1.3.1.5 Phân tổ theo PurchaseWeekday

Hành vi mua sắm trực tuyến chịu ảnh hưởng mạnh bởi thời điểm trong tuần, đặc biệt là ngày làm việc và cuối tuần.

Phân tổ theo PurchaseWeekday giúp xác định ngày nào ghi nhận khối lượng giao dịch cao nhất, từ đó hỗ trợ chiến lược thời gian khuyến mãi và tối ưu hóa lưu lượng truy cập website.

library(lubridate)
## 
## Attaching package: 'lubridate'
## The following objects are masked from 'package:base':
## 
##     date, intersect, setdiff, union
library(dplyr)

# Tạo biến ngày trong tuần
dataecommerce <- dataecommerce %>%
  mutate(PurchaseWeekday = wday(PurchaseDate, label = TRUE, abbr = FALSE))

# Tạo bảng tần số và tỷ lệ
tab_weekday <- table(dataecommerce$PurchaseWeekday)
pct_weekday <- round(prop.table(tab_weekday) * 100, 2)

tab_weekday
## 
## Chủ Nhật  Thứ Hai   Thứ Ba   Thứ Tư  Thứ Năm  Thứ Sáu  Thứ Bảy 
##    14332    14176    14218    14105    14204    14511    14454
pct_weekday
## 
## Chủ Nhật  Thứ Hai   Thứ Ba   Thứ Tư  Thứ Năm  Thứ Sáu  Thứ Bảy 
##    14.33    14.18    14.22    14.11    14.20    14.51    14.45

Giải thích kỹ thuật:

wday(PurchaseDate) trích xuất ngày trong tuần từ biến ngày mua (PurchaseDate), với label = TRUE để hiển thị tên ngày.

table() đếm số lượng giao dịch mỗi ngày, và prop.table() tính tỷ lệ phần trăm.

Phân tổ này giúp nhận diện chu kỳ mua sắm trong tuần, rất hữu ích cho việc lên kế hoạch flash sale hoặc email marketing theo thời điểm vàng.  

Nhận xét:

Kết quả cho thấy tần suất mua hàng phân bố khá đồng đều trong tuần, dao động từ 14.1% đến 14.5%.

Tuy nhiên, Thứ Sáu (14.51%) và Thứ Bảy (14.45%) có tỷ lệ giao dịch nhỉnh hơn, phản ánh xu hướng tăng chi tiêu vào cuối tuần khi người tiêu dùng có thời gian rảnh và tâm lý thoải mái hơn.

Ngược lại, Thứ Tư (14.11%) là ngày có hoạt động thấp nhất, cho thấy giữa tuần là giai đoạn trầm lắng của hành vi mua sắm trực tuyến.

Nhìn chung, chu kỳ này gợi ý rằng doanh nghiệp nên tập trung triển khai chiến dịch khuyến mãi vào cuối tuần để đạt hiệu quả tối ưu.

1.3.2 Phân tích đơn biến

Sau khi mô tả và phân tổ dữ liệu, bước tiếp theo là phân tích từng biến độc lập nhằm hiểu sâu về đặc trưng phân bố, xu hướng trung tâm và độ biến động của dữ liệu.

Phân tích đơn biến giúp nhận diện hành vi tiêu dùng tổng thể, xác định biến nào có ảnh hưởng mạnh đến giao dịch và là tiền đề cho phân tích đa biến ở phần tiếp theo.

1.3.2.1 Phân tích biến Age

Độ tuổi là một đặc trưng nhân khẩu học quan trọng phản ánh giai đoạn tiêu dùng và khả năng chi trả của khách hàng.

Phân tích thống kê mô tả nâng cao đối với biến Age giúp đánh giá xu hướng trung tâm (mean, median), độ phân tán (sd, IQR) và phạm vi giá trị, qua đó nhận diện độ đồng nhất của nhóm khách hàng.

library(dplyr)

# Thống kê mô tả nâng cao cho biến Age
dataecommerce %>%
  summarise(
    Sogiaodich = n(),
    Trungbinh = mean(Age, na.rm = TRUE),
    Max = max(Age, na.rm = TRUE),
    Min = min(Age, na.rm = TRUE),
    Trungvi = median(Age, na.rm = TRUE),
    Dolechchuan = sd(Age, na.rm = TRUE),
    Q1 = quantile(Age, 0.25, na.rm = TRUE),
    Q3 = quantile(Age, 0.75, na.rm = TRUE)
  )

Giải thích kỹ thuật:

summary() cung cấp thông tin về giá trị nhỏ nhất, lớn nhất, trung bình và trung vị, phản ánh độ lệch và phạm vi của biến.

geom_histogram() trực quan hóa phân bố tần suất độ tuổi, giúp phát hiện dạng phân bố (chuẩn, lệch trái/phải, hai đỉnh...).

geom_vline() thêm đường trung bình để dễ nhận diện vị trí trung tâm của phân bố.

1.3.2.2 Phân tích biến TotalAmount

Biến “TotalAmount” thể hiện tổng giá trị giao dịch của khách hàng, phản ánh mức chi tiêu và quy mô mua sắm trong dữ liệu. Phân tích biến này giúp đánh giá hành vi tiêu dùng, xác định mức giao dịch trung bình và phát hiện các giao dịch bất thường có giá trị cao hoặc thấp hơn so với mặt bằng chung.

dataecommerce %>%
  summarise(
    So_giao_dich = n(),
    Trung_binh = mean(TotalAmount, na.rm = TRUE),
    Max = max(TotalAmount, na.rm = TRUE),
    Min = min(TotalAmount, na.rm = TRUE),
    Trung_vi = median(TotalAmount, na.rm = TRUE),
    Do_lech_chuan = sd(TotalAmount, na.rm = TRUE),
    Q1 = quantile(TotalAmount, 0.25, na.rm = TRUE),
    Q3 = quantile(TotalAmount, 0.75, na.rm = TRUE)
  )

Giải thích kỹ thuật

summarise(): tạo bảng tóm tắt chứa các chỉ số thống kê của TotalAmount.

n() → So_giao_dich: đếm tổng số quan sát (giao dịch).

mean() → Trung_binh: tính giá trị trung bình của tổng tiền giao dịch.

max() / min() → Max, Min: xác định giá trị lớn nhất và nhỏ nhất.

median() → Trung_vi: xác định giá trị trung vị, thể hiện mức giữa của phân bố.

sd() → Do_lech_chuan: đo độ biến động của dữ liệu quanh giá trị trung bình.

quantile(…, 0.25) → Q1: giá trị tại phân vị 25%, tức 25% giao dịch thấp hơn mức này.

quantile(…, 0.75) → Q3: giá trị tại phân vị 75%, tức 75% giao dịch thấp hơn mức này.

Kết quả

Biến **“TotalAmount”** có **100.000 giao dịch**, với giá trị dao động từ **5,23** đến **3.977**, cho thấy mức chi tiêu rất đa dạng. **Trung bình** là **945,97**, cao hơn **trung vị** (**730,27**), chứng tỏ phân bố **lệch phải** — có một số giao dịch giá trị cao kéo trung bình lên. **Độ lệch chuẩn** lớn (**758,65**) cho thấy sự biến động mạnh giữa các giao dịch. Nhìn chung, phần lớn khách hàng chi tiêu ở mức trung bình – thấp, trong khi một nhóm nhỏ có giá trị giao dịch cao vượt trội.

1.3.2.3 Phân tích biến Price

Biến “Price” thể hiện giá của từng sản phẩm trong dữ liệu giao dịch thương mại điện tử. Phân tích biến này giúp hiểu rõ mức giá trung bình, độ biến động giá và sự phân bố của các sản phẩm được bán, từ đó đánh giá được chính sách giá và cấu trúc thị trường trong tập dữ liệu.

dataecommerce %>%
  summarise(
    So_giao_dich = n(),
    Trung_binh = mean(Price, na.rm = TRUE),
    Max = max(Price, na.rm = TRUE),
    Min = min(Price, na.rm = TRUE),
    Trung_vi = median(Price, na.rm = TRUE),
    Do_lech_chuan = sd(Price, na.rm = TRUE),
    Q1 = quantile(Price, 0.25, na.rm = TRUE),
    Q3 = quantile(Price, 0.75, na.rm = TRUE)
  )

Giải thích kỹ thuật

Đoạn mã trên sử dụng hàm summarise() trong R để tính toán các chỉ tiêu thống kê mô tả cho biến Price:

- n(): đếm số lượng quan sát (số giao dịch).

- mean(): tính giá trung bình.

- max(), min(): xác định giá cao nhất và thấp nhất.

- median(): tính trung vị, thể hiện giá trị giữa của tập dữ liệu.

- sd(): tính độ lệch chuẩn, đo lường mức độ biến động giá.

- quantile() với 0.25 và 0.75: xác định phân vị thứ nhất (Q1) và phân vị thứ ba (Q3), giúp hiểu phân bố 50% dữ liệu trung tâm.'

Kết quả

Kết quả cho thấy dữ liệu có 100.000 sản phẩm, với giá trung bình khoảng 505,63 và trung vị là 505,95, cho thấy giá phân bố khá đối xứng quanh trung tâm. Giá thấp nhất là 10, cao nhất là 999,98, phản ánh sự đa dạng lớn về mức giá sản phẩm. Độ lệch chuẩn là 286,14, thể hiện mức biến động trung bình trong giá bán.

Ngoài ra, 50% sản phẩm có giá nằm trong khoảng từ 257,14 (Q1) đến 753,73 (Q3). Nhìn chung, giá sản phẩm trong dữ liệu phân bố khá đồng đều, không có sự chênh lệch cực đoan, thể hiện cấu trúc giá tương đối ổn định trong thị trường.

1.3.3 Phân tích đa biến

Phân tích đa biến giúp khám phá mối quan hệ giữa các yếu tố nhân khẩu học, hành vi và tài chính trong dữ liệu thương mại điện tử. Thông qua việc kết hợp nhiều biến, ta có thể xác định nhóm khách hàng có hành vi tương đồng, phát hiện quy luật chi tiêu, và đề xuất định hướng marketing phù hợp.

1.3.3.1 Phân tích Age × TotalAmount

Phân tích mối quan hệ giữa độ tuổi khách hàng (Age) và tổng giá trị chi tiêu (TotalAmount) nhằm xác định nhóm tuổi có mức chi tiêu cao nhất, qua đó hỗ trợ doanh nghiệp xây dựng chiến lược tiếp thị và ưu đãi phù hợp cho từng phân khúc khách hàng.

age_spending <- dataecommerce %>%
  group_by(Age) %>%
  summarise(
    So_giao_dich = n(),
    Tong_chi_tieu = sum(TotalAmount, na.rm = TRUE),
    Trung_binh_chi_tieu = mean(TotalAmount, na.rm = TRUE)
  ) %>%
  arrange(Age)
print(age_spending)
## # A tibble: 52 × 4
##      Age So_giao_dich Tong_chi_tieu Trung_binh_chi_tieu
##    <dbl>        <int>         <dbl>               <dbl>
##  1    18         1944      1822952.                938.
##  2    19         1872      1710776.                914.
##  3    20         1952      1899868.                973.
##  4    21         1910      1752398.                917.
##  5    22         1972      1870708.                949.
##  6    23         1935      1812593.                937.
##  7    24         1907      1846751.                968.
##  8    25         1950      1867747.                958.
##  9    26         1888      1858164.                984.
## 10    27         1887      1775214.                941.
## # ℹ 42 more rows

Giải thích kỹ thuật

dataecommerce %>% — chuyền dataframe vào chuỗi xử lý (pipe của dplyr).

group_by(Age) — gom dữ liệu theo mỗi độ tuổi; mọi phép tính sau đó thực hiện trong từng nhóm tuổi.

summarise(...) — tạo bảng tóm tắt (một dòng cho mỗi tuổi) với:

So_giao_dich = n() — số quan sát/giao dịch trong nhóm tuổi.

Tong_chi_tieu = sum(TotalAmount, na.rm = TRUE) — tổng chi tiêu của nhóm (bỏ NA).

Trung_binh_chi_tieu = mean(TotalAmount, na.rm = TRUE) — chi tiêu trung bình mỗi giao dịch trong nhóm.

arrange(Age) — sắp xếp kết quả theo tuổi tăng dần.

Kết quả lưu vào age_spending để xem hoặc phân tích tiếp.

Kết quả tổng quan mỗi nhóm tuổi có khoảng ~1.800–2.050 giao dịch, tức kích thước mẫu theo tuổi khá đồng đều — nên trung bình chi tiêu đáng tin cậy giữa các nhóm.

Kết quả cho thấy mỗi nhóm tuổi có số giao dịch khá tương đồng (khoảng 1.800–2.000 giao dịch), nên có thể so sánh công bằng giữa các độ tuổi.

Chi tiêu trung bình cao nhất thuộc về nhóm tuổi 26 (984), tiếp theo là 44 (982), 53 (980) và 69 (978). → Đây là những nhóm tuổi chi tiêu mạnh, có thể là khách hàng có thu nhập ổn định hoặc sẵn sàng chi tiêu cho chất lượng và trải nghiệm.

Chi tiêu trung bình thấp hơn rơi vào độ tuổi 19, 35, 46–48, 67, dao động quanh ~910–925. → Những nhóm này có xu hướng thận trọng hơn khi mua sắm, hoặc khả năng chi tiêu thấp hơn (ví dụ sinh viên, người mới đi làm, hoặc người cao tuổi tiết kiệm).

Mức chênh lệch trung bình giữa nhóm cao nhất và thấp nhất chỉ khoảng 8%, cho thấy chi tiêu không khác biệt quá lớn giữa các độ tuổi, nhưng vẫn có những nhóm nổi bật hơn đôi chút.

Ta có thể kết luận cơ bản sau đây

Nhóm tuổi 26–44 có thể được xem là phân khúc khách hàng “vàng”, với hành vi chi tiêu cao, đáng để tập trung các chiến dịch marketing hoặc ưu đãi cá nhân hóa.

Nhóm dưới 25 tuổi và trên 60 tuổi chi tiêu ít hơn, nhưng lại có tiềm năng tăng trưởng nếu doanh nghiệp cung cấp ưu đãi phù hợp (ví dụ giảm giá sinh viên, sản phẩm chăm sóc sức khỏe, tiện lợi, v.v.).

1.3.3.2 Phân tích DiscountRate × Quantity

Phần này nhằm đánh giá tác động của mức giảm giá (DiscountRate) đến số lượng sản phẩm mua (Quantity).

Giả định rằng mức giảm giá cao có thể kích thích khách hàng mua nhiều hơn — phản ánh hiệu quả của chính sách khuyến mãi trong hành vi tiêu dùng.

discount_quantity <- dataecommerce %>%
  group_by(DiscountRate) %>%
  summarise(
    So_giao_dich = n(),
    Tong_so_luong = sum(Quantity, na.rm = TRUE),
    Trung_binh_so_luong = mean(Quantity, na.rm = TRUE)
  ) %>%
  arrange(DiscountRate)
print(discount_quantity)
## # A tibble: 51 × 4
##    DiscountRate So_giao_dich Tong_so_luong Trung_binh_so_luong
##           <dbl>        <int>         <dbl>               <dbl>
##  1         0            1015          2534                2.50
##  2         0.01         1996          4906                2.46
##  3         0.02         1995          5037                2.52
##  4         0.03         1966          4945                2.52
##  5         0.04         2037          5073                2.49
##  6         0.05         2015          5054                2.51
##  7         0.06         1935          4853                2.51
##  8         0.07         2013          5068                2.52
##  9         0.08         2030          5028                2.48
## 10         0.09         1993          4918                2.47
## # ℹ 41 more rows

Giải thích kỹ thuật

group_by(DiscountRate): chia dữ liệu thành các nhóm theo mức giảm giá (ví dụ 0%, 10%, 20%, ...).

summarise(...):

So_giao_dich: tổng số giao dịch ứng với mỗi mức giảm giá.

Tong_so_luong: tổng số sản phẩm được mua trong nhóm đó.

Trung_binh_so_luong: số lượng trung bình mỗi giao dịch — chỉ tiêu quan trọng thể hiện mức độ “kích cầu” của giảm giá.

arrange(DiscountRate): sắp xếp kết quả tăng dần theo mức giảm giá, giúp quan sát xu hướng rõ ràng.

Kết quả tổng quan

Dữ liệu cho thấy Trung_binh_so_luong dao động quanh 2.47–2.54, với xu hướng tăng nhẹ khi DiscountRate tăng. Ở các mức giảm giá thấp (0.00–0.10), lượng mua trung bình khoảng 2.48–2.51; khi tăng đến 0.40–0.50, chỉ số này nhích lên 2.52–2.55.

Tuy nhiên, số lượng giao dịch (So_giao_dich) giảm mạnh khi giảm giá vượt 0.45, cho thấy giảm giá sâu không còn thu hút thêm khách hàng, mà chỉ khiến một số ít người mua nhiều hơn trong mỗi đơn hàng.

Vì vậy, tác động của giảm giá tới hành vi mua là tích cực nhưng yếu, mang tính giới hạn và không tuyến tính.

Mức giảm giá tối ưu nằm trong khoảng 0.10–0.30, nơi vừa duy trì lượng giao dịch cao vừa đạt lượng mua trung bình ổn định.

1.3.3.3 Phân tích DeviceType × TotalAmount × Country

Xác định sự khác biệt trong hành vi chi tiêu giữa người dùng các loại thiết bị (Desktop, Mobile, Tablet) ở các quốc gia khác nhau. Mục đích là hiểu thiết bị nào và thị trường nào mang lại giá trị mua hàng cao nhất, từ đó hỗ trợ chiến lược marketing và phát triển sản phẩm.

device_country_spending <- dataecommerce %>%
  group_by(Country, DeviceType) %>%
  summarise(
    So_giao_dich = n(),
    Tong_chi_tieu = sum(TotalAmount, na.rm = TRUE),
    Trung_binh_chi_tieu = mean(TotalAmount, na.rm = TRUE),
    .groups = "drop"
  ) %>%
  arrange(desc(Trung_binh_chi_tieu))
print(device_country_spending)
## # A tibble: 18 × 5
##    Country   DeviceType So_giao_dich Tong_chi_tieu Trung_binh_chi_tieu
##    <chr>     <chr>             <int>         <dbl>               <dbl>
##  1 Canada    Tablet             5573      5411778.                971.
##  2 Australia Mobile             5504      5259327.                956.
##  3 UK        Mobile             5588      5331049.                954.
##  4 India     Mobile             5543      5285993.                954.
##  5 Germany   Mobile             5598      5329370.                952.
##  6 Germany   Desktop            5490      5206680.                948.
##  7 Australia Desktop            5437      5151857.                948.
##  8 UK        Tablet             5584      5289182.                947.
##  9 UK        Desktop            5527      5233029.                947.
## 10 India     Tablet             5562      5262377.                946.
## 11 USA       Mobile             5627      5316008.                945.
## 12 USA       Desktop            5603      5279790.                942.
## 13 Australia Tablet             5629      5301737.                942.
## 14 USA       Tablet             5614      5282252.                941.
## 15 Canada    Mobile             5708      5362465.                939.
## 16 India     Desktop            5433      5086975.                936.
## 17 Canada    Desktop            5486      5105949.                931.
## 18 Germany   Tablet             5494      5100951.                928.

Giải thích kỹ thuật

group_by(Country, DeviceType):
→ Chia dữ liệu thành các nhóm nhỏ theo quốc gia và loại thiết bị.

summarise(...):
→ Tính toán các chỉ số thống kê cho từng nhóm:

So_giao_dich = n(): đếm số giao dịch trong nhóm.

Tong_chi_tieu = sum(TotalAmount, na.rm = TRUE): tổng số tiền chi tiêu.

Trung_binh_chi_tieu = mean(TotalAmount, na.rm = TRUE): chi tiêu trung bình mỗi giao dịch.

.groups = "drop": sau khi tóm tắt thì bỏ nhóm để tránh cảnh báo.

arrange(desc(Trung_binh_chi_tieu)):
→ Sắp xếp kết quả theo chi tiêu trung bình giảm dần, giúp nhanh chóng nhận diện nhóm chi tiêu cao nhất.

Kết quả Kết quả cho thấy mức chi tiêu trung bình (Trung_binh_chi_tieu) có sự khác biệt rõ rệt giữa loại thiết bị và quốc gia.

Nhóm chi tiêu cao nhất thuộc về người dùng máy tính bảng tại Canada, với chi tiêu trung bình khoảng 971 USD/giao dịch. Điều này gợi ý nhóm khách hàng này có xu hướng mua hàng giá trị cao, có thể do hành vi mua sắm tiện lợi kết hợp khả năng chi trả tốt.

Ở nhóm thiết bị di động (Mobile), các quốc gia như Australia, UK, và Ấn Độ đạt chi tiêu trung bình khá cao (khoảng 950–955 USD), đồng thời có số lượng giao dịch lớn, chứng tỏ đây là phân khúc năng động nhất về tần suất mua hàng.

Người dùng máy tính để bàn (Desktop) có mức chi tiêu trung bình thấp hơn một chút (930–948 USD), tuy nhiên vẫn duy trì tổng chi tiêu (Tong_chi_tieu) cao do khối lượng giao dịch tương đối lớn. Điều này có thể phản ánh hành vi mua sắm “có cân nhắc”, phổ biến ở nhóm khách hàng làm việc tại văn phòng.

Ngoài ra, các thị trường như Mỹ, Canada và Đức thể hiện sự ổn định cao khi cả ba loại thiết bị đều có mức chi tiêu trung bình gần nhau (khoảng 930–945 USD), cho thấy thói quen chi tiêu đồng đều giữa các kênh.

Tóm lại

Máy tính bảng (Tablet) là nhóm thiết bị mang lại giá trị đơn hàng cao nhất → nên tập trung quảng bá sản phẩm cao cấp, trải nghiệm hiển thị tốt.

Thiết bị di động (Mobile) tạo doanh thu lớn nhất theo khối lượng → cần tối ưu giao diện, tốc độ tải và chương trình khuyến mãi ngắn hạn.

Desktop vẫn là kênh ổn định cho nhóm khách hàng truyền thống, nên giữ vững trải nghiệm mua hàng tin cậy và dễ thao tác.

1.4 TRỰC QUAN HÓA DỮ LIỆU

Phần trực quan hóa dữ liệu nhằm chuyển hóa kết quả phân tích thành hình ảnh sinh động và dễ hiểu, giúp người đọc nắm bắt xu hướng, mối quan hệ và phân bố dữ liệu một cách trực quan. Các đồ thị được thiết kế đa lớp (≥5 layer), có sự kết hợp giữa màu sắc, facet, annotation và tính toán thống kê, đảm bảo tính chuyên nghiệp và khoa học.

1.4.1 Trực quan hóa phân bố và xu hướng tổng quát

1.4.1.1 Histogram độ tuổi khách hàng (Age)

library(ggplot2)

ggplot(dataecommerce, aes(x = Age)) +
  geom_histogram(binwidth = 5, fill = "steelblue", color = "white", alpha = 0.8) +
  geom_vline(aes(xintercept = mean(Age, na.rm = TRUE)), color = "red", linetype = "dashed", size = 1) +
  annotate("text", x = mean(dataecommerce$Age, na.rm = TRUE) + 2, y = 500,
           label = paste("Mean =", round(mean(dataecommerce$Age, na.rm = TRUE), 1)),
           color = "red", hjust = 0) +
  theme_minimal(base_size = 13) +
  labs(title = "Phân bố độ tuổi khách hàng",
       x = "Tuổi",
       y = "Tần suất")
## Warning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.
## ℹ Please use `linewidth` instead.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.

Giải thích kỹ thuật

ggplot(dataecommerce, aes(x = Age)): khởi tạo đồ họa, trục x là biến Age.

geom_histogram(binwidth = 5, ...): vẽ histogram, mỗi cột (bin) chứa các quan sát trong khoảng 5 tuổi (ví dụ 20–24, 25–29,...). fill, color, alpha chỉ màu và độ trong suốt.

geom_vline(aes(xintercept = mean(...))): vẽ đường thẳng đứng tại giá trị trung bình (mean) của Age.

annotate("text", ...): thêm chữ ghi nhãn giá trị mean trên đồ thị.

theme_minimal() và labs(...): tinh chỉnh giao diện và tiêu đề/trục.

Mục đích tổng thể: histogram cho thấy tần suất (số khách) theo các nhóm tuổi; đường mean giúp nhận diện vị trí trung tâm.

Kết quả

Biểu đồ thể hiện phân bố độ tuổi của khách hàng cho thấy phần lớn người mua hàng nằm trong khoảng từ 18 đến khoảng 65 tuổi, với mật độ khá đồng đều giữa các nhóm tuổi. Tuổi trung bình của khách hàng là khoảng 43,5 tuổi, được đánh dấu bằng đường đỏ trên biểu đồ, cho thấy mẫu khách hàng chủ yếu thuộc nhóm trung niên.

Phân bố dữ liệu tương đối cân bằng, không có sự chênh lệch rõ rệt giữa các nhóm tuổi, và không xuất hiện đuôi lệch mạnh về phía nào, nghĩa là dữ liệu khá ổn định và không bị ảnh hưởng bởi các giá trị ngoại lai.

1.4.1.2 Density của ReviewScore theo Category

Biểu đồ này được sử dụng để quan sát phân bố điểm đánh giá sản phẩm (ReviewScore) theo từng danh mục sản phẩm (Category).

Mục tiêu là xem mức độ hài lòng của khách hàng có khác nhau giữa các nhóm sản phẩm hay không, và danh mục nào có điểm đánh giá tập trung cao hoặc thấp hơn trung bình.

ggplot(dataecommerce, aes(x = ReviewScore, fill = Category)) +
  geom_density(alpha = 0.5) +
  geom_vline(aes(xintercept = median(ReviewScore, na.rm = TRUE)), color = "red", linetype = "dashed") +
  facet_wrap(~Category) +
  theme_minimal(base_size = 13) +
  labs(title = "Mật độ điểm đánh giá sản phẩm theo danh mục",
       x = "Điểm đánh giá (ReviewScore)",
       y = "Mật độ")

Giải thích kỹ thuật

ggplot(dataecommerce, aes(x = ReviewScore, fill = Category)): Khởi tạo biểu đồ với trục hoành là điểm đánh giá, và tô màu khác nhau cho từng danh mục sản phẩm.

geom_density(alpha = 0.5): Vẽ đường mật độ (density plot) giúp thể hiện phân bố điểm đánh giá; alpha = 0.5 làm trong suốt màu để dễ so sánh.

geom_vline(...): Thêm đường dọc màu đỏ tại giá trị trung vị (median) của điểm đánh giá, giúp người xem nhận biết xu hướng trung tâm.

facet_wrap(~Category): Tách riêng biểu đồ cho từng danh mục để so sánh rõ ràng hơn.

theme_minimal() và labs(...): Dùng để tinh giản giao diện và thêm tiêu đề, nhãn trục cho biểu đồ.

Kết quả

Biểu đồ cho thấy điểm đánh giá ở các danh mục đều có phân bố gần như chuẩn (chuông), tập trung quanh giá trị trung vị khoảng 5 điểm.

Không có danh mục nào lệch rõ rệt về phía điểm thấp hoặc cao, cho thấy mức độ hài lòng của khách hàng tương đối đồng đều giữa các loại sản phẩm.

Tuy nhiên, sự khác biệt nhỏ về độ rộng đường cong cho thấy có thể một số nhóm sản phẩm (như Books) có độ phân tán lớn hơn, tức là người dùng có ý kiến đa dạng hơn khi đánh giá.

1.4.1.3 Boxplot giá sản phẩm (Price) theo Category

ggplot(dataecommerce, aes(x = Category, y = Price, fill = Category)) +
  geom_boxplot(alpha = 0.7, outlier.color = "grey50") +
  stat_summary(fun = mean, geom = "point", shape = 20, size = 3, color = "red") +
  coord_flip() +
  scale_fill_brewer(palette = "Set3") +
  theme_minimal(base_size = 13) +
  labs(title = "Phân bố giá sản phẩm theo danh mục",
       x = "Danh mục sản phẩm",
       y = "Giá (USD)")

Giải thích kỹ thuật

geom_boxplot(): vẽ biểu đồ hộp (boxplot) cho từng nhóm sản phẩm, hiển thị giá trị trung vị, tứ phân vị và các điểm ngoại lai.

stat_summary(fun = mean, ...): thêm điểm trung bình giá (mean) màu đỏ trên mỗi hộp.

coord_flip(): xoay trục để các danh mục nằm ngang, dễ đọc hơn.

scale_fill_brewer(palette = "Set3"): chọn bảng màu nhẹ, phân biệt từng danh mục.

theme_minimal() + labs(): tạo bố cục gọn gàng và gắn nhãn trục, tiêu đề.

Kết quả

Biểu đồ cho thấy giá các sản phẩm trong từng danh mục dao động khá đồng đều, với giá trung vị gần mức 500 USD.

Các nhóm như Electronics và Accessories có phạm vi giá rộng hơn, thể hiện sự đa dạng trong phân khúc sản phẩm.

Trong khi đó, Books và Apparel có giá ổn định và ít biến động hơn, cho thấy đây là các sản phẩm tầm trung – phổ thông.

Điểm đỏ ở giữa mỗi hộp thể hiện giá trung bình, nằm gần trung vị → phân bố giá khá cân đối, không bị lệch mạnh về phía cao hoặc thấp.

1.4.1.4 Violin plot phân bố chi tiêu theo sản phẩm và giới tính

ggplot(dataecommerce, aes(x = ProductName, y = TotalAmount, fill = Gender)) +
  geom_violin(position = position_dodge(0.8), trim = TRUE, alpha = 0.6, color = "grey40") +
  geom_boxplot(width = 0.08, position = position_dodge(0.8), fill = "white", outlier.shape = NA) +
  scale_fill_brewer(palette = "Pastel2") +
  theme_minimal(base_size = 14) +
  theme(
    legend.position = "top",
    plot.title = element_text(face = "bold", hjust = 0.5)
  ) +
  labs(
    title = "Phân bố chi tiêu theo sản phẩm và giới tính",
    x = "Tên sản phẩm",
    y = "Tổng chi tiêu (USD)",
    fill = "Giới tính"
  )

Giải thích kỹ thuật

aes(x = ProductName, y = TotalAmount, fill = Gender):Trục hoành là tên sản phẩm, trục tung là tổng chi tiêu, và màu thể hiện giới tính.

geom_violin():Tạo biểu đồ violin plot thể hiện mật độ phân bố chi tiêu cho từng nhóm giới tính.

trim = TRUE giúp cắt gọn phần đuôi để đồ thị không bị kéo dài.

alpha = 0.6 làm màu trong suốt nhẹ, giúp dễ nhìn khi các phần chồng nhau.

geom_boxplot(): Thêm boxplot nhỏ bên trong mỗi violin để thể hiện trung vị (median) và khoảng tứ phân vị (IQR). 

Màu trắng giúp nổi bật giữa các vùng màu pastel.

theme_minimal() và scale_fill_brewer(palette = "Pastel2"): Dùng giao diện nhẹ, màu sắc dịu giúp biểu đồ rõ ràng, thân thiện khi đọc.

Kết quả

Biểu đồ cho thấy:

  • Mỗi loại sản phẩm có phân bố chi tiêu khá tương đồng giữa các giới tính.

  • Dải violin cho thấy phần lớn chi tiêu tập trung ở mức thấp, với vài điểm chi tiêu cao kéo dài lên phía trên (outliers).

  • Hộp boxplot nhỏ ở giữa giúp ta thấy trung vị chi tiêu của các giới tính hầu như tương đương — không có nhóm nào vượt trội rõ ràng.

  • Điều này gợi ý rằng giới tính không phải là yếu tố tạo khác biệt lớn trong mức chi tiêu, mà có thể sản phẩm hoặc thu nhập ảnh hưởng nhiều hơn.

1.4.1.5 ECDF plot cho DiscountRate

ggplot(dataecommerce, aes(x = DiscountRate)) +
  stat_ecdf(geom = "step", color = "darkgreen", size = 1) +
  scale_x_continuous(labels = scales::percent_format()) +
  theme_minimal(base_size = 13) +
  labs(title = "Đồ thị ECDF của tỷ lệ giảm giá",
       x = "Tỷ lệ giảm giá (%)",
       y = "Xác suất tích lũy")

Phân tích kỹ thuật

aes(x = DiscountRate): Trục X là tỷ lệ giảm giá của sản phẩm (DiscountRate).

stat_ecdf():
Vẽ Empirical Cumulative Distribution Function (ECDF) — hay còn gọi là hàm phân phối tích lũy thực nghiệm.
Nó cho biết với mỗi giá trị giảm giá cụ thể, tỷ lệ quan sát nhỏ hơn hoặc bằng giá trị đó là bao nhiêu. Dạng step tạo hiệu ứng bậc thang rõ ràng.

scale_x_continuous(labels = scales::percent_format()): Chuyển trục X sang dạng phần trăm (%), dễ đọc và trực quan hơn.

theme_minimal():Làm đồ thị gọn gàng, chỉ giữ lại phần cần thiết. Màu xanh lá đậm được chọn để tạo sự tương phản dễ nhìn.

Kết quả

Biểu đồ cho thấy tỷ lệ giảm giá phân bố khá đều từ 0% đến khoảng 50%.

Đường ECDF gần như tuyến tính, điều này nghĩa là:

  • Xác suất tích lũy tăng đều theo tỷ lệ giảm giá.

  • Không có nhóm giảm giá nào chiếm ưu thế rõ ràng (ví dụ: không tập trung nhiều ở mức 10% hay 30%).

=> Kết luận: Chính sách giảm giá của doanh nghiệp trải đều ở nhiều mức khác nhau, giúp đa dạng lựa chọn cho khách hàng và không lệch về một nhóm cụ thể.

1.4.2 Trực quan hóa mối quan hệ giữa các biến

1.4.2.1 Scatterplot giữa Age và TotalAmount

ggplot(dataecommerce, aes(x = Age, y = TotalAmount)) +
  geom_point(alpha = 0.4, color = "#2E86C1") +
  geom_smooth(method = "lm", se = TRUE, color = "red", linewidth = 0.8) +
  theme_bw(base_size = 13) +
  labs(
    title = "Mối quan hệ giữa tuổi và tổng chi tiêu",
    subtitle = "Mối quan hệ chi tiêu theo tuổi",
    x = "Tuổi khách hàng",
    y = "Tổng chi tiêu (USD)"
  )
## `geom_smooth()` using formula = 'y ~ x'

Giải thích kỹ thuật

geom_point(): Vẽ các điểm dữ liệu, mỗi điểm biểu diễn tổng chi tiêu của một khách hàng theo độ tuổi.

alpha = 0.4 giúp các điểm trong vùng dày đặc trở nên trong suốt để dễ quan sát.

geom_smooth(method = "lm"): Thêm đường hồi quy tuyến tính (đường màu đỏ) thể hiện xu hướng chung giữa tuổi và chi tiêu.

se = TRUE hiển thị vùng sai số (confidence interval), tuy nhiên ở đây vùng này rất hẹp nên khó thấy rõ.

theme_bw(): Nền trắng (black & white) giúp đồ thị rõ nét, chuyên nghiệp.

base_size = 13 đảm bảo chữ dễ đọc.

Kết quả

Phân tán chi tiêu khá đồng đều ở mọi độ tuổi — từ 18 đến khoảng 70 tuổi, mức chi tiêu trung bình không thay đổi đáng kể.

Đường hồi quy màu đỏ gần như nằm ngang, cho thấy mối quan hệ giữa tuổi và tổng chi tiêu là rất yếu hoặc không có.

Tức là, tuổi khách hàng không phải là yếu tố quyết định chính trong chi tiêu — có thể các yếu tố khác như thu nhập, nghề nghiệp, hoặc hành vi mua sắm ảnh hưởng mạnh hơn.

1.4.2.2 Hexbin plot cho Price và Quantity

ggplot(dataecommerce, aes(x = Price, y = Quantity)) +
  geom_hex(bins = 30) +
  scale_fill_viridis_c() +
  theme_classic(base_size = 13) +
  labs(
    title = "Mối quan hệ giữa giá và số lượng sản phẩm",
    subtitle = "Biểu đồ hexbin cho Price và Quantity",
    x = "Giá sản phẩm (USD)",
    y = "Số lượng mua",
    fill = "Tần suất"
  )

Giải thích kỹ thuật

geom_hex(bins = 30): Vẽ biểu đồ hexbin (ô lục giác) — mỗi ô biểu diễn mật độ tần suất của các cặp giá trị Price và Quantity.

Số lượng bins = 30 quyết định mức chia nhỏ của trục.

scale_fill_viridis_c(): Dùng thang màu Viridis, giúp thể hiện tần suất rõ ràng hơn (từ tím → vàng, tương ứng từ thấp → cao).

theme_classic(): Giữ phong cách đơn giản, tập trung vào nội dung.

kết quả

Các điểm dữ liệu tập trung theo các hàng nằm ngang tương ứng với số lượng mua = 1, 2, 3, 4. => Điều này cho thấy người mua chủ yếu chọn số lượng nhỏ, lặp lại theo bậc (rất ít khi mua nhiều hơn 4 sản phẩm/lần).

Dọc theo trục Giá sản phẩm (USD), tần suất phân bố khá đều — nghĩa là mức giá không ảnh hưởng nhiều đến số lượng sản phẩm được mua. Dù giá thấp hay cao, khách hàng vẫn thường mua 1–2 sản phẩm mỗi lần.

Màu vàng (tần suất cao nhất) nằm ở các ô gần giá thấp hơn (0–300 USD) và số lượng = 1 hoặc 2, cho thấy:

Các giao dịch phổ biến nhất là mua 1–2 sản phẩm có giá rẻ.

Biểu đồ cho thấy phần lớn khách hàng chỉ mua 1 hoặc 2 sản phẩm mỗi lần, và giá sản phẩm không có tác động mạnh đến số lượng mua. Điều này gợi ý rằng hành vi mua hàng có thể mang tính cá nhân hoặc thói quen, hơn là bị ảnh hưởng bởi giá.

1.4.2.3 Bubble chart: Age – DiscountRate – TotalAmount

ggplot(dataecommerce, aes(x = Age, y = DiscountRate, size = TotalAmount, color = DiscountRate)) +
  geom_point(alpha = 0.6) +
  scale_color_viridis_c() +
  theme_bw(base_size = 13) +
  labs(
    title = "Quan hệ giữa Tuổi, Mức giảm giá và Tổng chi tiêu",
    x = "Tuổi khách hàng",
    y = "Tỷ lệ giảm giá (%)",
    size = "Tổng chi tiêu (USD)",
    color = "Tỷ lệ giảm giá"
  )

Giải thích kỹ thuật

aes(x = Age, y = DiscountRate, size = TotalAmount, color = DiscountRate): Mỗi điểm biểu diễn một khách hàng, trong đó:

- Trục X: tuổi khách hàng

- Trục Y: tỷ lệ giảm giá được hưởng

- Màu sắc: mức giảm giá (đậm → nhạt theo DiscountRate)

- Kích thước điểm: tổng chi tiêu (TotalAmount)

geom_point(alpha = 0.6): vẽ điểm bán trong suốt để dễ quan sát mật độ.

scale_color_viridis_c(): dùng bảng màu Viridis (từ tím → vàng) để thể hiện gradient giảm giá.

theme_bw(): nền sáng, dễ đọc và chuyên nghiệp.

Kết quả

Phân bố dọc đều trên trục Y (tỷ lệ giảm giá) → cho thấy mức giảm giá được phân phối đồng đều trong dữ liệu, không tập trung ở một mức cụ thể.

Trục X (tuổi): không có xu hướng nghiêng hay cụm rõ ràng => Điều này chứng tỏ tuổi khách hàng không ảnh hưởng rõ rệt đến mức giảm giá được hưởng.

Kích thước điểm (tổng chi tiêu) gần như tương đồng trên toàn bộ vùng giá trị→ Tổng chi tiêu không phụ thuộc nhiều vào tuổi hoặc tỷ lệ giảm giá.

Màu sắc chuyển đều từ tím → vàng từ dưới lên trên, thể hiện đúng độ dốc của DiscountRate, xác nhận dữ liệu được phân phối liên tục.

1.4.2.4 Facet scatter: Age × TotalAmount theo Country

ggplot(dataecommerce, aes(x = Age, y = TotalAmount)) +
  geom_point(alpha = 0.4, color = "#1F618D") +
  geom_smooth(method = "loess", se = FALSE, color = "red") +
  facet_wrap(~ Country, scales = "free") +
  theme_bw(base_size = 13) +
  labs(
    title = "Mối quan hệ giữa tuổi và chi tiêu theo từng quốc gia",
    x = "Tuổi khách hàng",
    y = "Tổng chi tiêu (USD)"
  )
## `geom_smooth()` using formula = 'y ~ x'

Giải thích kỹ thuật

geom_point(): mỗi điểm đại diện cho một khách hàng với tuổi và tổng chi tiêu.

alpha = 0.4: làm mờ để dễ thấy mật độ.

color = "#1F618D": màu xanh dương nhẹ, tạo cảm giác trung tính và chuyên nghiệp.

geom_smooth(method = "loess"): thêm đường xu hướng phi tuyến cho từng quốc gia, màu đỏ để dễ quan sát→ loess thích hợp khi dữ liệu không tuyến tính, giúp phát hiện xu hướng nhẹ.

facet_wrap(~ Country): chia nhỏ biểu đồ theo quốc gia, giúp so sánh trực quan giữa các nước.

theme_bw(): dùng nền trắng – rõ ràng, dễ đọc.

Kết quả Quan sát 6 biểu đồ con: Australia, Canada, Germany, India, UK, USA.

Phân bố điểm dày và đều trên toàn trục X (tuổi)→ cho thấy khách hàng ở mọi độ tuổi đều có hành vi chi tiêu tương đối đồng đều.

Đường màu đỏ (xu hướng chi tiêu) trong tất cả các quốc gia đều gần như phẳng, chỉ hơi dao động nhẹ ở vài nhóm tuổi cao → Điều này chứng tỏ tuổi không phải yếu tố ảnh hưởng mạnh đến tổng chi tiêu.

So sánh giữa các quốc gia:

  • Germany, USA, Canada: mức chi tiêu trung bình cao hơn (phần lớn điểm tập trung ở vùng trên 1000 USD).

  • India: mức chi tiêu nhìn chung thấp hơn một chút (phần lớn điểm quanh 500–1500 USD).

  • UK và Australia: phân bố khá đồng đều, không có cụm rõ rệt.

1.4.2.5 Correlation matrix giữa các biến định lượng

library(GGally)

GGally::ggpairs(select(dataecommerce, Age, Price, Quantity, DiscountRate, TotalAmount))

Giải thích kỹ thuật

Hàm ggpairs() trong gói GGally được dùng để tạo ma trận biểu đồ (pair plot) – một công cụ trực quan hóa dữ liệu khám phá (EDA) rất hiệu quả. Nó giúp quan sát nhanh mối quan hệ giữa tất cả các cặp biến định lượng trong cùng một biểu đồ.

Cách hoạt động

- select() (thuộc gói dplyr) dùng để chọn 5 biến cần phân tích: Age, Price, Quantity, DiscountRate, và TotalAmount.

ggpairs() tự động:

- Vẽ đồ thị phân bố (histogram hoặc density plot) trên đường chéo chính để mô tả dạng phân phối của từng biến.

- Hiển thị scatterplot (biểu đồ phân tán) ở phía dưới đường chéo, thể hiện mối quan hệ giữa từng cặp biến.

- Hiển thị hệ số tương quan Pearson ở phía trên đường chéo, kèm theo mức ý nghĩa thống kê (*, **, ***).

Hàm này cũng bỏ qua giá trị NA, tự động chuẩn hóa tỷ lệ trục và sử dụng các công cụ từ ggplot2 để hiển thị màu sắc, lưới và nhãn.

Kết quả

Mối tương quan giữa các biến

  • Price và TotalAmount có tương quan dương mạnh (~0.70) → sản phẩm giá cao thường đi kèm tổng chi tiêu lớn.

  • Quantity và TotalAmount cũng có tương quan dương đáng kể (~0.56) → mua nhiều sản phẩm hơn dẫn tới doanh thu cao hơn.

  • DiscountRate có tương quan âm nhẹ (~-0.24) với TotalAmount → các giao dịch giảm giá mạnh thường có tổng chi tiêu thấp hơn.

  • Age gần như không tương quan với các biến còn lại → tuổi khách hàng không ảnh hưởng rõ ràng đến giá, số lượng hoặc chi tiêu.

Dạng phân phối

  • TotalAmount lệch phải rõ rệt → nhiều đơn hàng giá trị thấp, chỉ một số ít đơn hàng giá trị cao.

  • Quantity hiển thị các mức rời rạc (1, 2, 3, 4) → phản ánh đặc trưng của dữ liệu giao dịch thực tế.

  • Các biến khác như Price và DiscountRate phân bố tương đối đều.

Ta có thể rút ra rằng:

  • Price và Quantity là hai biến dự báo quan trọng cho TotalAmount.

  • DiscountRate ảnh hưởng ngược chiều nhẹ, nên có thể cần kiểm tra thêm bằng mô hình hồi quy.

  • Age có thể bỏ qua hoặc chỉ giữ lại để kiểm định kiểm soát.

Đây là cơ sở tốt để chuyển sang bước xây dựng mô hình hồi quy hoặc phân cụm, đồng thời xem xét việc chuẩn hóa hoặc log-transform biến TotalAmount để giảm độ lệch phân phối.

1.4.3 Trực quan hóa theo nhóm và danh mục

1.4.3.1 Bar chart số lượng sản phẩm theo Category & Country

ggplot(dataecommerce, aes(x = Category, fill = Country)) +
  geom_bar(position = "dodge") +
  geom_text(stat = "count",
            aes(label = ..count..),
            position = position_dodge(0.9),
            vjust = -0.5,
            size = 3) +
  facet_wrap(~Country) +
  theme_minimal(base_size = 13) +
  labs(
    title = "Số lượng sản phẩm theo danh mục và quốc gia",
    x = "Danh mục sản phẩm",
    y = "Số lượng sản phẩm"
  ) +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))
## Warning: The dot-dot notation (`..count..`) was deprecated in ggplot2 3.4.0.
## ℹ Please use `after_stat(count)` instead.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.

Giải thích kỹ thuật

geom_bar(position = "dodge"): tạo biểu đồ cột, các quốc gia được đặt cạnh nhau để dễ so sánh.

geom_text(stat = "count"): thêm nhãn hiển thị số lượng trên mỗi cột giúp dễ đọc giá trị.

facet_wrap(~Country): chia biểu đồ thành từng ô theo quốc gia để so sánh riêng biệt.

theme_minimal(): dùng giao diện tối giản, dễ quan sát.

axis.text.x = element_text(angle = 45): xoay nhãn trục X để tránh đè chữ.

labs(): đặt tiêu đề và nhãn trục cho biểu đồ.

Kết luận

Quan sát biểu đồ cho thấy ở tất cả các quốc gia (Australia, Canada, Germany, India, UK, USA), danh mục Electronics luôn chiếm tỷ trọng cao nhất, với số lượng sản phẩm dao động quanh mức 7000. Điều này phản ánh rõ xu hướng mua sắm thiết bị điện tử mạnh mẽ ở mọi thị trường.

Danh mục Apparel đứng thứ hai với số lượng sản phẩm khoảng 4700–4800 ở hầu hết các quốc gia, cho thấy đây cũng là nhóm hàng có sức tiêu thụ ổn định.

Hai danh mục còn lại là Accessories và Books có số lượng sản phẩm thấp hơn rõ rệt (khoảng 2300–2500), thể hiện mức độ phổ biến thấp hơn so với hai nhóm hàng trên.

Nhìn tổng thể, mô hình phân bố khá giống nhau giữa các quốc gia, không có sự khác biệt lớn về thứ hạng các danh mục. Tuy nhiên, mức chênh lệch nhỏ về chiều cao cột cho thấy quy mô tiêu thụ ở các thị trường như Germany, USA và UK có xu hướng cao hơn một chút, phản ánh tiềm năng mua sắm lớn hơn ở những quốc gia này.

1.4.3.2 Pie chart tỷ lệ Category trong từng giới tính

library(ggplot2)
library(scales)
## 
## Attaching package: 'scales'
## The following object is masked from 'package:readr':
## 
##     col_factor
ggplot(dataecommerce, aes(x = "", fill = Category)) +
  geom_bar(width = 1, stat = "count") +
  coord_polar("y") +
  facet_wrap(~Gender) +
  geom_text(
    stat = "count",
    aes(label = percent(..count.. / sum(..count..))),
    position = position_stack(vjust = 0.5),
    size = 3, color = "white"
  ) +
  labs(title = "Tỷ lệ danh mục sản phẩm theo giới tính") +
  theme_void() +
  theme(legend.position = "bottom")

Giải thích kỹ thuật

geom_bar(width = 1, stat = "count"): tạo biểu đồ cột tròn (dạng thanh đầy 100%), trong đó mỗi phần biểu diễn số lượng sản phẩm thuộc từng danh mục.

coord_polar("y"): chuyển đổi biểu đồ cột thành biểu đồ tròn (pie chart) bằng cách biểu diễn theo góc quay trục y.

facet_wrap(~Gender): chia biểu đồ thành ba phần riêng biệt tương ứng với ba giới tính: Female, Male, và Non-Binary, giúp so sánh tỷ lệ danh mục giữa các nhóm giới.

geom_text(aes(label = percent(..count.. / sum(..count..))): hiển thị tỷ lệ phần trăm ngay trên từng lát của biểu đồ, giúp người xem nhận biết rõ tỉ trọng mà không cần ước lượng.

theme_void() loại bỏ toàn bộ trục và đường lưới để biểu đồ tròn trở nên nổi bật và dễ nhìn hơn, đồng thời theme(legend.position = "bottom") đưa chú giải xuống dưới biểu đồ để bố cục cân đối, gọn gàng.

Kết quả

Biểu đồ hiển thị ba vòng tròn đại diện cho ba nhóm giới tính: Female, Male, và Non-Binary. Dễ thấy rằng ở cả ba nhóm, Electronics là danh mục chiếm tỷ trọng cao nhất — khoảng 14% tổng số sản phẩm, thể hiện mức độ phổ biến mạnh mẽ của sản phẩm điện tử ở mọi giới tính.

Danh mục Apparel (trang phục) đứng thứ hai với tỷ lệ khoảng 9.5%, cho thấy đây cũng là nhóm hàng có sức tiêu thụ khá ổn định ở cả nam, nữ và người phi nhị nguyên.

Hai danh mục Books và Accessories có tỷ lệ thấp hơn đáng kể, chỉ khoảng 4.7–4.8%, phản ánh nhu cầu thấp hơn so với hai nhóm hàng chính.

Đáng chú ý, sự khác biệt giữa các giới tính là rất nhỏ — các tỷ lệ gần như trùng nhau. Điều này cho thấy sở thích mua sắm theo danh mục sản phẩm không khác biệt đáng kể giữa nam, nữ và nhóm giới tính khác, gợi ý rằng chiến lược sản phẩm có thể áp dụng tương tự cho mọi nhóm khách hàng mà không cần phân chia quá chi tiết theo giới.

1.4.3.3 Stacked bar chart: DiscountLevel theo DeviceType

library(dplyr)
dataecommerce <- dataecommerce %>%
  mutate(
    DiscountLevel = case_when(
      DiscountRate == 0 ~ "Không giảm",
      DiscountRate <= 0.2 ~ "Giảm nhẹ",
      DiscountRate <= 0.4 ~ "Giảm vừa",
      TRUE ~ "Giảm mạnh"
    )
  )

ggplot(dataecommerce, aes(x = DeviceType, fill = DiscountLevel)) +
  geom_bar(position = "fill") +
  scale_y_continuous(labels = scales::percent) +
  theme_minimal(base_size = 13) +
  labs(
    title = "Tỷ trọng mức giảm giá theo loại thiết bị",
    x = "Loại thiết bị",
    y = "Tỷ lệ (%)",
    fill = "Mức giảm giá"
  )

Giải thích kỹ thuật

Biểu đồ được tạo bằng geom_bar(position = "fill"), cho phép hiển thị tỷ trọng phần trăm của từng mức giảm giá trong mỗi loại thiết bị. Việc sử dụng tùy chọn "fill" giúp chuẩn hóa chiều cao các cột về 100%, từ đó dễ dàng so sánh tỷ lệ giữa các nhóm giảm giá thay vì so sánh giá trị tuyệt đối.

Hàm scale_y_continuous(labels = scales::percent) được dùng để chuyển giá trị trên trục tung thành phần trăm, giúp người xem dễ dàng nhận biết tỷ trọng. Phần xử lý dữ liệu trước đó sử dụng mutate() kết hợp với case_when() để phân loại biến DiscountRate thành 4 nhóm: “Không giảm”, “Giảm nhẹ”, “Giảm vừa”, và “Giảm mạnh”, đảm bảo dữ liệu được nhóm đúng logic kinh tế.

Cuối cùng, theme_minimal(base_size = 13) được áp dụng để tạo bố cục sáng sủa, dễ đọc và giữ sự tập trung vào nội dung chính của biểu đồ mà không bị rối bởi các chi tiết trang trí.

Kết quả

Quan sát biểu đồ, ta thấy tỷ trọng các mức giảm giá giữa ba loại thiết bị Desktop, Mobile, và Tablet khá đồng đều, không có sự khác biệt rõ rệt. Hai nhóm “Giảm nhẹ” và “Giảm vừa” chiếm phần lớn, dao động quanh mức 35–40%, cho thấy phần lớn sản phẩm được áp dụng mức giảm giá trung bình.

Nhóm “Giảm mạnh” chiếm khoảng 20%, thể hiện các chương trình khuyến mãi sâu vẫn được triển khai nhưng ít phổ biến hơn. Trong khi đó, nhóm “Không giảm” chiếm tỷ lệ rất nhỏ, gần như không đáng kể, chứng tỏ đa phần sản phẩm đều có ưu đãi giảm giá nhất định.

Tổng thể, biểu đồ cho thấy chính sách khuyến mãi được triển khai khá nhất quán trên tất cả các thiết bị, không có dấu hiệu thiên lệch về kênh mua hàng.

1.4.3.4 Lollipop chart: Top 10 sản phẩm theo doanh thu

library(dplyr)
top10_products <- dataecommerce %>%
  group_by(ProductName) %>%
  summarise(TotalRevenue = sum(TotalAmount, na.rm = TRUE)) %>%
  arrange(desc(TotalRevenue)) %>%
  slice(1:10)

ggplot(top10_products, aes(x = reorder(ProductName, TotalRevenue), y = TotalRevenue)) +
  geom_segment(aes(xend = ProductName, y = 0, yend = TotalRevenue), color = "grey70") +
  geom_point(size = 4, color = "red") +
  coord_flip() +
  theme_bw(base_size = 13) +
  labs(
    title = "Top 10 sản phẩm có doanh thu cao nhất",
    x = "Tên sản phẩm",
    y = "Tổng doanh thu (USD)"
  )

Giải thích kỹ thuật

Đoạn mã trên bắt đầu bằng việc sử dụng dplyr để xử lý dữ liệu. Hàm group_by(ProductName) nhóm dữ liệu theo tên sản phẩm.

summarise(TotalRevenue = sum(TotalAmount, na.rm = TRUE)) tính tổng doanh thu (TotalAmount) cho từng sản phẩm, đồng thời loại bỏ giá trị thiếu với na.rm = TRUE. 

arrange(desc(TotalRevenue)) sắp xếp các sản phẩm theo thứ tự giảm dần về doanh thu, và slice(1:10) chọn ra 10 sản phẩm có doanh thu cao nhất.

Khi trực quan hóa, ggplot() được sử dụng với geom_segment() để tạo các đường ngang nối từ trục hoành đến điểm biểu diễn doanh thu, giúp dễ dàng so sánh độ dài tương ứng với giá trị.

geom_point(size = 4, color = "red") thêm các điểm đỏ nổi bật thể hiện giá trị cụ thể của từng sản phẩm. Hàm coord_flip() đảo trục hoành và tung để hiển thị biểu đồ ngang, phù hợp với việc đọc tên sản phẩm dài. Cuối cùng, theme_bw(base_size = 13) chọn nền trắng, phông chữ dễ đọc, và labs() thêm tiêu đề, tên trục rõ ràng.

Kết quả

Biểu đồ hiển thị top 10 sản phẩm có doanh thu cao nhất. Các điểm màu đỏ đại diện cho tổng doanh thu của từng sản phẩm, với vị trí trên trục hoành cho thấy mức doanh thu cụ thể. Nhìn vào biểu đồ, ta thấy các sản phẩm như Book, T-shirt, Headphones, Smartphone, Watch, Laptop, Shoes đều có mức doanh thu rất cao, gần tương đương nhau — thể hiện qua việc các điểm đỏ gần nằm trên một đường thẳng dọc.

Điều này gợi ý rằng nhóm sản phẩm phổ biến như điện tử (Smartphone, Laptop, Headphones) và thời trang (T-shirt, Shoes, Watch) chiếm tỷ trọng doanh thu lớn, trong khi nhóm Book cũng bất ngờ đạt doanh thu cao, có thể do nhu cầu mua trực tuyến mạnh mẽ hoặc chiến dịch khuyến mãi hiệu quả. Tổng thể, biểu đồ này giúp nhận diện nhanh các sản phẩm chủ lực đóng góp nhiều nhất vào doanh thu toàn bộ hệ thống.

1.4.3.5 Treemap tỷ trọng doanh thu theo Category

library(treemapify)

category_revenue <- dataecommerce %>%
  group_by(Category) %>%
  summarise(TotalRevenue = sum(TotalAmount, na.rm = TRUE))

ggplot(category_revenue, aes(area = TotalRevenue, fill = Category, label = Category)) +
  treemapify::geom_treemap() +
  treemapify::geom_treemap_text(color = "white", place = "centre", size = 5) +
  theme_void(base_size = 13) +
  labs(title = "Tỷ trọng doanh thu theo danh mục sản phẩm")

Giải thích kết quả

Đoạn mã trên sử dụng gói treemapify để trực quan hóa tỷ trọng doanh thu theo danh mục sản phẩm dưới dạng biểu đồ khối (treemap).

Đầu tiên, hàm group_by(Category) nhóm dữ liệu theo danh mục sản phẩm, sau đó summarise(TotalRevenue = sum(TotalAmount, na.rm = TRUE)) tính tổng doanh thu (TotalAmount) của từng danh mục, bỏ qua giá trị thiếu (na.rm = TRUE).

Tiếp đến, trong ggplot(), biến area = TotalRevenue quy định diện tích của từng khối tỉ lệ thuận với tổng doanh thu, giúp so sánh quy mô doanh thu giữa các danh mục. Tham số fill = Category tô màu khác nhau cho từng loại sản phẩm, còn label = Category hiển thị tên danh mục ngay giữa ô.

Lớp geom_treemap() vẽ các khối hình chữ nhật tỉ lệ với doanh thu, và geom_treemap_text() thêm nhãn trắng ở trung tâm khối để dễ nhận biết. Cuối cùng, theme_void() loại bỏ trục và khung thừa, tạo giao diện tối giản, còn labs() thêm tiêu đề cho biểu đồ.

Kết quả

Biểu đồ cho thấy tỷ trọng doanh thu giữa các danh mục sản phẩm được thể hiện bằng kích thước của từng khối màu. Quan sát hình, ta thấy Electronics (màu tím) chiếm diện tích lớn nhất, chứng tỏ đây là nhóm sản phẩm mang lại doanh thu cao nhất trong toàn bộ danh mục. Tiếp đến là Apparel (màu xanh lục), thể hiện mức doanh thu đáng kể nhưng thấp hơn một chút so với Electronics.

Hai danh mục còn lại — Books (màu xanh lam) và Accessories (màu cam)** — có diện tích nhỏ hơn rõ rệt, cho thấy chúng đóng góp tỷ trọng doanh thu thấp hơn.

Nhìn chung, biểu đồ treemap giúp nhận biết nhanh rằng Electronics và Apparel là hai danh mục chủ lực, trong khi Books và Accessories mang tính bổ trợ trong cơ cấu doanh thu của doanh nghiệp.

1.4.4 Trực quan hóa thời gian và hành vi

1.4.4.1 Line chart – Doanh thu theo tháng

library(dplyr)
library(ggplot2)
library(lubridate)

# Chuyển đổi định dạng ngày nếu cần
dataecommerce <- dataecommerce %>%
  mutate(PurchaseDate = as.Date(PurchaseDate, format = "%Y-%m-%d"))

dataecommerce %>%
  mutate(Month = lubridate::floor_date(PurchaseDate, "month")) %>%
  group_by(Month) %>%
  summarise(Doanh_thu = sum(TotalAmount, na.rm = TRUE)) %>%
  ggplot(aes(x = Month, y = Doanh_thu)) +
  geom_line(color = "steelblue", size = 1) +
  geom_point(color = "darkred", size = 2) +
  geom_smooth(se = FALSE, color = "orange") +
  labs(title = "Doanh thu theo tháng", x = "Thời gian", y = "Doanh thu (USD)") +
  theme_minimal()
## `geom_smooth()` using method = 'loess' and formula = 'y ~ x'

Giải thích kỹ thuật Đoạn mã trên trực quan hóa sự biến động doanh thu theo thời gian bằng biểu đồ đường (line chart) trong R. Cụ thể:

Xử lý dữ liệu thời gian:

- Cột PurchaseDate được chuyển sang định dạng ngày (as.Date).

- Sau đó, hàm floor_date(..., "month") trong lubridate được dùng để làm tròn về đầu tháng, giúp nhóm dữ liệu theo tháng.

Tính toán doanh thu:

- Hàm group_by(Month) nhóm dữ liệu theo tháng.

- summarise(Doanh_thu = sum(TotalAmount, na.rm = TRUE)) tính tổng doanh thu hàng tháng (bỏ qua giá trị bị thiếu).

Vẽ biểu đồ:

- geom_line() vẽ đường thể hiện xu hướng doanh thu (màu xanh thép).

- geom_point() thêm các điểm dữ liệu thật (màu đỏ sẫm).

- geom_smooth(se = FALSE, color = "orange") vẽ đường xu hướng mượt (smoothing line) màu cam, giúp nhận biết xu hướng tổng thể.

- theme_minimal() tạo giao diện gọn gàng, dễ đọc.

kết quả

Trong suốt năm 2021, doanh thu duy trì ổn định, dao động quanh mức 9–10 triệu USD mỗi tháng.

Một số thời điểm (như khoảng tháng 3 và tháng 10/2021) có biến động nhẹ, thể hiện các giai đoạn tăng nhẹ doanh thu, có thể liên quan đến chiến dịch khuyến mãi hoặc mùa mua sắm.

Tuy nhiên, từ tháng 11/2021 đến tháng 1/2022, doanh thu giảm mạnh và đột ngột — thể hiện rõ qua đường xanh giảm thẳng đứng và đường cam xu hướng đi xuống mạnh. → Điều này có thể do dữ liệu chưa hoàn tất cho tháng 12/2021 – 1/2022.

1.4.4.2 Area chart – Lượng đăng ký theo thời gian

signup_summary <- dataecommerce %>%
  mutate(SignUpDate = as.Date(SignUpDate),
         SignUpMonth = lubridate::floor_date(SignUpDate, "month")) %>%
  count(SignUpMonth)

ggplot(signup_summary, aes(SignUpMonth, n)) +
  geom_area(fill = "#69b3a2", alpha = 0.6) +
  labs(title = "Lượng đăng ký người dùng theo thời gian",
       x = "Thời gian", y = "Số người dùng") +
  theme_minimal()

Giải thích kỹ thuật

Đoạn mã trên được dùng để trực quan hóa số lượng người dùng mới đăng ký qua các tháng bằng biểu đồ vùng (area chart).

Trước hết, cột SignUpDate được chuyển sang định dạng ngày để có thể xử lý theo thời gian. Sau đó, hàm floor_date(..., "month") giúp gom nhóm dữ liệu theo từng tháng đăng ký. Cuối cùng, hàm count() đếm số lượng người đăng ký mỗi tháng và tạo ra bảng tổng hợp signup_summary.

Phần trực quan sử dụng geom_area() để hiển thị vùng màu xanh nhạt biểu diễn xu hướng số người đăng ký. Màu được chọn với mức alpha = 0.6 để vùng biểu đồ có độ trong suốt nhẹ, tạo cảm giác mềm và cân bằng thị giác. Giao diện được tối giản bằng theme_minimal() giúp biểu đồ gọn gàng và dễ quan sát.

Kết quả

Trong giai đoạn từ năm 2020 đến 2023, số lượng người dùng đăng ký duy trì khá ổn định, dao động quanh mức 2.700–3.000 người mỗi tháng. Nhìn chung, không có biến động quá lớn, chỉ xuất hiện vài điểm giảm nhẹ ngắn hạn, có thể do yếu tố mùa vụ hoặc thời gian bảo trì hệ thống.

Xu hướng tổng thể cho thấy nền tảng giữ được sức hút ổn định với người dùng mới qua các năm, không có dấu hiệu sụt giảm dài hạn. Đây là tín hiệu tích cực cho thấy khả năng duy trì độ nhận diện thương hiệu và hiệu quả marketing ổn định của doanh nghiệp.

1.4.4.3 Heatmap – Doanh thu theo giờ & ngày

dataecommerce %>%
  mutate(
    PurchaseDate = as.Date(PurchaseDate),
    Month = month(PurchaseDate, label = TRUE),
    Weekday = wday(PurchaseDate, label = TRUE, week_start = 1)
  ) %>%
  group_by(Weekday, Month) %>%
  summarise(Tong_doanh_thu = sum(TotalAmount, na.rm = TRUE)) %>%
  ggplot(aes(x = Month, y = Weekday, fill = Tong_doanh_thu)) +
  geom_tile(color = "white") +
  scale_fill_viridis_c() +
  labs(
    title = "Doanh thu theo tháng và ngày trong tuần",
    x = "Tháng",
    y = "Ngày trong tuần"
  ) +
  theme_minimal()
## `summarise()` has grouped output by 'Weekday'. You can override using the
## `.groups` argument.

Giải thích kỹ thuật

Biểu đồ trên là heatmap (bản đồ nhiệt), được tạo ra nhằm thể hiện sự phân bố doanh thu theo hai chiều: thời gian trong ngày và ngày trong tuần.

Đầu tiên mutate(Date = as.Date(PurchaseDate)) chuyển cột ngày về kiểu Date. month(Date, label = TRUE) và wday(Date, label = TRUE, week_start = 1) lần lượt trích tháng (Jan–Dec) và thứ trong tuần (Mon–Sun) dưới dạng nhãn. 

group_by(Weekday, Month) rồi summarise(Doanh_thu = sum(TotalAmount, na.rm = TRUE)) gom và tính tổng doanh thu cho từng cặp (ngày trong tuần, tháng). 

Trong phần vẽ, ggplot(aes(x = Month, y = Weekday, fill = Doanh_thu)) + geom_tile(color = "white") vẽ từng ô (tile) với màu theo Doanh_thu và viền trắng giữa các ô; scale_fill_viridis_c() chọn bảng màu dạng liên tục phù hợp phân biệt cường độ; labs() và theme_minimal() thêm nhãn và làm gọn bố cục. Kết quả là một ma trận màu thể hiện biến động doanh thu theo lịch.

Kết quả

Có các vùng sáng (màu vàng/xanh lá nhạt) tập trung rải rác, cho thấy những ngày cụ thể trong một số tháng có doanh thu cao hơn phần còn lại — tức có các “điểm nóng” theo lịch.

Cuối tuần (Sat / Sun) nhìn chung có nhiều ô sáng hơn so với giữa tuần, gợi ý doanh thu thường tăng vào cuối tuần.

Một vài tháng (ví dụ bạn có thể thấy các cột tương đối sáng ở May, Oct, Nov trong hình) có xu hướng doanh thu cao hơn trên nhiều ngày — có thể do mùa khuyến mãi/giải phóng hàng/đặc thù theo mùa.

Ngược lại, có ô rất tối (ví dụ giữa tuần ở một vài tháng) phản ánh ngày có doanh thu thấp bất thường — cần kiểm tra xem đó là do thực tế tiêu thụ thấp hay do thiếu dữ liệu.

Tổng thể: heatmap cho thấy mẫu chu kỳ theo tuần + biến động theo tháng — tức một số tháng/ ngày kết hợp tạo đỉnh doanh thu, không phải doanh thu phân bố đồng đều suốt năm.

1.4.4.4 Calendar plot – Doanh thu theo ngày

library(dplyr)
library(ggplot2)
library(lubridate)

dataecommerce %>%
  mutate(Date = as.Date(PurchaseDate)) %>%
  group_by(Date) %>%
  summarise(Doanh_thu = sum(TotalAmount, na.rm = TRUE)) %>%
  mutate(
    Week = isoweek(Date),
    Weekday = wday(Date, label = TRUE)
  ) %>%
  ggplot(aes(x = Weekday, y = Week, fill = Doanh_thu)) +
  geom_tile(color = "white") +
  scale_fill_gradient(low = "white", high = "red") +
  labs(
    title = "Doanh thu theo lịch trong năm",
    x = "Thứ trong tuần",
    y = "Tuần",
    fill = "Doanh thu"
  ) +
  theme_minimal()

Giải thích kỹ thuật

Biểu đồ này là heatmap (bản đồ nhiệt) thể hiện sự biến động doanh thu của doanh nghiệp trong từng ngày của năm, được tổ chức theo tuần (trục dọc) và thứ trong tuần (trục ngang).

Quy trình tạo biểu đồ gồm:

Chuyển cột ngày mua hàng (PurchaseDate) về dạng ngày (as.Date).

Gom nhóm theo ngày (group_by(Date)) để tính tổng doanh thu mỗi ngày (sum(TotalAmount)).

Trích xuất số tuần trong năm (isoweek()) và thứ trong tuần (wday()) từ ngày đó.

Dữ liệu sau đó được vẽ bằng geom_tile(), trong đó mỗi ô thể hiện doanh thu trong một ngày cụ thể.
Thang màu từ trắng → đỏ thể hiện mức độ doanh thu từ thấp đến cao, giúp dễ dàng nhận biết những giai đoạn “nóng” trong năm.

Kết quả

Biểu đồ cho thấy doanh thu có xu hướng biến động nhẹ giữa các tuần, nhưng nhìn chung phân bố tương đối đều. Một số điểm đỏ đậm xuất hiện vào đầu và giữa năm, cho thấy những giai đoạn doanh thu tăng mạnh — có thể trùng với các dịp khuyến mãi lớn hoặc mùa cao điểm mua sắm (ví dụ: lễ, Tết, hoặc Black Friday).

Bên cạnh đó, doanh thu giữa tuần (đặc biệt là thứ Hai và thứ Sáu) thường cao hơn, thể hiện rõ bằng các mảng đỏ đậm hơn — điều này phù hợp với thói quen tiêu dùng khi khách hàng thường mua sắm trực tuyến vào đầu hoặc cuối tuần làm việc.

Ngược lại, giữa tuần (Thứ Ba - Thứ Năm) có xu hướng nhạt màu hơn, phản ánh hoạt động mua sắm giảm nhẹ trong những ngày này.

Tổng thể, biểu đồ giúp doanh nghiệp nhận diện chu kỳ doanh thu theo thời gian, xác định thời điểm cao điểm và thấp điểm trong năm, từ đó tối ưu chiến dịch marketing và quản lý hàng tồn kho hiệu quả hơn.

1.4.4.5 Sankey chart – Hành trình mua hàng

library(ggalluvial)
library(dplyr)
library(ggplot2)

dataecommerce %>%
  count(Country, DeviceType, Category) %>%
  ggplot(aes(axis1 = Country, axis2 = DeviceType, axis3 = Category, y = n)) +
  geom_alluvium(aes(fill = Country), width = 1/12, alpha = 0.7) +
  geom_stratum(width = 1/12, fill = "grey80", color = "black") +
  geom_text(stat = "stratum", aes(label = after_stat(stratum)), size = 3) +
  scale_fill_brewer(type = "qual", palette = "Set3") +
  labs(
    title = "Sankey Chart – Hành trình mua hàng (Country → DeviceType → Category)",
    y = "Số lượng giao dịch"
  ) +
  theme_minimal()

Giải thích kỹ thuật

Biểu đồ Sankey (hay alluvial chart) được sử dụng để mô tả luồng chuyển động của dữ liệu giữa nhiều cấp độ phân loại. Trong trường hợp này, nó mô tả hành trình mua hàng của người tiêu dùng qua 3 giai đoạn:

Quốc gia (Country) – nơi khách hàng thực hiện giao dịch.

Thiết bị (DeviceType) – loại thiết bị mà khách hàng sử dụng để mua hàng (Desktop, Mobile, Tablet).

Danh mục sản phẩm (Category) – nhóm sản phẩm mà họ mua (Electronics, Apparel, Books, Accessories...).

Các bước thực hiện trong mã:

- count(Country, DeviceType, Category) tính số lượng giao dịch cho từng kết hợp ba yếu tố này.

- geom_alluvium() vẽ các dải luồng (flow) thể hiện tỷ lệ giao dịch di chuyển từ quốc gia → thiết bị → danh mục sản phẩm.

- geom_stratum() tạo các “cột” trung gian, tượng trưng cho từng nhóm ở mỗi giai đoạn.

- geom_text() gắn nhãn tên nhóm (quốc gia, thiết bị, danh mục).

- Màu sắc (fill = Country) giúp phân biệt luồng theo quốc gia.

Kết quả

Giai đoạn đầu – Quốc gia:

  • Các quốc gia như Ấn Độ, Mỹ, và Đức có luồng dày nhất → cho thấy số lượng giao dịch nhiều nhất.

Giai đoạn giữa – Thiết bị:

  • Mobile và Desktop là hai kênh mua sắm chính.

  • Ở các quốc gia như Ấn Độ và Mỹ, Mobile chiếm ưu thế, trong khi ở Đức và Anh, Desktop vẫn có tỷ trọng cao hơn. → Điều này gợi ý về sự khác biệt hành vi mua sắm theo khu vực địa lý.

Giai đoạn cuối – Danh mục sản phẩm:

  • Các luồng đậm dẫn đến Electronics và Apparel cho thấy đây là hai danh mục được mua nhiều nhất.

  • Ấn Độ và Mỹ có tỷ lệ cao ở Electronics, trong khi Anh và Đức lại mạnh ở Apparel và Books.

2 CHƯƠNG 2: PHÂN TÍCH NGÂN HÀNG THƯƠNG MẠI CỔ PHẦN NGOẠI THƯƠNG VIỆT NAM

2.1 GIỚI THIỆU BỘ DỮ LIỆU NGÂN HÀNG VCB

2.1.1 Giới thiệu chung về Ngân hàng TMCP Ngoại thương Việt Nam (Vietcombank – VCB)

Ngân hàng TMCP Ngoại thương Việt Nam, thường gọi tắt là Vietcombank (VCB), được chính thức thành lập vào ngày 01/04/1963 trên cơ sở tách ra từ Sở Quản lý Ngoại hối Trung ương của Ngân hàng Nhà nước Việt Nam. Ban đầu, Vietcombank hoạt động chủ yếu phục vụ các giao dịch thương mại đối ngoại; kể từ những năm 1990, ngân hàng đa dạng hoạt động sang thị trường nội địa và trở thành một ngân hàng thương mại đa năng với nhiều dịch vụ tài chính phong phú. Trong hơn 60 năm hoạt động, Vietcombank đã đóng góp quan trọng cho sự phát triển của nền kinh tế và luôn phát huy vai trò là “ngân hàng ngoại thương chủ lực” của đất nước. Ngân hàng cung cấp đầy đủ các dịch vụ tài chính hàng đầu trong lĩnh vực thương mại quốc tế và trong các hoạt động truyền thống (như huy động vốn, tín dụng, tài trợ dự án) cũng như các dịch vụ ngân hàng hiện đại (kinh doanh ngoại hối, công cụ phái sinh, dịch vụ thẻ, ngân hàng điện tử…).

Hiện nay, Vietcombank là một trong những ngân hàng thương mại lớn nhất tại Việt Nam. Đến giữa năm 2025, tổng tài sản hợp nhất của ngân hàng đạt khoảng 2,2 triệu tỷ đồng, dư nợ tín dụng gần 1,6 triệu tỷ đồng và tỷ lệ an toàn vốn (CAR) duy trì ở mức 11,8%. Hệ thống mạng lưới Vietcombank rất rộng lớn với hơn 600 chi nhánh và phòng giao dịch trong và ngoài nước, hơn 24.000 cán bộ nhân viên, cùng hệ thống trên 3.000 máy ATM và 121.000 điểm chấp nhận thẻ trên toàn quốc. Với quy mô vốn hóa thị trường khoảng 20 tỷ USD, Vietcombank được xếp vào danh sách 100 ngân hàng niêm yết lớn nhất toàn cầu. Ngân hàng cũng nhiều năm liền được vinh danh “Ngân hàng tốt nhất Việt Nam” và được các tổ chức xếp hạng tín nhiệm quốc tế (S&P, Fitch, Moody’s) đánh giá ở mức cao nhất trong hệ thống ngân hàng Việt Nam

2.1.2 Lý do lựa chọn Vietcombank để phân tích

Có nhiều lý do thuyết phục để chọn Vietcombank làm đối tượng phân tích tài chính. Đầu tiên, Vietcombank chiếm vị thế dẫn đầu trong ngành với năng lực tài chính vững chắc và hiệu quả hoạt động vượt trội. Theo Vietnam Report, ngân hàng này 9 năm liên tiếp dẫn đầu danh sách “Top 10 ngân hàng thương mại Việt Nam uy tín”, thể hiện uy tín thương hiệu và tầm ảnh hưởng hàng đầu. Về kết quả kinh doanh, Vietcombank thường xuyên đạt lợi nhuận trước thuế cao nhất thị trường; lợi nhuận trước thuế năm 2024 của ngân hàng vượt mốc 1,5 tỷ USD. Thứ hai, báo cáo tài chính của Vietcombank được đánh giá minh bạch và đáng tin cậy: toàn bộ báo cáo đều được kiểm toán độc lập và công khai đầy đủ, và các cơ quan xếp hạng tín nhiệm quốc tế như S&P, Fitch, Moody’s đều duy trì đánh giá ở mức cao nhất đối với. Ngoài ra, quy mô tài sản lớn và vai trò chủ lực của Vietcombank trong hệ thống tài chính – đặc biệt trong mảng thanh toán quốc tế – khiến cổ phiếu VCB được giới đầu tư trong và ngoài nước quan tâm theo dõi rộng rãi.

2.1.3 Phạm vi và nguồn dữ liệu

Phân tích trong đề tài này sẽ sử dụng dữ liệu từ các báo cáo tài chính hợp nhất của Vietcombank trong giai đoạn 2015–2024. Cụ thể, ba báo cáo chính bao gồm Bảng Cân đối Kế toán (CĐKT), Báo cáo Kết quả Hoạt động Kinh doanh (KQHĐKD) và Báo cáo Lưu chuyển Tiền tệ (LCTT) sẽ được tập hợp và phân tích. Tất cả số liệu đều được trích xuất từ các báo cáo tài chính hàng năm chính thức của Vietcombank đã được kiểm toán, đảm bảo tính chính xác và minh bạch cao cho kết quả nghiên cứu.

Nguồn tham khảo:Tài liệu báo cáo tài chính được lấy trực tiếp từ các báo cáo hợp nhất đã công bố hàng năm của Vietcombank (2015–2024).

Sau khi chọn lọc, trích xuất dữ liệu và loại bỏ các dữ liệu không thống nhất qua các năm từ báo cáo tài chính của doanh nghiệp. Nhóm tác giả đã xuất ra các biến sau từ bảng báo cáo tài chính, đồng thời đặt tên viết tắt cho các biến để tránh trường hợp ngôn ngữ có dấu xuất hiện các lỗi không mong muốn.

library(readxl)
chuonghai <- read_xlsx(file.choose())
dim(chuonghai)
## [1] 10 47
names(chuonghai)
##  [1] "Nam"            "TSNH"           "T_KTDT"         "CKPT"          
##  [5] "TSDH"           "TSCD"           "TSCDHH"         "NG_TSCDHH"     
##  [9] "GTHMLK_ TSCDHH" "TSCDVV"         "NG_TSCDVH"      "GTHMLK_TSCDVH" 
## [13] "TSDHK"          "CPTTDH"         "TTS"            "NPT"           
## [17] "NNH"            "VVNNH"          "PTHDCK"         "TVCKPNNH"      
## [21] "PTNLD"          "CPPT"           "QKTPL"          "PTGDTP"        
## [25] "NDH"            "VCSH"           "VDL"            "VK"            
## [29] "QDPTC"          "TNV"            "DT"             "LNTT"          
## [33] "LNSTTNDN"       "CPQLDN"         "CPHDKD"         "DTCK"          
## [37] "DTBLPHCK"       "DTHDTV"         "DTLKCK"         "DTK"           
## [41] "TNK"            "CPTTNDNHH"      "CPTTNDNHL"      "LCTTHDKD"      
## [45] "TTTNGV"         "LCTTHDTC"       "LCTTTN"

Kết quả sau khi xuất cho thấy bộ dữ liệu có 10 quan sát tương ứng với 10 năm từ 2015-2024 và 47 biến tương ứng với số liệu đã chọn lọc thu thập từ báo cáo tài chính của doanh nghiệp VCB.

Phụ lục viết tắt của các biến

library(dplyr)
library(knitr)
library(kableExtra)

ten_bien <- c(
"Năm", "TÀI SẢN NGẮN HẠN", "Tiền và các khoản tương đương tiền", "Các khoản phải thu khác",
"TÀI SẢN DÀI HẠN", "Tài sản cố định", "Tài sản cố định hữu hình", "Nguyên giá TSCDHH",
"Giá trị hao mòn lũy kế TSCDHH", "Tài sản cố định vô hình", "Nguyên giá TSCDVH",
"Giá trị hao mòn lũy kế TSCDVH", "Tài sản dài hạn khác", "Chi phí trả trước dài hạn",
"TỔNG CỘNG TÀI SẢN", "NỢ PHẢI TRẢ", "Nợ ngắn hạn", "Vay và nợ ngắn hạn",
"Phải trả hoạt động giao dịch chứng khoán", "Thuế và các khoản phải nộp Nhà nước",
"Phải trả người lao động", "Chi phí phải trả", "Quỹ khen thưởng và phúc lợi",
"Phải trả giao dịch mua bán lại trái phiếu", "Nợ dài hạn", "VỐN CHỦ SỞ HỮU",
"Vốn điều lệ", "Vốn khác", "Quỹ dự phòng tài chính", "TỔNG NGUỒN VỐN", "Doanh thu",
"Lợi nhuận trước thuế", "Lợi nhuận sau thuế TNDN", "Chi phí quản lý doanh nghiệp",
"Chi phí hoạt động kinh doanh", "Doanh thu hoạt động môi giới chứng khoán",
"Doanh thu bảo lãnh phát hành chứng khoán", "Doanh thu hoạt động tư vấn",
"Doanh thu lưu ký chứng khoán", "Doanh thu khác", "Thu nhập khác",
"Chi phí thuế TNDN hiện hành", "Chi phí thuế TNDN hoãn lại",
"Lưu chuyển tiền thuần từ hoạt động kinh doanh", "Tiền chi trả nợ gốc vay",
"Lưu chuyển tiền thuần từ hoạt động tài chính", "Lưu chuyển tiền thuần trong năm"
)

viet_tat <- c(
"Nam", "TSNH", "T_KTDT", "CKPT", "TSDH", "TSCD", "TSCDHH", "NG_TSCDHH",
"GTHMLK_TSCDHH", "TSCDVH", "NG_TSCDVH", "GTHMLK_TSCDVH", "TSDHK", "CPTTDH",
"TTS", "NPT", "NNH", "VVNNH", "PTHDCK", "TVCKPNNH", "PTNLD", "CPPT", "QKTPL",
"PTGDTP", "NDH", "VCSH", "VDL", "VK", "QDPTC", "TNV", "DT", "LNTT", "LNSTTNDN",
"CPQLDN", "CPHDKD", "DTCK", "DTBLPHCK", "DTHDTV", "DTLKCK", "DTK", "TNK",
"CPTTNDNHH", "CPTTNDNHL", "LCTTHDKD", "TTTNGV", "LCTTHDTC", "LCTTTN"
)

bang_viettat <- data.frame(
"Tên đầy đủ" = ten_bien,
"Viết tắt" = viet_tat
)

bang_viettat %>%
kable("html", caption = " Bảng viết tắt tên biến", align = c("l", "c")) %>%
kable_styling(
bootstrap_options = c("striped", "hover", "condensed", "responsive"),
full_width = FALSE,
font_size = 13
) %>%
row_spec(0, bold = TRUE, color = "white", background = "#2c3e50") %>%
column_spec(1, width = "60%") %>%
column_spec(2, bold = TRUE, color = "#2c3e50") %>%
add_header_above(c(" " = 1, "Danh mục viết tắt" = 1), bold = TRUE, color = "white", background = "#1abc9c")
Bảng viết tắt tên biến
Danh mục viết tắt
Tên.đầy.đủ Viết.tắt
Năm Nam
TÀI SẢN NGẮN HẠN TSNH
Tiền và các khoản tương đương tiền T_KTDT
Các khoản phải thu khác CKPT
TÀI SẢN DÀI HẠN TSDH
Tài sản cố định TSCD
Tài sản cố định hữu hình TSCDHH
Nguyên giá TSCDHH NG_TSCDHH
Giá trị hao mòn lũy kế TSCDHH GTHMLK_TSCDHH
Tài sản cố định vô hình TSCDVH
Nguyên giá TSCDVH NG_TSCDVH
Giá trị hao mòn lũy kế TSCDVH GTHMLK_TSCDVH
Tài sản dài hạn khác TSDHK
Chi phí trả trước dài hạn CPTTDH
TỔNG CỘNG TÀI SẢN TTS
NỢ PHẢI TRẢ NPT
Nợ ngắn hạn NNH
Vay và nợ ngắn hạn VVNNH
Phải trả hoạt động giao dịch chứng khoán PTHDCK
Thuế và các khoản phải nộp Nhà nước TVCKPNNH
Phải trả người lao động PTNLD
Chi phí phải trả CPPT
Quỹ khen thưởng và phúc lợi QKTPL
Phải trả giao dịch mua bán lại trái phiếu PTGDTP
Nợ dài hạn NDH
VỐN CHỦ SỞ HỮU VCSH
Vốn điều lệ VDL
Vốn khác VK
Quỹ dự phòng tài chính QDPTC
TỔNG NGUỒN VỐN TNV
Doanh thu DT
Lợi nhuận trước thuế LNTT
Lợi nhuận sau thuế TNDN LNSTTNDN
Chi phí quản lý doanh nghiệp CPQLDN
Chi phí hoạt động kinh doanh CPHDKD
Doanh thu hoạt động môi giới chứng khoán DTCK
Doanh thu bảo lãnh phát hành chứng khoán DTBLPHCK
Doanh thu hoạt động tư vấn DTHDTV
Doanh thu lưu ký chứng khoán DTLKCK
Doanh thu khác DTK
Thu nhập khác TNK
Chi phí thuế TNDN hiện hành CPTTNDNHH
Chi phí thuế TNDN hoãn lại CPTTNDNHL
Lưu chuyển tiền thuần từ hoạt động kinh doanh LCTTHDKD
Tiền chi trả nợ gốc vay TTTNGV
Lưu chuyển tiền thuần từ hoạt động tài chính LCTTHDTC
Lưu chuyển tiền thuần trong năm LCTTTN

2.2 XỬ LÝ, CHỌN LỌC, ĐÁNH GIÁ DỮ LIỆU

2.2.1 Lựa chọn dữ liệu biến chính để phân tích Vietcombank

Lí do lựa chọn và đặt tên cho bộ mới

Khi phân tích tài chính, các nhà doanh nghiệp luôn chú tâm vào các biến phản ánh đầy đủ các khía cạnh then chốt trong phân tích tài chính ngân hàng. Chính vì thế, nhóm tác giả quyết định chọn các biến có khả năng phản chiếu sức khỏe tài chính của doanh nghiệp, cụ thể là nhóm chỉ số thanh khoản, đòn bẩy (cấu trúc vốn), hiệu quả hoạt động và khả năng sinh lời.

Trong đó, Tiền và các khoản tương đương tiền là tài sản có tính thanh khoản cao nhất trên bảng cân đối kế toán, cho phép đánh giá khả năng chi trả ngắn hạn; Lưu chuyển tiền thuần từ HĐKD thể hiện dòng tiền thực từ hoạt động cốt lõi, giúp đánh giá tính bền vững và chất lượng lợi nhuận của Vietcombank.

Các chỉ tiêu Tài sản dài hạn, Tổng tài sản, Nợ phải trả, Vốn chủ sở hữu và Tổng nguồn vốn phản ánh quy mô và cơ cấu tài sản - nguồn vốn của ngân hàng (theo phương trình tài sản = nợ + vốn chủ sở hữu), qua đó thể hiện mức độ sử dụng đòn bẩy tài chính.

Các chỉ số Doanh thu, Lợi nhuận trước thuế và Lợi nhuận sau thuế cho biết khả năng sinh lời của ngân hàng, trong khi Chi phí hoạt động kinh doanh phản ánh hiệu quả quản lý chi phí.

Cuối cùng, Quỹ khen thưởng và phúc lợi được đưa vào nhằm tính toán đầy đủ các khoản trích lập từ lợi nhuận cho phúc lợi nhân viên, đây là một phần của vốn chủ sở hữu ngân hàng.

# Nạp thư viện hỗ trợ (nếu muốn dùng cú pháp gọn)
library(dplyr)

# Chọn 12 biến cần phân tích và lưu vào dataset mới tên là VCBbctc
VCBbctc <- chuonghai %>%
  select(
    Nam,
    T_KTDT,       # Tiền và các khoản tương đương tiền
    TSDH,         # Tài sản dài hạn
    TTS,          # Tổng cộng tài sản
    NPT,          # Nợ phải trả
    QKTPL,        # Quỹ khen thưởng và phúc lợi
    VCSH,         # Vốn chủ sở hữu
    TNV,          # Tổng nguồn vốn
    DT,           # Doanh thu
    LNTT,         # Lợi nhuận trước thuế
    LNSTTNDN,     # Lợi nhuận sau thuế TNDN
    CPHDKD,       # Chi phí hoạt động kinh doanh
    LCTTHDKD      # Lưu chuyển tiền thuần từ hoạt động kinh doanh
  )

# Kiểm tra tên biến trong bộ dữ liệu mới
names(VCBbctc)
##  [1] "Nam"      "T_KTDT"   "TSDH"     "TTS"      "NPT"      "QKTPL"   
##  [7] "VCSH"     "TNV"      "DT"       "LNTT"     "LNSTTNDN" "CPHDKD"  
## [13] "LCTTHDKD"
# Xem trước vài dòng đầu của dữ liệu mới
head(VCBbctc)

Giải thích kỹ thuật

library(dplyr): nạp gói hỗ trợ xử lý dữ liệu với cú pháp gọn.

VCBbctc <- chuonghai %>% select(...): tạo bộ dữ liệu mới VCBbctc bằng cách chọn 12 biến cần phân tích từ dữ liệu gốc chuonghai.

%>%: toán tử pipe, truyền kết quả của bước trước sang bước sau.

select(): chọn các cột cụ thể trong dữ liệu (giống lệnh SELECT trong SQL).

names(VCBbctc): hiển thị danh sách tên biến trong bộ dữ liệu mới để kiểm tra.

head(VCBbctc): xem nhanh vài dòng đầu của dữ liệu mới để đối chiếu và xác nhận.

Đổi đơn vị

Trong quá trình xử lý dữ liệu tài chính của Ngân hàng Vietcombank, các chỉ tiêu được ghi nhận theo đơn vị VND với giá trị tuyệt đối rất lớn, gây khó khăn cho việc quan sát, so sánh và tính toán các tỷ suất tài chính. Do đó, nhóm nghiên cứu quy đổi toàn bộ các biến giá trị sang đơn vị tỷ đồng (tỷ VND) nhằm chuẩn hóa dữ liệu, đảm bảo tính dễ đọc, đồng nhất và thuận tiện cho quá trình phân tích, trực quan hóa kết quả.

# Chuyển đơn vị từ VND sang tỷ VND
VCBbctc <- VCBbctc %>%
  mutate(across(
    # danh sách các biến có giá trị tài chính (trừ cột Năm)
    c(T_KTDT, TSDH, TTS, NPT, QKTPL, VCSH, TNV, DT, LNTT, LNSTTNDN, CPHDKD, LCTTHDKD),
    ~ . / 1e9
  ))

2.2.2 Thêm biến vào bộ dữ liệu

Để đánh giá toàn diện tình hình tài chính và hiệu quả hoạt động của Ngân hàng TMCP Ngoại thương Việt Nam (Vietcombank), việc chỉ xem xét các chỉ tiêu kế toán cơ bản là chưa đủ. Do đó, nhóm tiến hành bổ sung một hệ thống 10 chỉ số tài chính tổng hợp, được tính toán dựa trên dữ liệu từ báo cáo tài chính giai đoạn 2015–2024. Các chỉ số này giúp phản ánh rõ nét hiệu quả sinh lời (ROA, ROE, Biên lợi nhuận ròng), mức độ an toàn tài chính và đòn bẩy (CAR, D/E, Tỷ trọng vốn chủ sở hữu), khả năng thanh khoản (Tỷ lệ tiền mặt), tốc độ phát triển (Tăng trưởng tổng tài sản, Tăng trưởng lợi nhuận sau thuế), cũng như chất lượng lợi nhuận (CFKD/LNST).

Việc bổ sung các biến này giúp chuyển đổi dữ liệu tài chính thô thành các thước đo phân tích định lượng, qua đó hỗ trợ đánh giá hiệu quả hoạt động, khả năng tăng trưởng bền vững và sức khỏe tài chính của ngân hàng một cách khoa học và có hệ thống.

VCBbctc <- VCBbctc %>%
  mutate(
    ROA = LNSTTNDN / TTS,
    ROE = LNSTTNDN / VCSH,
    Bien_LN_rong = LNSTTNDN / DT,
    CAR = VCSH / TTS,
    DE = NPT / VCSH,
    Tytrong_VCSH = VCSH / TNV,
    Ty_le_tien_mat = T_KTDT / TTS,
    Tangtruong_TS = (TTS - lag(TTS)) / lag(TTS),
    Tangtruong_LNST = (LNSTTNDN - lag(LNSTTNDN)) / lag(LNSTTNDN),
    Chatluong_LN = LCTTHDKD / LNSTTNDN
  )

Cơ sở lý thuyết các biến thêm

Ý nghĩa các biến

library(dplyr)
colnames(VCBbctc)
##  [1] "Nam"             "T_KTDT"          "TSDH"            "TTS"            
##  [5] "NPT"             "QKTPL"           "VCSH"            "TNV"            
##  [9] "DT"              "LNTT"            "LNSTTNDN"        "CPHDKD"         
## [13] "LCTTHDKD"        "ROA"             "ROE"             "Bien_LN_rong"   
## [17] "CAR"             "DE"              "Tytrong_VCSH"    "Ty_le_tien_mat" 
## [21] "Tangtruong_TS"   "Tangtruong_LNST" "Chatluong_LN"
# Cài gói nếu chưa có
# install.packages(c("knitr", "kableExtra", "writexl"))

library(knitr)
library(kableExtra)

# Tạo bảng mô tả biến
mo_ta_bien <- data.frame(
  `Tên biến` = c(
    # 12 biến gốc
    "T_KTDT", "TSDH", "TTS", "NPT", "QKTPL", "VCSH", "TNV",
    "DT", "LNTT", "LNSTTNDN", "CPHDKD", "LCTTHDKD",
    # 10 biến bổ sung
    "ROA", "ROE", "Bien_LN_rong", "CAR", "DE",
    "Tytrong_VCSH", "Ty_le_tien_mat",
    "Tangtruong_TS", "Tangtruong_LNST", "Chatluong_LN"
  ),
  
  `Ý nghĩa` = c(
    # Ý nghĩa 12 biến gốc
    "Tiền và các khoản tương đương tiền – phản ánh khả năng thanh toán tức thời.",
    "Tài sản dài hạn – thể hiện quy mô đầu tư dài hạn, năng lực hoạt động ổn định.",
    "Tổng cộng tài sản – thể hiện quy mô tổng thể của ngân hàng.",
    "Nợ phải trả – phản ánh nghĩa vụ tài chính và mức độ sử dụng đòn bẩy.",
    "Quỹ khen thưởng và phúc lợi – thể hiện chính sách đãi ngộ nhân viên.",
    "Vốn chủ sở hữu – nguồn vốn tự có, cơ sở đánh giá khả năng tự chủ tài chính.",
    "Tổng nguồn vốn – tổng cộng các nguồn vốn hình thành nên tài sản của ngân hàng.",
    "Doanh thu – tổng thu nhập từ hoạt động kinh doanh.",
    "Lợi nhuận trước thuế – phản ánh hiệu quả hoạt động trước nghĩa vụ thuế.",
    "Lợi nhuận sau thuế TNDN – lợi nhuận ròng, thước đo hiệu quả cuối cùng.",
    "Chi phí hoạt động kinh doanh – phản ánh hiệu quả quản lý chi phí vận hành.",
    "Lưu chuyển tiền thuần từ hoạt động kinh doanh – phản ánh chất lượng lợi nhuận.",
    
    # Ý nghĩa 10 biến bổ sung
    "ROA – Hiệu quả sử dụng tài sản (LNST / Tổng tài sản).",
    "ROE – Hiệu quả sử dụng vốn chủ sở hữu (LNST / VCSH).",
    "Biên lợi nhuận ròng – Tỷ lệ lợi nhuận ròng trên doanh thu.",
    "CAR – Tỷ lệ an toàn vốn xấp xỉ (VCSH / Tổng tài sản).",
    "D/E – Hệ số nợ trên vốn chủ sở hữu (NPT / VCSH).",
    "Tỷ trọng vốn chủ sở hữu – Tỷ lệ VCSH / Tổng nguồn vốn.",
    "Tỷ lệ tiền mặt – Tỷ lệ tiền & tương đương tiền / Tổng tài sản.",
    "Tăng trưởng tổng tài sản – tốc độ tăng TTS so với năm trước.",
    "Tăng trưởng lợi nhuận sau thuế – tốc độ tăng LNST năm nay so với năm trước.",
    "CFKD/LNST – Chất lượng lợi nhuận (tiền thuần HĐKD / LNST)."
  )
)

# Tạo bảng màu đẹp
mo_ta_bien %>%
  kable("html", caption = "Bảng: Ý nghĩa các biến sử dụng trong phân tích tài chính Vietcombank") %>%
  kable_styling(
    bootstrap_options = c("striped", "hover", "condensed", "responsive"),
    full_width = FALSE,
    font_size = 13,
    position = "center"
  ) %>%
  column_spec(1, bold = TRUE, color = "white", background = "#004C97", width = "8em") %>%   # Cột biến nền xanh đậm
  column_spec(2, background = "#F5F7FA", width = "35em") %>%                                # Cột mô tả nền xám nhạt
  row_spec(0, bold = TRUE, background = "#007BFF", color = "white", font_size = 14) %>%     # Header màu xanh sáng
  kable_paper("hover", full_width = FALSE)
Bảng: Ý nghĩa các biến sử dụng trong phân tích tài chính Vietcombank
Tên.biến Ý.nghĩa
T_KTDT Tiền và các khoản tương đương tiền – phản ánh khả năng thanh toán tức thời.
TSDH Tài sản dài hạn – thể hiện quy mô đầu tư dài hạn, năng lực hoạt động ổn định.
TTS Tổng cộng tài sản – thể hiện quy mô tổng thể của ngân hàng.
NPT Nợ phải trả – phản ánh nghĩa vụ tài chính và mức độ sử dụng đòn bẩy.
QKTPL Quỹ khen thưởng và phúc lợi – thể hiện chính sách đãi ngộ nhân viên.
VCSH Vốn chủ sở hữu – nguồn vốn tự có, cơ sở đánh giá khả năng tự chủ tài chính.
TNV Tổng nguồn vốn – tổng cộng các nguồn vốn hình thành nên tài sản của ngân hàng.
DT Doanh thu – tổng thu nhập từ hoạt động kinh doanh.
LNTT Lợi nhuận trước thuế – phản ánh hiệu quả hoạt động trước nghĩa vụ thuế.
LNSTTNDN Lợi nhuận sau thuế TNDN – lợi nhuận ròng, thước đo hiệu quả cuối cùng.
CPHDKD Chi phí hoạt động kinh doanh – phản ánh hiệu quả quản lý chi phí vận hành.
LCTTHDKD Lưu chuyển tiền thuần từ hoạt động kinh doanh – phản ánh chất lượng lợi nhuận.
ROA ROA – Hiệu quả sử dụng tài sản (LNST / Tổng tài sản).
ROE ROE – Hiệu quả sử dụng vốn chủ sở hữu (LNST / VCSH).
Bien_LN_rong Biên lợi nhuận ròng – Tỷ lệ lợi nhuận ròng trên doanh thu.
CAR CAR – Tỷ lệ an toàn vốn xấp xỉ (VCSH / Tổng tài sản).
DE D/E – Hệ số nợ trên vốn chủ sở hữu (NPT / VCSH).
Tytrong_VCSH Tỷ trọng vốn chủ sở hữu – Tỷ lệ VCSH / Tổng nguồn vốn.
Ty_le_tien_mat Tỷ lệ tiền mặt – Tỷ lệ tiền & tương đương tiền / Tổng tài sản.
Tangtruong_TS Tăng trưởng tổng tài sản – tốc độ tăng TTS so với năm trước.
Tangtruong_LNST Tăng trưởng lợi nhuận sau thuế – tốc độ tăng LNST năm nay so với năm trước.
Chatluong_LN CFKD/LNST – Chất lượng lợi nhuận (tiền thuần HĐKD / LNST).

2.2.3 Kiểm tra, đánh giá bộ dữ liệu

2.2.3.1 Kiểm tra biến thiếu

sum(is.na(VCBbctc))
## [1] 2

Kết quả cho thấy bộ dữ liệu của VCB có 2 giá trị NA (của biến tăng trưởng tài sản và tăng trưởng lợi nhuận sau thuế), tuy nhiên nhóm tác giả chấp nhận biến thiếu này vì tăng trưởng của tài sản và tăng trưởng lợi nhuận sau thuế không thể tính năm 2015 do không có số liệu của 2014.

2.2.3.2 Kiểm tra biến trùng lặp

sum(duplicated(VCBbctc))
## [1] 0

Kết quả cho thấy không có dòng trùng lặp trong bộ dữ liệu.

2.2.3.3 Kiểm tra giá trị bất thường

2.3 THỰC HIỆN CÁC THỐNG KÊ MÔ TẢ

2.3.1 Phân tổ dữ liệu

2.3.1.1 Phân tổ theo quy mô tài sản

Trong phân tích tài chính ngân hàng, quy mô tổng tài sản (TTS) là một trong những chỉ tiêu nền tảng phản ánh mức độ lớn nhỏ, khả năng huy động vốn và tiềm lực hoạt động của ngân hàng.

Phân tổ theo quy mô tài sản giúp so sánh hiệu quả hoạt động (ROA, ROE), an toàn vốn (CAR) hay chất lượng lợi nhuận giữa các nhóm quy mô khác nhau.

library(dplyr)

# 1. Phân tổ theo quy mô tổng tài sản (theo tứ phân vị)
VCBbctc <- VCBbctc %>%
  mutate(Nhom_Quymo_TS = cut(TTS,
                             breaks = quantile(TTS, probs = seq(0, 1, 0.25), na.rm = TRUE),
                             include.lowest = TRUE,
                             labels = c("Nhỏ", "Vừa", "Lớn", "Rất lớn")))

# 2. Bảng tần suất số quan sát mỗi nhóm
table(VCBbctc$Nhom_Quymo_TS)
## 
##     Nhỏ     Vừa     Lớn Rất lớn 
##       3       2       2       3
# 3. Thống kê trung bình các chỉ tiêu tài chính theo nhóm quy mô
Thongke_Quymo <- VCBbctc %>%
  group_by(Nhom_Quymo_TS) %>%
  summarise(
    So_nam = n(),
    ROA_tb = mean(ROA, na.rm = TRUE),
    ROE_tb = mean(ROE, na.rm = TRUE),
    CAR_tb = mean(CAR, na.rm = TRUE),
    DE_tb = mean(DE, na.rm = TRUE),
    ChatluongLN_tb = mean(Chatluong_LN, na.rm = TRUE)
  )

Thongke_Quymo

Giải thích kỹ thuật

cut()   Chia tổng tài sản thành 4 nhóm (Nhỏ – Vừa – Lớn – Rất lớn) dựa theo tứ phân vị.

table() Đếm số lượng quan sát trong từng nhóm — kết quả bạn gửi ảnh: Nhỏ (3), Vừa (2), Lớn (2), Rất lớn (3).

group_by(Nhom_Quymo_TS) Gom dữ liệu theo nhóm quy mô tài sản.

summarise() Tính trung bình của các chỉ tiêu tài chính cho từng nhóm.

mean(..., na.rm = TRUE) Tính giá trị trung bình, bỏ qua giá trị thiếu (NA).

So_nam = n()    Đếm số quan sát trong từng nhóm (tức là số năm).

Ý nghĩa thống kê

ROA và ROE tăng dần theo quy mô → ngân hàng lớn có hiệu quả sinh lời cao hơn.

CAR giảm mạnh (từ 0.43 xuống 0.32) → cho thấy mức độ an toàn vốn giảm, tức là các ngân hàng quy mô lớn phụ thuộc nhiều hơn vào nguồn vốn vay.

D/E tăng đều (1.32 → 2.22) → thể hiện xu hướng sử dụng đòn bẩy tài chính cao khi mở rộng hoạt động.

Chất lượng lợi nhuận (CFKD/LNST) biến động mạnh, chỉ nhóm “Lớn” có giá trị dương → phản ánh lợi nhuận gắn với dòng tiền thực, trong khi các nhóm khác chủ yếu lợi nhuận kế toán.

Tổng kết:

Khi quy mô tài sản tăng, hiệu quả sinh lời được cải thiện rõ rệt, tuy nhiên đi kèm là rủi ro tài chính và sự suy giảm chất lượng lợi nhuận.

Nhóm “Lớn” (không quá nhỏ cũng không quá lớn) cho thấy cấu trúc tài chính cân bằng nhất, vừa đạt hiệu quả sinh lời tốt, vừa duy trì chất lượng dòng tiền ổn định.

2.3.2 Phân tích đơn biến

2.3.2.1 Phân tích biến Tổng tài sản

Biến Tổng tài sản (TTS) phản ánh quy mô hoạt động và năng lực tài chính tổng thể của ngân hàng. Đối với Vietcombank, việc phân tích TTS qua thời gian giúp đánh giá xu hướng mở rộng quy mô, khả năng tăng trưởng bền vững, và vị thế cạnh tranh trong hệ thống ngân hàng thương mại.

Phân tích dưới đây tập trung vào thống kê mô tả, giúp định lượng mức độ biến động và tính ổn định của quy mô tài sản Vietcombank trong giai đoạn nghiên cứu.

# --- Phân tích đơn biến: TTS (Tổng tài sản) ---

VCBbctc %>%
  summarise(
    So_nam = n(),
    Gia_tri_trung_binh = mean(TTS, na.rm = TRUE),
    Trung_vi = median(TTS, na.rm = TRUE),
    Lon_nhat = max(TTS, na.rm = TRUE),
    Nho_nhat = min(TTS, na.rm = TRUE),
    Do_lech_chuan = sd(TTS, na.rm = TRUE),
    Heso_Bien_thien = sd(TTS, na.rm = TRUE) / mean(TTS, na.rm = TRUE) * 100,
    Q1 = quantile(TTS, 0.25, na.rm = TRUE),
    Q3 = quantile(TTS, 0.75, na.rm = TRUE)
  )

Giải thích kỹ thuật

summarise() Tạo bảng tổng hợp thống kê cho biến TTS.
n() Đếm số quan sát (số năm dữ liệu).
mean(TTS, na.rm = TRUE) Tính giá trị trung bình, thể hiện quy mô tài sản trung bình qua các năm.
median(TTS, na.rm = TRUE)   Trung vị – giá trị giữa dãy, phản ánh xu hướng trung tâm bền vững hơn trung bình khi có ngoại lệ.
max(), min()    Xác định giá trị lớn nhất – nhỏ nhất, giúp nhận diện phạm vi biến động.
sd()    Độ lệch chuẩn – đo mức độ dao động tuyệt đối của tổng tài sản giữa các năm.
sd()/mean()*100 Hệ số biến thiên (CV) – đo mức độ biến động tương đối (%), CV nhỏ → ổn định.
quantile(..., 0.25/0.75)    Lấy tứ phân vị Q1, Q3, giúp hiểu phạm vi giữa nhóm dữ liệu trung bình (khoảng 50% giữa).

Giải thích & Ý nghĩa thống kê

Giá trị trung bình: 6.223,6 tỷ đồng – phản ánh quy mô tài sản khá lớn.

Trung vị: 4.696,9 tỷ đồng < trung bình → xu hướng tăng nhanh ở các năm gần đây.

Phạm vi (2.594–12.899) cho thấy mức tăng mạnh gấp gần 5 lần sau 10 năm.

Độ lệch chuẩn 3.901 và CV = 62,7% → biến động cao, đặc trưng cho giai đoạn mở rộng quy mô nhanh.

Khoảng Q1–Q3 (≈3.000–8.800) thể hiện phần lớn giá trị tập trung quanh mức trung bình.

Kết luận: Tổng tài sản Vietcombank tăng nhanh, quy mô mở rộng mạnh mẽ, phản ánh năng lực tài chính và sức cạnh tranh vững chắc, dù mức dao động tương đối lớn do quá trình tăng trưởng mạnh mẽ.

2.3.2.2 Lợi nhuận sau thuế thu nhập doanh nghiệp

Lợi nhuận sau thuế TNDN (LNSTTNDN) là chỉ tiêu phản ánh hiệu quả hoạt động cuối cùng của ngân hàng sau khi đã trừ toàn bộ chi phí và nghĩa vụ thuế. Phân tích biến này giúp đánh giá mức độ sinh lời ròng và khả năng tạo giá trị thực tế cho cổ đông của Vietcombank.

VCBbctc %>%
  summarise(
    So_nam = n(),
    Gia_tri_trung_binh = mean(LNSTTNDN, na.rm = TRUE),
    Trung_vi = median(LNSTTNDN, na.rm = TRUE),
    Lon_nhat = max(LNSTTNDN, na.rm = TRUE),
    Nho_nhat = min(LNSTTNDN, na.rm = TRUE),
    Do_lech_chuan = sd(LNSTTNDN, na.rm = TRUE),
    Heso_Bien_thien = sd(LNSTTNDN, na.rm = TRUE)/mean(LNSTTNDN, na.rm = TRUE)*100,
    Q1 = quantile(LNSTTNDN, 0.25, na.rm = TRUE),
    Q3 = quantile(LNSTTNDN, 0.75, na.rm = TRUE)
  )

Ý nghĩa thống kê

Giá trị trung bình 265,3, trung vị 187,3 → lợi nhuận ròng có xu hướng tăng mạnh ở các năm gần đây.

Khoảng dao động lớn (65,7–585,9) → phản ánh tốc độ tăng trưởng nhanh nhưng chưa ổn định.

Độ lệch chuẩn 191,0 và CV = 72% → biến động tương đối cao, thể hiện giai đoạn mở rộng lợi nhuận mạnh mẽ.

Khoảng Q1–Q3 (147–431) cho thấy 50% giá trị lợi nhuận tập trung quanh mức trung bình.

Kết luận ngắn gọn: LNSTTNDN của Vietcombank tăng rõ rệt và dao động mạnh, phản ánh hiệu quả kinh doanh được cải thiện đáng kể, tuy nhiên mức ổn định chưa cao do chịu ảnh hưởng của chu kỳ tăng trưởng và điều kiện kinh tế từng năm.

2.3.2.3 Biến ROA

ROA (Return on Assets) phản ánh mức lợi nhuận ròng thu được trên mỗi đồng tài sản. Đây là chỉ tiêu đánh giá hiệu quả sử dụng tài sản – cho thấy ngân hàng chuyển hóa tài sản thành lợi nhuận tốt đến mức nào. ROA càng cao, hiệu quả hoạt động càng lớn và cấu trúc tài sản được quản lý càng tốt.

VCBbctc %>%
  summarise(
    So_nam = n(),
    Gia_tri_trung_binh = mean(ROA, na.rm = TRUE),
    Trung_vi = median(ROA, na.rm = TRUE),
    Lon_nhat = max(ROA, na.rm = TRUE),
    Nho_nhat = min(ROA, na.rm = TRUE),
    Do_lech_chuan = sd(ROA, na.rm = TRUE),
    Heso_Bien_thien = sd(ROA, na.rm = TRUE)/mean(ROA, na.rm = TRUE)*100,
    Q1 = quantile(ROA, 0.25, na.rm = TRUE),
    Q3 = quantile(ROA, 0.75, na.rm = TRUE)
  )

Ý nghĩa thống kê

ROA trung bình 4,14%, trung vị 4,13% → hai giá trị gần nhau, cho thấy phân bố ổn định, không có năm bất thường.

Khoảng dao động 2,5% – 6,3% → biến động vừa phải, thể hiện hiệu quả tài sản tương đối nhất quán.

Độ lệch chuẩn 0,013 và CV = 31,6% → mức ổn định tốt, phản ánh Vietcombank duy trì hiệu quả sử dụng tài sản cao và ít rủi ro.

Khoảng Q1–Q3 (3,05% – 4,60%) → phần lớn dữ liệu tập trung quanh trung bình, chứng tỏ sự bền vững về hiệu quả hoạt động.

Kết luận: ROA của Vietcombank duy trì ổn định quanh 4–5%, thể hiện khả năng sinh lời cao trên mỗi đồng tài sản và năng lực quản trị tài sản hiệu quả trong giai đoạn 10 năm.

2.3.2.4 Biến ROE

ROE (Return on Equity) thể hiện mức lợi nhuận ròng trên mỗi đồng vốn chủ sở hữu. Đây là chỉ tiêu cốt lõi phản ánh hiệu quả đầu tư của cổ đông, cho thấy khả năng ngân hàng tạo ra lợi nhuận từ nguồn vốn tự có. ROE cao và ổn định là dấu hiệu của doanh nghiệp tài chính mạnh, quản lý vốn hiệu quả

VCBbctc %>%
  summarise(
    So_nam = n(),
    Gia_tri_trung_binh = mean(ROE, na.rm = TRUE),
    Trung_vi = median(ROE, na.rm = TRUE),
    Lon_nhat = max(ROE, na.rm = TRUE),
    Nho_nhat = min(ROE, na.rm = TRUE),
    Do_lech_chuan = sd(ROE, na.rm = TRUE),
    Heso_Bien_thien = sd(ROE, na.rm = TRUE)/mean(ROE, na.rm = TRUE)*100,
    Q1 = quantile(ROE, 0.25, na.rm = TRUE),
    Q3 = quantile(ROE, 0.75, na.rm = TRUE)
  )

Ý nghĩa thống kê

ROE trung bình 11,41% cho thấy Vietcombank tạo ra 11,41 đồng lợi nhuận ròng trên mỗi 100 đồng vốn chủ sở hữu, phản ánh hiệu quả sử dụng vốn tương đối cao trong ngành ngân hàng.

Trung vị 11,59% gần bằng trung bình → dữ liệu phân bố cân đối, không có năm bất thường hoặc cực trị mạnh.

Giá trị nhỏ nhất 5,65% và lớn nhất 24,71% → biên độ biến động khá rộng, thể hiện có những giai đoạn tăng trưởng lợi nhuận vượt trội, đặc biệt khi môi trường kinh doanh thuận lợi.

Độ lệch chuẩn 0,0556 và hệ số biến thiên 48,8% → ROE biến động ở mức trung bình, nghĩa là hiệu quả vốn có dao động nhưng vẫn duy trì xu hướng ổn định.

Khoảng Q1–Q3 (7,36% – 12,10%) → 50% dữ liệu tập trung quanh trung bình, cho thấy sự bền vững trong hiệu quả sinh lời của vốn chủ sở hữu.

Kết luận: ROE của Vietcombank duy trì ổn định quanh mức 10–12%, phản ánh khả năng tạo lợi nhuận tốt trên vốn chủ sở hữu. Dù có giai đoạn tăng đột biến (ROE cao nhất đạt 24,7%), xu hướng chung vẫn ổn định và bền vững, thể hiện hiệu quả quản trị vốn tốt và sức hấp dẫn đối với cổ đông.

2.3.2.5 Tỷ lệ an toàn vốn (CAR)

CAR (Capital Adequacy Ratio) là chỉ tiêu quan trọng phản ánh mức độ an toàn tài chính của ngân hàng, đo bằng tỷ lệ vốn tự có trên tổng tài sản có rủi ro. CAR cao thể hiện khả năng chống chịu tốt với rủi ro tín dụng và thị trường, trong khi CAR quá thấp cảnh báo rủi ro mất cân đối vốn. Phân tích CAR giúp đánh giá mức độ an toàn và tuân thủ chuẩn Basel II/III của Vietcombank.

VCBbctc %>%
  summarise(
    So_nam = n(),
    Gia_tri_trung_binh = mean(CAR, na.rm = TRUE),
    Trung_vi = median(CAR, na.rm = TRUE),
    Lon_nhat = max(CAR, na.rm = TRUE),
    Nho_nhat = min(CAR, na.rm = TRUE),
    Do_lech_chuan = sd(CAR, na.rm = TRUE),
    Heso_Bien_thien = sd(CAR, na.rm = TRUE)/mean(CAR, na.rm = TRUE)*100,
    Q1 = quantile(CAR, 0.25, na.rm = TRUE),
    Q3 = quantile(CAR, 0.75, na.rm = TRUE)
  )

Ý nghĩa thống kê

CAR trung bình đạt 38,76%, cao hơn rất nhiều so với chuẩn an toàn vốn tối thiểu của Basel II (8%).Điều này cho thấy Vietcombank có mức độ an toàn tài chính rất cao, đảm bảo khả năng hấp thụ rủi ro và duy trì hoạt động ổn định.

Trung vị 38,48% gần bằng trung bình → dữ liệu phân bố cân đối, không có năm đột biến bất thường.

Khoảng dao động 25,6% – 48,8% cho thấy CAR biến động trong vùng an toàn rộng, phản ánh sự điều chỉnh vốn linh hoạt của ngân hàng trước thay đổi của quy mô tài sản rủi ro.

Độ lệch chuẩn 0,0758 và CV = 19,55% → mức biến động thấp, thể hiện tính ổn định cao của chính sách vốn.

Khoảng Q1–Q3 (34,6% – 43,6%) → 50% dữ liệu tập trung quanh vùng trung bình, chứng minh Vietcombank duy trì ổn định cấu trúc vốn trong dài hạn.

Kết luận:

CAR của Vietcombank ổn định quanh 38–40%, thể hiện năng lực quản lý vốn và an toàn tài chính vượt trội so với tiêu chuẩn ngành.

Ngân hàng duy trì nguồn vốn tự có mạnh, đủ khả năng chống chịu rủi ro tín dụng và biến động kinh tế, qua đó củng cố niềm tin thị trường và uy tín hệ thống.

2.3.2.6 Hệ số nợ trên vốn chủ sở hữu(D/E)

D/E (Debt-to-Equity ratio) đo lường mức độ đòn bẩy tài chính của ngân hàng, thể hiện tỷ lệ giữa nợ phải trả và vốn chủ sở hữu.

Chỉ số này phản ánh cấu trúc tài chính – mức độ ngân hàng phụ thuộc vào nguồn vốn vay so với vốn tự có.

D/E càng cao, ngân hàng càng sử dụng nhiều nợ, làm tăng rủi ro tài chính; ngược lại, D/E thấp thể hiện tính tự chủ và an toàn vốn cao.

VCBbctc %>%
  summarise(
    So_nam = n(),
    Gia_tri_trung_binh = mean(DE, na.rm = TRUE),
    Trung_vi = median(DE, na.rm = TRUE),
    Lon_nhat = max(DE, na.rm = TRUE),
    Nho_nhat = min(DE, na.rm = TRUE),
    Do_lech_chuan = sd(DE, na.rm = TRUE),
    Heso_Bien_thien = sd(DE, na.rm = TRUE)/mean(DE, na.rm = TRUE)*100,
    Q1 = quantile(DE, 0.25, na.rm = TRUE),
    Q3 = quantile(DE, 0.75, na.rm = TRUE)
  )

Ý nghĩa thống kê

D/E trung bình 1.68 lần nghĩa là Vietcombank sử dụng 1,68 đồng nợ cho mỗi đồng vốn chủ sở hữu → mức đòn bẩy tài chính vừa phải, hợp lý trong hoạt động ngân hàng.

Trung vị 1.60 gần với trung bình → phân bố cân đối, không có năm biến động cực đoan.

Khoảng dao động 1.05 – 2.91 cho thấy ngân hàng điều chỉnh linh hoạt cơ cấu nợ–vốn tùy theo giai đoạn tăng trưởng, nhưng vẫn trong vùng an toàn.

Độ lệch chuẩn 0.578 và CV = 34.4% → biến động ở mức trung bình, phản ánh Vietcombank duy trì ổn định cấu trúc tài chính, không phụ thuộc quá nhiều vào nợ vay.

Khoảng Q1–Q3 (1.29 – 1.89) → 50% dữ liệu nằm gần vùng trung bình, thể hiện mức độ ổn định cao trong chính sách huy động và quản lý nợ.

Kết luận:

Hệ số D/E của Vietcombank ổn định quanh 1,5–1,8 lần, phản ánh mức độ sử dụng nợ hợp lý và kiểm soát đòn bẩy tài chính hiệu quả.

Điều này cho thấy ngân hàng cân bằng tốt giữa nợ vay và vốn chủ sở hữu, đảm bảo khả năng sinh lời mà vẫn duy trì an toàn tài chính trong dài hạn.

2.3.2.7 Tỷ lệ tiền mặt

Tỷ lệ tiền mặt (Cash Ratio) phản ánh khả năng thanh toán tức thời của ngân hàng, đo bằng tỷ lệ tiền và các khoản tương đương tiền so với tổng tài sản.

Chỉ tiêu này thể hiện mức độ sẵn sàng chi trả các nghĩa vụ ngắn hạn và khả năng thanh khoản của ngân hàng.

Tỷ lệ quá thấp báo hiệu rủi ro thanh khoản, còn tỷ lệ quá cao có thể cho thấy nguồn vốn chưa được sử dụng hiệu quả.

VCBbctc %>%
  summarise(
    So_nam = n(),
    Gia_tri_trung_binh = mean(Ty_le_tien_mat, na.rm = TRUE),
    Trung_vi = median(Ty_le_tien_mat, na.rm = TRUE),
    Lon_nhat = max(Ty_le_tien_mat, na.rm = TRUE),
    Nho_nhat = min(Ty_le_tien_mat, na.rm = TRUE),
    Do_lech_chuan = sd(Ty_le_tien_mat, na.rm = TRUE),
    Heso_Bien_thien = sd(Ty_le_tien_mat, na.rm = TRUE)/mean(Ty_le_tien_mat, na.rm = TRUE)*100,
    Q1 = quantile(Ty_le_tien_mat, 0.25, na.rm = TRUE),
    Q3 = quantile(Ty_le_tien_mat, 0.75, na.rm = TRUE)
  )

Ý nghĩa thống kê

Tỷ lệ tiền mặt trung bình đạt 91,18%, nghĩa là trung bình gần 91 đồng tài sản có thể quy đổi ngay thành tiền mặt trong tổng tài sản của Vietcombank – cho thấy khả năng thanh khoản cực cao.

Trung vị 98,6% gần với trung bình → dữ liệu ổn định, không có năm cực trị bất thường.

Khoảng dao động 28,5% – 99,4% cho thấy một số năm ngân hàng chủ động tăng lượng tiền mặt nắm giữ (đặc biệt khi rủi ro thanh khoản thị trường tăng).

Độ lệch chuẩn 0.221 và CV = 24,24% → mức biến động thấp đến trung bình, phản ánh sự ổn định trong chính sách dự trữ tiền mặt.

Khoảng Q1–Q3 (≈ 98,4% – 98,7%) rất hẹp → 50% giá trị dữ liệu tập trung quanh mức cao, chứng tỏ tỷ lệ tiền mặt của ngân hàng duy trì ổn định ở vùng an toàn.

Kết luận:

Tỷ lệ tiền mặt của Vietcombank duy trì ở mức cao và ổn định (~0,9–1 lần), phản ánh khả năng thanh khoản vững chắc, quản trị dòng tiền hiệu quả, và năng lực đáp ứng nghĩa vụ thanh toán tức thời rất tốt. Điều này góp phần củng cố uy tín và sức khỏe tài chính dài hạn của ngân hàng.

###Phân tích đa biến

2.3.2.8 Phân tích mối quan hệ giữa đòn bẩy tài chính (D/E) và hiệu quả vốn chủ sở hữu (ROE)

Trong hoạt động ngân hàng, đòn bẩy tài chính (D/E) phản ánh mức độ sử dụng vốn vay để tài trợ tài sản, trong khi ROE (Return on Equity) đo lường khả năng sinh lời trên vốn chủ sở hữu.

Việc sử dụng nợ hợp lý giúp ngân hàng khuếch đại lợi nhuận trên vốn chủ, nhưng nếu quá mức, chi phí lãi vay và rủi ro tín dụng sẽ làm giảm hiệu quả.

Do đó, phân tích mối quan hệ giữa D/E và ROE nhằm đánh giá mức độ tối ưu trong việc sử dụng đòn bẩy tài chính của VCB, xem liệu ngân hàng đang tận dụng nợ hiệu quả hay phụ thuộc quá nhiều vào vốn vay.

# Tạo nhóm đòn bẩy tài chính
VCBbctc <- VCBbctc %>%
  mutate(mucDE = case_when(
    DE < 5 ~ "Thấp",
    DE >= 5 & DE < 8 ~ "Trung bình",
    DE >= 8 ~ "Cao"
  ))

# Tính thống kê mô tả ROE theo các mức đòn bẩy
thongke_DE_ROE <- VCBbctc %>%
  group_by(mucDE) %>%
  summarise(
    Soluong = n(),
    ROE_tb = mean(ROE, na.rm = TRUE),
    ROE_max = max(ROE, na.rm = TRUE),
    ROE_min = min(ROE, na.rm = TRUE),
    Trungvi = median(ROE, na.rm = TRUE),
    Dolechchuan = sd(ROE, na.rm = TRUE),
    Q1 = quantile(ROE, 0.25, na.rm = TRUE),
    Q3 = quantile(ROE, 0.75, na.rm = TRUE)
  ) %>%
  arrange(mucDE)
thongke_DE_ROE 

Giải thích kỹ thuật

mutate() tạo biến phân nhóm “mức đòn bẩy” để dễ so sánh.

group_by() nhóm dữ liệu theo mức DE.

summarise() tính trung bình, độ lệch chuẩn, trung vị, Q1–Q3 giúp hiểu phân bố ROE trong từng nhóm.

2.3.2.9 Phân tích mối quan hệ giữa tỷ lệ an toàn vốn (CAR) và hiệu quả sử dụng tài sản (ROA)

CAR (Capital Adequacy Ratio) thể hiện khả năng của ngân hàng duy trì vốn tự có để chống rủi ro tín dụng, trong khi ROA (Return on Assets) phản ánh mức sinh lời trên mỗi đồng tài sản.

Ngân hàng có CAR cao thường an toàn hơn, nhưng đồng thời có thể giảm đòn bẩy sinh lời, làm ROA thấp hơn.

Phân tích mối quan hệ giữa CAR và ROA giúp đánh giá khả năng cân bằng giữa an toàn và hiệu quả của VCB, xem ngân hàng có thể duy trì hiệu quả sinh lời mà vẫn đảm bảo an toàn vốn hay không.

# Phân nhóm CAR
VCBbctc <- VCBbctc %>%
  mutate(mucCAR = case_when(
    CAR < 8 ~ "Thấp (dưới chuẩn Basel II)",
    CAR >= 8 & CAR < 10 ~ "Trung bình",
    CAR >= 10 ~ "Cao"
  ))

# Thống kê ROA theo mức CAR
thongke_CAR_ROA <- VCBbctc %>%
  group_by(mucCAR) %>%
  summarise(
    Soluong = n(),
    ROA_tb = mean(ROA, na.rm = TRUE),
    ROA_max = max(ROA, na.rm = TRUE),
    ROA_min = min(ROA, na.rm = TRUE),
    Trungvi = median(ROA, na.rm = TRUE),
    Dolechchuan = sd(ROA, na.rm = TRUE),
    Q1 = quantile(ROA, 0.25, na.rm = TRUE),
    Q3 = quantile(ROA, 0.75, na.rm = TRUE)
  ) %>%
  arrange(mucCAR)

thongke_CAR_ROA

Giải thích kỹ thuật

mucCAR chia ngân hàng thành 3 nhóm an toàn vốn.

Các thống kê mô tả cho thấy hiệu quả (ROA) khác nhau ở từng mức an toàn.

Ý nghĩa thống kê

2.3.2.10 Phân tích mối quan hệ giữa chất lượng lợi nhuận (CFKD/LNST) và ROE

Chất lượng lợi nhuận (CFKD/LNST) phản ánh mức độ mà lợi nhuận kế toán đi kèm dòng tiền thực tế từ hoạt động kinh doanh. ROE đo hiệu quả sử dụng vốn chủ sở hữu. Nhóm tác giả chọn hai biến này nhằm kiểm tra tính “thật” của lợi nhuận – liệu hiệu quả cao có đi kèm dòng tiền mạnh, hay chỉ là lợi nhuận kế toán. Phân tích này giúp đánh giá tính bền vững của hiệu quả tài chính tại VCB.

# Phân nhóm chất lượng lợi nhuận
VCBbctc <- VCBbctc %>%
  mutate(mucChatluong = case_when(
    Chatluong_LN < 0.8 ~ "Thấp (LN không gắn dòng tiền)",
    Chatluong_LN >= 0.8 & Chatluong_LN <= 1.2 ~ "Trung bình",
    Chatluong_LN > 1.2 ~ "Cao (LN bền vững)"
  ))

# Thống kê ROE theo mức chất lượng lợi nhuận
thongke_CF_ROE <- VCBbctc %>%
  group_by(mucChatluong) %>%
  summarise(
    Soluong = n(),
    ROE_tb = mean(ROE, na.rm = TRUE),
    ROE_max = max(ROE, na.rm = TRUE),
    ROE_min = min(ROE, na.rm = TRUE),
    Trungvi = median(ROE, na.rm = TRUE),
    Dolechchuan = sd(ROE, na.rm = TRUE),
    Q1 = quantile(ROE, 0.25, na.rm = TRUE),
    Q3 = quantile(ROE, 0.75, na.rm = TRUE)
  ) %>%
  
  arrange(mucChatluong)
thongke_CF_ROE

Giải thích kỹ thuật

Nhóm “Cao” nghĩa là lợi nhuận có dòng tiền hỗ trợ mạnh, “Thấp” là lợi nhuận kém thực.

Ý nghĩa thống kê

ROE_tb cao ở nhóm “Cao” → lợi nhuận thật, tài chính bền vững.

ROE_tb cao ở nhóm “Thấp” → cảnh báo khả năng “lợi nhuận kế toán”, không bền vững.

Độ lệch chuẩn nhỏ → hoạt động ổn định theo thời gian.

2.3.2.11 Phân tích mối quan hệ giữa tăng trưởng tổng tài sản (Tangtruong_TS) và hiệu quả sử dụng tài sản (ROA)

Tổng tài sản thể hiện quy mô hoạt động, còn ROA phản ánh hiệu quả tạo lợi nhuận từ tài sản. Một ngân hàng tăng trưởng mạnh chưa chắc hiệu quả nếu tài sản tăng nhanh hơn lợi nhuận. Phân tích mối quan hệ này giúp đánh giá chất lượng tăng trưởng của VCB – tức là mở rộng quy mô có đi kèm hiệu quả hay không.

# Phân nhóm tăng trưởng tài sản
VCBbctc <- VCBbctc %>%
  mutate(mucTangtruongTS = case_when(
    Tangtruong_TS < 5 ~ "Tăng chậm",
    Tangtruong_TS >= 5 & Tangtruong_TS < 15 ~ "Tăng vừa",
    Tangtruong_TS >= 15 ~ "Tăng mạnh"
  ))

# Thống kê ROA theo mức tăng trưởng tài sản
thongke_TS_ROA <- VCBbctc %>%
  group_by(mucTangtruongTS) %>%
  summarise(
    Soluong = n(),
    ROA_tb = mean(ROA, na.rm = TRUE),
    ROA_max = max(ROA, na.rm = TRUE),
    ROA_min = min(ROA, na.rm = TRUE),
    Trungvi = median(ROA, na.rm = TRUE),
    Dolechchuan = sd(ROA, na.rm = TRUE),
    Q1 = quantile(ROA, 0.25, na.rm = TRUE),
    Q3 = quantile(ROA, 0.75, na.rm = TRUE)
  ) %>%
  arrange(mucTangtruongTS)
thongke_TS_ROA

Giải thích kỹ thuật

Tạo 3 nhóm tăng trưởng để mô tả ROA tương ứng.

Thống kê mô tả giúp xác định hiệu quả trung bình và mức dao động trong từng nhóm.

Ý nghĩa thống kê

2.4 TRỰC QUAN HÓA DỮ LIỆU

2.4.1 Xu hướng tăng trưởng lợi nhuận sau thuế theo năm

Lợi nhuận sau thuế (LNST) thể hiện hiệu quả kinh doanh ròng của ngân hàng sau khi đã trừ toàn bộ chi phí và nghĩa vụ thuế. Phân tích xu hướng lợi nhuận qua các năm giúp nhận định hiệu quả hoạt động, khả năng sinh lời và tính ổn định của kết quả kinh doanh. Với VCB, biểu đồ đường cho giai đoạn 2015–2024 phản ánh rõ mức độ tăng trưởng lợi nhuận bền vững và khả năng phục hồi sau COVID-19.

library(ggplot2)
library(dplyr)

# Chuẩn hóa đơn vị về nghìn tỷ để dễ nhìn
VCBbctc <- VCBbctc %>% mutate(LNST_nghin_ti = LNSTTNDN / 1000)

ggplot(VCBbctc, aes(x = Nam, y = LNST_nghin_ti)) +
  geom_line(color = "darkgreen", size = 1.5) +
  geom_point(color = "forestgreen", size = 3) +
  geom_text(aes(label = round(LNST_nghin_ti, 1)),
            vjust = -0.6, size = 3) +
  labs(
    title = "Xu hướng tăng trưởng lợi nhuận sau thuế của VCB (2015–2024)",
    subtitle = "Đơn vị: Nghìn tỷ đồng",
    x = "Năm",
    y = "Lợi nhuận sau thuế (nghìn tỷ đồng)"
  ) +
  theme_minimal() +
  theme(
    plot.title = element_text(face = "bold", color = "darkgreen", size = 14),
    plot.subtitle = element_text(color = "gray40", size = 11),
    axis.text.x = element_text(angle = 0)
  )

Giải thích kỹ thuật

mutate(LNST_nghin_ti = LNSTTNDN / 1000) Chuyển đơn vị lợi nhuận sang nghìn tỷ đồng để trực quan hơn.
geom_line() Vẽ đường biểu diễn xu hướng lợi nhuận theo năm.
geom_point()    Đánh dấu từng năm bằng chấm tròn.
geom_text() Hiển thị giá trị cụ thể của từng năm ngay trên đồ thị.
labs()  Thêm tiêu đề, trục, chú thích rõ ràng.
theme_minimal() Tạo giao diện gọn, hiện đại.

📈 Ý nghĩa thống kê

2.4.2 Tăng trưởng tổng tài sản (TTS) qua thời gian

Tổng tài sản (TTS) thể hiện quy mô hoạt động tổng thể của ngân hàng — bao gồm toàn bộ nguồn lực tài chính và tài sản mà ngân hàng kiểm soát. Phân tích xu hướng biến động tổng tài sản qua thời gian cho phép đánh giá tốc độ tăng trưởng và khả năng mở rộng quy mô của VCB trong giai đoạn 2015–2024.

library(ggplot2)
library(dplyr)

# Gợi ý: chuẩn hóa đơn vị về "nghìn tỷ đồng" cho dễ đọc
VCBbctc <- VCBbctc %>% mutate(TTS_nghin_ti = TTS / 1000)

ggplot(VCBbctc, aes(x = factor(Nam), y = TTS_nghin_ti)) +
  geom_col(fill = "steelblue") +
  geom_text(aes(label = round(TTS_nghin_ti, 1)),
            vjust = -0.3, size = 3.2) +
  labs(
    title = "Tổng tài sản của VCB giai đoạn 2015–2024",
    subtitle = "Đơn vị: Nghìn tỷ đồng",
    x = "Năm",
    y = "Tổng tài sản (nghìn tỷ đồng)"
  ) +
  expand_limits(y = max(VCBbctc$TTS_nghin_ti, na.rm = TRUE) * 1.1) +
  theme_minimal() +
  theme(
    plot.title = element_text(face = "bold", color = "navy"),
    axis.text.x = element_text(angle = 0, vjust = 1)
  )

Giải thích kỹ thuật

mutate(TTS_nghin_ti = TTS / 1000): đổi đơn vị TTS sang nghìn tỷ để trục Y gọn gàng.

aes(x = factor(Nam), y = TTS_nghin_ti): dùng năm làm trục X (đưa về factor để mỗi năm là một cột), TTS_nghin_ti là chiều cao cột.

geom_col(fill = "steelblue"): vẽ biểu đồ cột với chiều cao bằng giá trị.

geom_text(label = …): in nhãn số trên đầu mỗi cột (làm tròn 1 chữ số thập phân).

expand_limits(y = … * 1.1): nới trục Y để nhãn số không bị tràn ra ngoài.

theme_minimal(): giao diện gọn, dễ đọc.

Ý nghĩa thống kê

2.4.3 So sánh Nợ phải trả và Vốn chủ sở hữu (2015–2024)

Trong cấu trúc tài chính của ngân hàng, nợ phải trả (NPT) và vốn chủ sở hữu (VCSH) là hai nguồn hình thành tổng tài sản. Sự thay đổi của hai yếu tố này thể hiện mức độ an toàn và đòn bẩy tài chính.

Việc trực quan hóa bằng biểu đồ cột giúp đánh giá sự cân đối giữa vốn vay và vốn tự có của VCB trong giai đoạn 2015–2024.

library(ggplot2)
library(dplyr)
library(tidyr)

# Chuẩn hóa dữ liệu về dạng "long" để vẽ nhóm cột
VCBbctc_NPT_VCSH <- VCBbctc %>%
  select(Nam, NPT, VCSH) %>%
  mutate(across(c(NPT, VCSH), ~ .x / 1000)) %>%  # đổi đơn vị sang nghìn tỷ
  pivot_longer(cols = c(NPT, VCSH), names_to = "Chitieu", values_to = "Giatringhin")

# Biểu đồ cột nhóm
ggplot(VCBbctc_NPT_VCSH, aes(x = factor(Nam), y = Giatringhin, fill = Chitieu)) +
  geom_col(position = "dodge") +
  geom_text(aes(label = round(Giatringhin, 1)),
            position = position_dodge(width = 0.9), vjust = -0.3, size = 3) +
  labs(
    title = "So sánh Nợ phải trả và Vốn chủ sở hữu của VCB (2015–2024)",
    subtitle = "Đơn vị: Nghìn tỷ đồng",
    x = "Năm",
    y = "Giá trị (nghìn tỷ đồng)",
    fill = "Chỉ tiêu tài chính"
  ) +
  scale_fill_manual(values = c("NPT" = "firebrick", "VCSH" = "steelblue")) +
  theme_minimal() +
  theme(
    plot.title = element_text(face = "bold", color = "navy", size = 14),
    plot.subtitle = element_text(size = 11, color = "gray40"),
    legend.position = "bottom"
  )

Giải thích kỹ thuật

select(Nam, NPT, VCSH)  Chọn 2 chỉ tiêu cần so sánh.
mutate(across(..., ~ .x / 1000))    Chuẩn hóa đơn vị về nghìn tỷ đồng để dễ đọc.
pivot_longer()  Chuyển dữ liệu sang dạng “dài” để ggplot có thể vẽ nhiều nhóm.
geom_col(position = "dodge")    Vẽ cột đặt cạnh nhau (so sánh trực tiếp từng năm).
geom_text() Hiển thị giá trị số trên đầu mỗi cột.
scale_fill_manual() Tô hai màu khác nhau: đỏ cho NPT, xanh cho VCSH.

2.4.4 So sánh ROA và ROE theo năm

Hai chỉ tiêu ROA và ROE là thước đo cốt lõi đánh giá hiệu quả sinh lời của ngân hàng:

ROA cho biết 1 đồng tài sản tạo ra bao nhiêu đồng lợi nhuận;

ROE cho biết 1 đồng vốn chủ sở hữu mang lại bao nhiêu đồng lợi nhuận. Phân tích xu hướng hai chỉ tiêu này qua thời gian giúp nhận diện khả năng sinh lời và mức độ đòn bẩy tài chính của VCB trong giai đoạn 2015–2024.

library(ggplot2)
library(dplyr)
library(tidyr)

# Chuẩn hóa dữ liệu về dạng "long" để vẽ 2 đường cùng lúc
VCBbctc_ROA_ROE <- VCBbctc %>%
  select(Nam, ROA, ROE) %>%
  pivot_longer(cols = c(ROA, ROE),
               names_to = "Chitieu",
               values_to = "Giatriphantram")

# Biểu đồ đường
ggplot(VCBbctc_ROA_ROE, aes(x = Nam, y = Giatriphantram, color = Chitieu)) +
  geom_line(size = 1.4) +
  geom_point(size = 3) +
  geom_text(aes(label = round(Giatriphantram, 2)), 
            vjust = -0.6, size = 3) +
  labs(
    title = "So sánh ROA và ROE của VCB giai đoạn 2015–2024",
    subtitle = "Đơn vị: %",
    x = "Năm",
    y = "Tỷ suất sinh lời (%)",
    color = "Chỉ tiêu"
  ) +
  scale_color_manual(values = c("ROA" = "darkgreen", "ROE" = "steelblue")) +
  theme_minimal() +
  theme(
    plot.title = element_text(face = "bold", color = "navy", size = 14),
    plot.subtitle = element_text(size = 11, color = "gray40"),
    legend.position = "bottom"
  )

Giải thích kỹ thuật

pivot_longer()  Chuyển dữ liệu từ dạng rộng sang “dài” để ggplot có thể vẽ nhiều đường cùng lúc.
geom_line(size=1.4) Vẽ hai đường thể hiện biến động ROA và ROE theo năm.
geom_point()    Đánh dấu từng điểm dữ liệu trên đường.
geom_text() Ghi nhãn giá trị % lên từng điểm.
scale_color_manual()    Gán màu riêng cho từng chỉ tiêu (ROA – xanh lá, ROE – xanh dương).
theme_minimal() Giúp đồ thị sáng, chuyên nghiệp và dễ đọc.

📈 Ý nghĩa thống kê

ROE cao hơn ROA nhiều → hiệu ứng đòn bẩy tài chính mạnh.

2.4.5 Biểu đồ cột so sánh doanh thu (DT) và chi phí hoạt động (CPHDKD)

Doanh thu và chi phí hoạt động là hai yếu tố cơ bản nhất quyết định lợi nhuận hoạt động của ngân hàng. So sánh hai chỉ tiêu này qua thời gian giúp đánh giá hiệu quả quản trị chi phí và mức độ mở rộng hoạt động. Biểu đồ cột nhóm thể hiện trực quan mối tương quan giữa doanh thu và chi phí hoạt động của VCB giai đoạn 2015–2024..

library(ggplot2)
library(dplyr)
library(tidyr)

# Chuyển dữ liệu về dạng long để vẽ 2 cột song song
VCBbctc_DT_CP <- VCBbctc %>%
  select(Nam, DT, CPHDKD) %>%
  mutate(across(c(DT, CPHDKD), ~ .x / 1000)) %>%  # đổi đơn vị nghìn tỷ
  pivot_longer(cols = c(DT, CPHDKD),
               names_to = "Chitieu",
               values_to = "Giatringhin")

# Biểu đồ cột nhóm
ggplot(VCBbctc_DT_CP, aes(x = factor(Nam), y = Giatringhin, fill = Chitieu)) +
  geom_col(position = "dodge") +
  geom_text(aes(label = round(Giatringhin, 1)),
            position = position_dodge(width = 0.9), vjust = -0.4, size = 3) +
  labs(
    title = "So sánh Doanh thu và Chi phí hoạt động của VCB (2015–2024)",
    subtitle = "Đơn vị: Nghìn tỷ đồng",
    x = "Năm",
    y = "Giá trị (nghìn tỷ đồng)",
    fill = "Chỉ tiêu"
  ) +
  scale_fill_manual(values = c("DT" = "steelblue", "CPHDKD" = "tomato")) +
  theme_minimal() +
  theme(
    plot.title = element_text(face = "bold", color = "navy", size = 14),
    plot.subtitle = element_text(size = 11, color = "gray40"),
    legend.position = "bottom"
  )

Giải thích kỹ thuật

select(Nam, DT, CPHDKD) Lấy 2 chỉ tiêu cần so sánh: doanh thu & chi phí.
mutate(across(..., ~ .x / 1000))    Chuẩn hóa đơn vị thành nghìn tỷ đồng.
pivot_longer()  Đưa dữ liệu sang dạng “dài” để vẽ nhóm cột.
geom_col(position = "dodge")    Hiển thị hai cột cạnh nhau mỗi năm.
geom_text() Hiển thị giá trị số trên đầu mỗi cột.
scale_fill_manual() Gán màu riêng: xanh cho doanh thu, đỏ cho chi phí.

📈 Ý nghĩa thống kê

2.4.6 Mối quan hệ giữa D/E và ROE

Để đánh giá hiệu quả sử dụng đòn bẩy tài chính, ta xem xét mối quan hệ giữa hệ số nợ trên vốn chủ sở hữu (D/E) và tỷ suất sinh lời trên vốn chủ sở hữu (ROE). Nếu VCB sử dụng đòn bẩy hợp lý, thì khi D/E tăng, ROE cũng sẽ tăng – phản ánh việc vay nợ giúp khuếch đại lợi nhuận cho cổ đông. Ngược lại, nếu D/E cao nhưng ROE không tăng, chứng tỏ đòn bẩy không còn mang lại hiệu quả.

library(ggplot2)

ggplot(VCBbctc, aes(x = DE, y = ROE)) +
  geom_point(size = 3, color = "steelblue") +
  geom_smooth(method = "lm", se = TRUE, color = "darkred", linetype = "dashed") +
  geom_text(aes(label = Nam), vjust = -0.6, size = 3) +
  labs(
    title = "Mối quan hệ giữa Đòn bẩy tài chính (D/E) và Hiệu quả sinh lời (ROE)",
    subtitle = "Giai đoạn 2015–2024 | Mỗi điểm biểu diễn một năm",
    x = "Hệ số D/E (Nợ / Vốn chủ sở hữu)",
    y = "Tỷ suất sinh lời trên vốn chủ (ROE, %)"
  ) +
  theme_minimal() +
  theme(
    plot.title = element_text(face = "bold", color = "navy", size = 14),
    plot.subtitle = element_text(size = 11, color = "gray40")
  )
## `geom_smooth()` using formula = 'y ~ x'

Giải thích kỹ thuật

aes(x = DE, y = ROE)    Trục hoành là hệ số D/E, trục tung là tỷ suất ROE.
geom_point()    Vẽ từng điểm dữ liệu (mỗi điểm = 1 năm).
geom_smooth(method = "lm")  Thêm đường hồi quy tuyến tính để xem mối quan hệ xu hướng.
geom_text(label = Nam)  Ghi năm tương ứng cho từng điểm để dễ theo dõi.
theme_minimal() Giao diện gọn, hiện đại.

📈 Ý nghĩa thống kê

2.4.7 Mối quan hệ giữa Tỷ lệ an toàn vốn (CAR) và Hiệu quả sử dụng tài sản (ROA) của VCB (2015–2024)

Trong hoạt động ngân hàng, việc cân bằng giữa hiệu quả sinh lời và mức độ an toàn vốn là một trong những bài toán cốt lõi của quản trị tài chính. Hai chỉ tiêu đại diện cho hai mục tiêu này là:

ROA (Return on Assets) – phản ánh hiệu quả sử dụng tài sản, cho biết mỗi đồng tài sản tạo ra bao nhiêu đồng lợi nhuận ròng.

CAR (Capital Adequacy Ratio) – phản ánh khả năng duy trì mức vốn tự có cần thiết để đảm bảo an toàn cho toàn bộ hoạt động.

Phân tích mối quan hệ giữa ROA và CAR giúp đánh giá liệu VCB có đang duy trì được sự cân bằng giữa hiệu quả và an toàn vốn hay không trong giai đoạn 2015–2024. Nếu ngân hàng tăng được CAR mà vẫn giữ được ROA ở mức cao, điều đó cho thấy mô hình tài chính hiệu quả và bền vững.

library(ggplot2)

ggplot(VCBbctc, aes(x = CAR, y = ROA)) +
  geom_point(size = 3, color = "steelblue") +
  geom_smooth(method = "lm", se = TRUE, color = "darkred", linetype = "dashed") +
  geom_text(aes(label = Nam), vjust = -0.6, size = 3) +
  labs(
    title = "Mối quan hệ giữa Tỷ lệ an toàn vốn (CAR) và Hiệu quả sử dụng tài sản (ROA)",
    subtitle = "VCB giai đoạn 2015–2024 | Mỗi điểm biểu diễn một năm",
    x = "CAR (%) – Tỷ lệ an toàn vốn",
    y = "ROA (%) – Hiệu quả sử dụng tài sản"
  ) +
  theme_minimal() +
  theme(
    plot.title = element_text(face = "bold", color = "navy", size = 14),
    plot.subtitle = element_text(size = 11, color = "gray40")
  )
## `geom_smooth()` using formula = 'y ~ x'

Giải thích kỹ thuật

aes(x = CAR, y = ROA)   Thiết lập trục X là CAR, trục Y là ROA.
geom_point()    Biểu diễn mỗi năm bằng một điểm trên đồ thị.
geom_smooth(method = "lm")  Thêm đường hồi quy tuyến tính, giúp xác định xu hướng quan hệ giữa hai biến.
geom_text(aes(label = Nam)) Ghi chú năm tương ứng lên từng điểm, giúp người đọc nhận diện theo thời gian.
theme_minimal() Tối giản bố cục, giúp nổi bật dữ liệu.

2.4.8 So sánh Tổng tài sản, Doanh thu và Lợi nhuận sau thuế của VCB (2015–2024)

Để đánh giá sự phát triển toàn diện của ngân hàng, cần xem xét mối quan hệ giữa quy mô tài sản, doanh thu và lợi nhuận qua thời gian. Ba chỉ tiêu này phản ánh quá trình hình thành giá trị tài chính:

Tài sản tạo ra cơ sở cho hoạt động kinh doanh,

Doanh thu phản ánh hiệu quả khai thác tài sản,

Và lợi nhuận sau thuế thể hiện kết quả cuối cùng của chuỗi giá trị đó.

Phân tích đồng thời TTS, DT và LNSTTNDN giúp nhận diện sự hài hòa giữa tăng trưởng quy mô và hiệu quả sinh lời của VCB trong giai đoạn 2015–2024.

library(ggplot2)
library(dplyr)
library(tidyr)

# Chuyển dữ liệu về dạng long để vẽ 3 đường trên cùng biểu đồ
VCBbctc_long <- VCBbctc %>%
  select(Nam, TTS, DT, LNSTTNDN) %>%
  mutate(across(c(TTS, DT, LNSTTNDN), ~ .x / 1000)) %>%  # đổi đơn vị sang nghìn tỷ đồng
  pivot_longer(cols = c(TTS, DT, LNSTTNDN),
               names_to = "Chitieu",
               values_to = "Giatri")

# Vẽ biểu đồ đường
ggplot(VCBbctc_long, aes(x = Nam, y = Giatri, color = Chitieu)) +
  geom_line(size = 1.4) +
  geom_point(size = 3) +
  geom_text(aes(label = round(Giatri, 1)), vjust = -0.6, size = 3) +
  labs(
    title = "So sánh Tổng tài sản, Doanh thu và Lợi nhuận sau thuế của VCB (2015–2024)",
    subtitle = "Đơn vị: Nghìn tỷ đồng",
    x = "Năm", y = "Giá trị (nghìn tỷ đồng)",
    color = "Chỉ tiêu tài chính"
  ) +
  scale_color_manual(values = c("TTS" = "steelblue", "DT" = "darkorange", "LNSTTNDN" = "darkgreen")) +
  theme_minimal() +
  theme(
    plot.title = element_text(face = "bold", color = "navy", size = 14),
    plot.subtitle = element_text(size = 11, color = "gray40"),
    legend.position = "bottom"
  )

Giải thích kỹ thuật

mutate(across(..., ~ .x / 1000))    Đổi đơn vị sang nghìn tỷ đồng cho dễ so sánh.
pivot_longer()  Biến dữ liệu thành dạng “dài” để ggplot có thể vẽ nhiều đường.
geom_line() Vẽ 3 đường biểu diễn xu hướng theo năm cho 3 chỉ tiêu.
geom_point()    Đánh dấu giá trị từng năm.
geom_text() Ghi giá trị lên từng điểm dữ liệu.
scale_color_manual()    Gán màu riêng: xanh (TTS), cam (DT), xanh lá (LNST).

2.4.9 Quan hệ giữa Tổng tài sản và Lợi nhuận sau thuế của VCB (2015–2024)

Một trong những mối quan hệ quan trọng nhất trong phân tích tài chính ngân hàng là giữa quy mô tài sản và mức lợi nhuận. Nếu lợi nhuận tăng tương xứng với quy mô tài sản, ngân hàng đang phát triển bền vững và hiệu quả; ngược lại, nếu tài sản tăng mạnh nhưng lợi nhuận không tăng tương ứng, có thể hiệu quả sử dụng vốn chưa tối ưu.

Biểu đồ mật độ hai chiều (2D density plot) giúp ta quan sát phân bố chung của hai biến, xác định vùng tập trung giá trị (điểm thường xuất hiện) và mối tương quan tổng thể giữa Tổng tài sản (TTS) và Lợi nhuận sau thuế (LNSTTNDN).

library(ggplot2)

ggplot(VCBbctc, aes(x = TTS / 1000, y = LNSTTNDN / 1000)) +
  geom_point(alpha = 0.4, color = "steelblue") +
  geom_density_2d(color = "darkred", size = 1) +
  geom_density_2d_filled(alpha = 0.5) +
  labs(
    title = "Phân bố đồng thời giữa Tổng tài sản và Lợi nhuận sau thuế của VCB (2015–2024)",
    subtitle = "Biểu đồ mật độ hai chiều (2D density plot)",
    x = "Tổng tài sản (nghìn tỷ đồng)",
    y = "Lợi nhuận sau thuế (nghìn tỷ đồng)"
  ) +
  theme_minimal() +
  theme(
    plot.title = element_text(face = "bold", color = "navy", size = 14),
    plot.subtitle = element_text(color = "gray40", size = 11)
  )

Giải thích kỹ thuật

geom_point()    Vẽ các điểm thực tế (mỗi điểm = 1 năm hoặc 1 quan sát).
geom_density_2d()   Thêm đường contour (đường đồng mật độ), giống như “bản đồ địa hình” của dữ liệu.
geom_density_2d_filled()    Tô màu vùng mật độ cao để dễ nhìn hơn.
alpha   Độ trong suốt để nhìn rõ các lớp chồng lên nhau.
/ 1000  Chuyển đơn vị sang nghìn tỷ đồng để dễ đọc trục.

2.4.10 Mối quan hệ giữa TTS và LNSTTNDN

Tổng tài sản (TTS) phản ánh quy mô hoạt động của ngân hàng, trong khi Lợi nhuận sau thuế (LNSTTNDN) thể hiện hiệu quả kinh doanh ròng. Phân tích mối quan hệ giữa hai biến này giúp đánh giá liệu VCB có chuyển hóa quy mô tài sản thành lợi nhuận một cách hiệu quả hay không. Biểu đồ tán xạ (scatter plot) được sử dụng để thể hiện mối tương quan giữa TTS và LNST trong giai đoạn 2015–2024.

library(ggplot2)

ggplot(VCBbctc, aes(x = TTS / 1000, y = LNSTTNDN / 1000)) +
  geom_point(size = 3, color = "steelblue") +
  geom_smooth(method = "lm", se = TRUE, color = "darkred", linetype = "dashed") +
  geom_text(aes(label = Nam), vjust = -0.8, size = 3) +
  labs(
    title = "Mối quan hệ giữa Tổng tài sản và Lợi nhuận sau thuế của VCB (2015–2024)",
    subtitle = "Đơn vị: Nghìn tỷ đồng",
    x = "Tổng tài sản (nghìn tỷ đồng)",
    y = "Lợi nhuận sau thuế (nghìn tỷ đồng)"
  ) +
  theme_minimal() +
  theme(
    plot.title = element_text(face = "bold", color = "navy", size = 14),
    plot.subtitle = element_text(size = 11, color = "gray40")
  )
## `geom_smooth()` using formula = 'y ~ x'

Giải thích kỹ thuật

aes(x = TTS / 1000, y = LNSTTNDN / 1000)    Dùng TTS làm trục X, LNST làm trục Y (đơn vị: nghìn tỷ).
geom_point()    Vẽ các điểm biểu diễn từng năm.
geom_smooth(method = "lm")  Thêm đường hồi quy tuyến tính (màu đỏ, dạng đứt đoạn).
geom_text(aes(label = Nam)) Ghi chú năm tương ứng lên từng điểm.
theme_minimal() Làm biểu đồ sạch, dễ đọc.

📈 Ý nghĩa thống kê

2.4.11 Biểu đồ tròn phân tổ – Cơ cấu nguồn vốn của VCB giai đoạn 2015–2024

Cơ cấu nguồn vốn là một trong những yếu tố quan trọng nhất trong đánh giá sức khỏe tài chính ngân hàng. Tỷ lệ giữa Nợ phải trả (NPT) và Vốn chủ sở hữu (VCSH) thể hiện mức độ đòn bẩy tài chính và khả năng tự chủ vốn của ngân hàng.

Để quan sát xu hướng thay đổi cơ cấu này qua thời gian, ta sử dụng biểu đồ tròn phân tổ (facet pie chart) — trong đó mỗi vòng tròn đại diện cho một năm từ 2015 đến 2024, giúp thể hiện rõ mức độ thay đổi tỷ trọng của từng thành phần nguồn vốn qua từng giai đoạn.

library(ggplot2)
library(dplyr)
library(tidyr)

# Tính tỷ trọng nợ phải trả và vốn chủ sở hữu theo từng năm
VCB_tytrong <- VCBbctc %>%
  mutate(
    Tytrong_NPT = NPT / TNV * 100,
    Tytrong_VCSH = VCSH / TNV * 100
  ) %>%
  select(Nam, Tytrong_NPT, Tytrong_VCSH) %>%
  pivot_longer(cols = c(Tytrong_NPT, Tytrong_VCSH),
               names_to = "Thanhphan",
               values_to = "Tytrong")

# Biểu đồ tròn phân tổ
ggplot(VCB_tytrong, aes(x = "", y = Tytrong, fill = Thanhphan)) +
  geom_col(width = 1, color = "white") +
  coord_polar(theta = "y") +
  facet_wrap(~ Nam, ncol = 5) +
  scale_fill_manual(values = c("Tytrong_NPT" = "firebrick", "Tytrong_VCSH" = "steelblue"),
                    labels = c("Nợ phải trả", "Vốn chủ sở hữu")) +
  labs(
    title = "Cơ cấu nguồn vốn của VCB qua các năm (2015–2024)",
    subtitle = "Biểu đồ tròn phân tổ thể hiện tỷ trọng Nợ phải trả và Vốn chủ sở hữu",
    fill = "Thành phần nguồn vốn"
  ) +
  theme_void() +
  theme(
    plot.title = element_text(face = "bold", color = "navy", size = 14),
    plot.subtitle = element_text(color = "gray40", size = 11),
    legend.position = "bottom"
  )

Giải thích kỹ thuật

mutate()    Tính tỷ trọng (%) của NPT và VCSH trong Tổng nguồn vốn từng năm.
pivot_longer()  Chuyển dữ liệu sang dạng “dài” để ggplot dễ vẽ nhiều lát cắt.
geom_col()  Vẽ biểu đồ cột (nền), sau đó chuyển sang tròn bằng coord_polar().
coord_polar(theta = "y")    Biến cột thành biểu đồ tròn.
facet_wrap(~ Nam)   Tạo nhiều biểu đồ nhỏ, mỗi biểu đồ là một năm.
scale_fill_manual() Đặt màu riêng cho từng thành phần nguồn vốn.

2.4.12 Xu hướng cơ cấu tài sản của VCB giai đoạn 2015–2024

Trong ngân hàng, tài sản phản ánh quy mô và định hướng hoạt động của tổ chức. Việc phân tích cơ cấu tài sản cho thấy VCB đang phân bổ nguồn vốn vào đâu – giữa tiền mặt (T_KTDT), tài sản dài hạn (TSDH) và tài sản khác (chủ yếu là cho vay, đầu tư ngắn hạn, tài sản sinh lời).

Biểu đồ diện tích (area chart) giúp trực quan hóa tỷ trọng từng loại tài sản trong tổng tài sản qua các năm, từ đó đánh giá được xu hướng chuyển dịch cơ cấu tài sản — liệu VCB đang tăng tính thanh khoản, mở rộng đầu tư dài hạn, hay giữ nguyên cấu trúc ổn định trong giai đoạn 2015–2024.

library(ggplot2)
library(dplyr)
library(tidyr)

# Chuẩn bị dữ liệu: tính tỷ trọng từng loại tài sản trong tổng tài sản
VCB_TS_long <- VCBbctc %>%
  mutate(
    TS_khac = TTS - (T_KTDT + TSDH),
    Tytrong_Tien = T_KTDT / TTS * 100,
    Tytrong_TSDH = TSDH / TTS * 100,
    Tytrong_TSkhac = TS_khac / TTS * 100
  ) %>%
  select(Nam, Tytrong_Tien, Tytrong_TSDH, Tytrong_TSkhac) %>%
  pivot_longer(cols = c(Tytrong_Tien, Tytrong_TSDH, Tytrong_TSkhac),
               names_to = "Loai_TS", values_to = "Tytrong")

# Vẽ biểu đồ area
ggplot(VCB_TS_long, aes(x = Nam, y = Tytrong, fill = Loai_TS)) +
  geom_area(alpha = 0.8, color = "white") +
  scale_fill_manual(
    values = c("Tytrong_Tien" = "skyblue", 
               "Tytrong_TSDH" = "goldenrod", 
               "Tytrong_TSkhac" = "darkgreen"),
    labels = c("Tiền & tương đương tiền", "Tài sản dài hạn", "Tài sản khác")
  ) +
  labs(
    title = "Xu hướng cơ cấu tài sản của VCB giai đoạn 2015–2024",
    subtitle = "Biểu đồ diện tích thể hiện tỷ trọng từng loại tài sản trong tổng tài sản",
    x = "Năm",
    y = "Tỷ trọng (%)",
    fill = "Thành phần tài sản"
  ) +
  theme_minimal() +
  theme(
    plot.title = element_text(face = "bold", color = "navy", size = 14),
    plot.subtitle = element_text(size = 11, color = "gray40"),
    legend.position = "bottom"
  )

Giải thích kỹ thuật

mutate()    Tính phần “Tài sản khác” và tỷ trọng của từng loại tài sản so với tổng tài sản.
pivot_longer()  Chuyển dữ liệu từ dạng rộng (nhiều cột) sang dạng dọc để ggplot có thể vẽ được nhiều lớp.
geom_area() Tạo biểu đồ diện tích (area chart), các vùng chồng lên nhau theo tỷ trọng.
scale_fill_manual() Gán màu sắc riêng cho từng loại tài sản để dễ phân biệt.
alpha = 0.8 Độ trong suốt nhẹ giúp nhìn các lớp chồng nhau rõ ràng hơn.

2.4.13 Biểu đồ heatmap – Ma trận tương quan giữa các chỉ tiêu tài chính

Trong phân tích tài chính, việc hiểu rõ mối quan hệ giữa các chỉ tiêu cốt lõi giúp nhà phân tích nhận biết yếu tố nào ảnh hưởng mạnh đến hiệu quả hoạt động và sức khỏe tài chính của ngân hàng.

Đối với VCB, ta xem xét 6 biến đại diện cho ba nhóm khía cạnh chính:

Hiệu quả sinh lời: ROA, ROE

An toàn tài chính: CAR, DE

Tăng trưởng: Tangtruong_TS, Tangtruong_LNST

Bằng cách trực quan hóa hệ số tương quan (correlation) dưới dạng heatmap, ta có thể nhìn rõ mức độ và chiều hướng liên hệ (thuận hoặc nghịch) giữa các biến này trong giai đoạn 2015–2024.

library(ggplot2)
library(reshape2)
## 
## Attaching package: 'reshape2'
## The following object is masked from 'package:tidyr':
## 
##     smiths
library(dplyr)

# Tạo ma trận tương quan giữa các biến tài chính chính
corr_data <- VCBbctc %>%
  select(ROA, ROE, CAR, DE, Tangtruong_TS, Tangtruong_LNST) %>%
  cor(use = "complete.obs")

# Chuyển dữ liệu sang dạng "dài" để ggplot đọc được
melted_corr <- melt(corr_data)

# Vẽ heatmap
ggplot(melted_corr, aes(x = Var1, y = Var2, fill = value)) +
  geom_tile(color = "white") +
  geom_text(aes(label = round(value, 2)), color = "black", size = 3.5) +
  scale_fill_gradient2(
    low = "red", mid = "white", high = "blue", midpoint = 0,
    limits = c(-1, 1)
  ) +
  labs(
    title = "Ma trận tương quan giữa các chỉ tiêu tài chính của VCB (2015–2024)",
    subtitle = "Màu xanh thể hiện tương quan dương, màu đỏ thể hiện tương quan âm",
    x = "",
    y = "",
    fill = "Hệ số tương quan"
  ) +
  theme_minimal() +
  theme(
    plot.title = element_text(face = "bold", color = "navy", size = 14),
    plot.subtitle = element_text(color = "gray40", size = 11),
    axis.text.x = element_text(angle = 45, hjust = 1)
  )

Giải thích kỹ thuật

cor()   Tính hệ số tương quan Pearson giữa các cặp biến.
melt()  Chuyển ma trận tương quan thành dạng “dài” để ggplot có thể đọc từng cặp biến.
geom_tile() Tạo các ô vuông màu thể hiện giá trị tương quan (nhiệt độ màu).
scale_fill_gradient2()  Màu đỏ cho tương quan âm, xanh cho tương quan dương, trắng cho trung tính.
geom_text() Ghi giá trị số của hệ số tương quan trong mỗi ô.

2.4.14 Tăng trưởng tổng tài sản và lợi nhuận sau thuế của VCB (2015–2024)

Tăng trưởng là chỉ dấu quan trọng phản ánh sức khỏe và khả năng mở rộng hoạt động của ngân hàng. Tuy nhiên, tăng trưởng quy mô (tổng tài sản) chỉ thật sự có ý nghĩa nếu đi kèm với tăng trưởng hiệu quả (lợi nhuận sau thuế).

Do đó, việc so sánh tốc độ tăng tổng tài sản (Tangtruong_TS) và tốc độ tăng lợi nhuận sau thuế (Tangtruong_LNST) trong giai đoạn 2015–2024 giúp xác định mức độ đồng bộ giữa mở rộng quy mô và hiệu quả kinh doanh của VCB.

library(ggplot2)
library(dplyr)
library(tidyr)

# Chuẩn bị dữ liệu theo định dạng "dài"
VCB_tangtruong <- VCBbctc %>%
  select(Nam, Tangtruong_TS, Tangtruong_LNST) %>%
  pivot_longer(
    cols = c(Tangtruong_TS, Tangtruong_LNST),
    names_to = "Chi_tieu",
    values_to = "Tangtruong"
  )

# Biểu đồ đường
ggplot(VCB_tangtruong, aes(x = Nam, y = Tangtruong, color = Chi_tieu, group = Chi_tieu)) +
  geom_line(size = 1.3) +
  geom_point(size = 3) +
  scale_color_manual(
    values = c("Tangtruong_TS" = "steelblue", "Tangtruong_LNST" = "firebrick"),
    labels = c("Tăng trưởng Tổng tài sản", "Tăng trưởng Lợi nhuận sau thuế")
  ) +
  labs(
    title = "Tăng trưởng Tổng tài sản và Lợi nhuận sau thuế của VCB (2015–2024)",
    subtitle = "So sánh tốc độ tăng trưởng quy mô và hiệu quả qua thời gian",
    x = "Năm",
    y = "Tốc độ tăng trưởng (%)",
    color = "Chỉ tiêu"
  ) +
  theme_minimal() +
  theme(
    plot.title = element_text(face = "bold", color = "navy", size = 14),
    plot.subtitle = element_text(color = "gray40", size = 11),
    legend.position = "bottom"
  )

Giải thích kỹ thuật

pivot_longer()  Chuyển dữ liệu từ dạng ngang (2 cột tăng trưởng) sang dọc để ggplot vẽ được nhiều đường.
geom_line() Vẽ đường biểu diễn xu hướng tăng trưởng của từng chỉ tiêu.
geom_point()    Thêm điểm dữ liệu cụ thể để làm nổi bật từng năm.
scale_color_manual()    Đặt màu riêng cho từng biến để phân biệt rõ ràng.
theme_minimal() Làm sạch biểu đồ, giữ tập trung vào dữ liệu.

2.4.15 Biểu đồ scatter + màu – Quan hệ LCTTHDKD và LNSTTNDN

Trong phân tích tài chính, chất lượng lợi nhuận là yếu tố phản ánh tính bền vững của kết quả kinh doanh. Lợi nhuận sau thuế (LNSTTNDN) cho biết hiệu quả kế toán, còn lưu chuyển tiền thuần từ hoạt động kinh doanh (LCTTHDKD) thể hiện lượng tiền thật mà doanh nghiệp thu được từ hoạt động cốt lõi.

Đối với ngân hàng, nếu hai chỉ tiêu này đồng biến, nghĩa là lợi nhuận không chỉ trên sổ sách mà còn chuyển hóa thành dòng tiền thực — dấu hiệu cho hiệu quả hoạt động và sức khỏe tài chính lành mạnh.

library(ggplot2)

ggplot(VCBbctc, aes(x = LNSTTNDN / 1000, y = LCTTHDKD / 1000, color = factor(Nam))) +
  geom_point(size = 4, alpha = 0.7) +
  geom_smooth(method = "lm", se = FALSE, color = "gray40", linetype = "dashed") +
  scale_color_viridis_d(option = "C") +
  labs(
    title = "Quan hệ giữa Lợi nhuận sau thuế và Lưu chuyển tiền thuần từ HĐKD của VCB (2015–2024)",
    subtitle = "Biểu đồ tán xạ thể hiện chất lượng lợi nhuận qua mối liên hệ giữa dòng tiền và lợi nhuận",
    x = "Lợi nhuận sau thuế TNDN (nghìn tỷ đồng)",
    y = "Lưu chuyển tiền thuần từ hoạt động kinh doanh (nghìn tỷ đồng)",
    color = "Năm"
  ) +
  theme_minimal() +
  theme(
    plot.title = element_text(face = "bold", color = "navy", size = 14),
    plot.subtitle = element_text(color = "gray40", size = 11),
    legend.position = "bottom"
  )
## `geom_smooth()` using formula = 'y ~ x'

Giải thích kỹ thuật

aes(x = LNSTTNDN, y = LCTTHDKD, color = factor(Nam))    Trục hoành là lợi nhuận sau thuế, trục tung là dòng tiền hoạt động. Màu sắc thể hiện năm.
geom_point()    Vẽ các điểm dữ liệu, mỗi điểm là một năm.
geom_smooth(method = "lm")  Thêm đường hồi quy tuyến tính (dashed) để thể hiện xu hướng chung.
scale_color_viridis_d() Tô màu đẹp, tương phản tốt cho từng năm.
/1000   Quy đổi sang nghìn tỷ đồng để biểu đồ dễ đọc.

2.4.16 So sánh các chỉ tiêu tài chính của VCB trước và sau COVID-19

Giai đoạn đại dịch COVID-19 (2020–2021) đánh dấu biến động lớn trong hoạt động ngân hàng, tác động trực tiếp đến hiệu quả sinh lời, an toàn vốn và chất lượng dòng tiền. Để đánh giá tác động và khả năng phục hồi của VCB, biểu đồ dưới đây thể hiện diễn biến của các chỉ tiêu tài chính qua từng năm từ 2015–2024, làm nổi bật giai đoạn COVID-19 để dễ quan sát xu hướng trước và sau.

library(ggplot2)
library(tidyr)
library(dplyr)

# Tạo biến giai đoạn
VCB_covid <- VCBbctc %>%
  mutate(Giai_doan = ifelse(Nam <= 2019, "Trước COVID-19", "Sau COVID-19")) %>%
  select(Nam, Giai_doan, ROA, ROE, CAR, Tangtruong_LNST, LCTTHDKD) %>%
  pivot_longer(cols = c(ROA, ROE, CAR, Tangtruong_LNST, LCTTHDKD),
               names_to = "Chi_tieu", values_to = "Gia_tri")

# Biểu đồ cột chia giai đoạn
ggplot(VCB_covid, aes(x = factor(Nam), y = Gia_tri, fill = Giai_doan)) +
  geom_col(position = "dodge") +
  facet_wrap(~ Chi_tieu, scales = "free_y") +
  scale_fill_manual(values = c("Trước COVID-19" = "steelblue", "Sau COVID-19" = "firebrick")) +
  labs(
    title = "So sánh các chỉ tiêu tài chính của VCB trước và sau COVID-19 (2015–2024)",
    subtitle = "Biểu đồ cột nhóm theo năm và phân giai đoạn COVID-19",
    x = "Năm",
    y = "Giá trị (%) hoặc nghìn tỷ đồng",
    fill = "Giai đoạn"
  ) +
  theme_minimal() +
  theme(
    plot.title = element_text(face = "bold", color = "navy", size = 14),
    plot.subtitle = element_text(color = "gray40", size = 11),
    strip.text = element_text(face = "bold", size = 11),
    axis.text.x = element_text(angle = 30, hjust = 1),
    legend.position = "bottom"
  )
## Warning: Removed 1 row containing missing values or values outside the scale range
## (`geom_col()`).

Giải thích kỹ thuật

mutate(Giai_doan = ifelse(Nam <= 2019, ...))    Tạo biến phân loại “Trước COVID” và “Sau COVID”.
pivot_longer()  Gom các chỉ tiêu về 2 cột: tên chỉ tiêu (Chi_tieu) và giá trị (Gia_tri).
geom_col(position = "dodge")    Tạo các cột song song cho hai giai đoạn, giúp so sánh trực tiếp.
facet_wrap(~ Chi_tieu)  Mỗi chỉ tiêu là một biểu đồ con riêng — giúp dễ đọc, không lẫn giá trị.
scale_fill_manual() Màu xanh cho trước COVID, đỏ cho sau COVID.

Nhận xét Biểu đồ cho thấy các chỉ tiêu tài chính của VCB có sự biến động rõ giữa hai giai đoạn.

Trước COVID-19 (2015–2019), các chỉ tiêu duy trì ổn định với hiệu quả và tăng trưởng đều.

Trong giai đoạn 2020–2021, ROA, ROE và tăng trưởng lợi nhuận giảm do ảnh hưởng dịch, đồng thời dòng tiền hoạt động (LCTTHDKD) âm mạnh.

Từ 2022 trở đi, các chỉ tiêu phục hồi nhanh, ROE và ROA tăng trở lại, dòng tiền dương, phản ánh khả năng thích ứng và phục hồi tốt của VCB.

Tỷ lệ an toàn vốn (CAR) được duy trì ổn định, cho thấy nền tảng tài chính vững chắc và chất lượng lợi nhuận được cải thiện sau đại dịch.