1 PHẦN 1: BỘ DỮ LIỆU MARKETING CAMPAIGN PERFORMANCE DATASET

1.1 Giới thiệu dữ liệu

1.1.1 Tải thư viện

library(readr)      
library(readxl)      
library(tidyverse)   
library(lubridate)   
library(scales)      
library(ggplot2)     
library(dplyr)       
library(knitr)       
library(kableExtra)   
library(DT)         
library(janitor)     
library(gt)          
library(skimr)       
library(patchwork)   
library(showtext)    
library(reshape2)   
library(broom)       
library(ggcorrplot)
library(gridExtra) 
library(grid)       
library(waterfalls)  
options(knitr.kable.NA = "") 
options(scipen = 999)
  • Giải thích Kỹ thuật:
    • 1 - 2: Đọc dữ liệu từ file CSV và Excel.
    • 3: Xử lý và phân tích dữ liệu.
    • 4: Làm việc với dữ liệu thời gian.
    • 5 - 6: Trực quan hóa dữ liệu.
    • 7: Biến đổi dữ liệu.
    • 8: Hàm Kable() dùng để tạo bảng từ dữ liệu dạng data.frame hoặc tibble.
    • 9: THàm Kable_styling()dùng để tùy chỉnh định dạng bảng được tạo bởi hàm Kable().
    • 10: Hiển thị bảng dữ liệu tương tác.
    • 11: Làm sạch và chuẩn hóa dữ liệu.
    • 12: Dùng để tạo ra các bảng đẹp thường thấy trong báo cáo.
    • 13: Thống kê mô tả nhanh dữ liệu.
    • 14: Kết hợp nhiều biểu đồ ggplot2.
    • 15: Hỗ trợ font chữ trong biểu đồ.
    • 16: Biến đổi dữ liệu dạng rộng/dài.
    • 17: Chuyển đổi kết quả mô hình thành data frame.
    • 18: Vẽ ma trận tương quan.
    • 19 - 20: Sắp xếp nhiều biểu đồ trên cùng một trang.
    • 21: Vẽ biểu đồ thác nước.
    • 22: Ẩn giá trị NA thay bằng hiển thị ô trống.
    • 23: Tắt ký hiệu khoa học trong biểu đồ và bảng.

1.1.2 Đọc dữ liệu

kd <- read_csv("D:/HK3-2025/NGON_NGU_LAP_TRINH/Marketing_Campaign_Performance_Dataset.csv")
  • Giải thích kỹ thuật:
    • Sử dụng hàm read_csv() từ gói readr để đọc dữ liệu từ file CSV và lưu vào biến kd dưới dạng data frame.

1.1.3 Xem trước dữ liệu

kd %>%
  slice(1:20) %>%  
    kable(format = "latex", booktabs = TRUE,  
    caption = "20 dòng đầu của bộ dữ liệu Marketing Campaign Performance Dataset") %>%
    kable_styling(
      latex_options = c("hold_position", "scale_down"),
      position = "center" )
  • Giải thích kỹ thuật:
    • 1: Toán tử %>% dùng để truyền kết quả từ bước này đến bước tiếp theo.
    • 2: Lấy 20 dòng đầu tiên của bảng dữu liệu gốc.
    • 3 - 7: Tạo bảng từ dữ liệu đã chọn và tùy chỉnh định dạng bảng.
    • 3: Định dạng dùng latex, booktabs = TRUE bật đường kẻ bảng.
    • 4: Đặt tên cho bảng.
    • 6: hold_position: Giữ bảng đứng vị trí khi xuất file, scale_down: Thu nhỏ bảng nếu bảng quá rộng, nhưng vẫn giữ tỷ lệ.
    • 7: Căn giữa bảng trên trang.
  • Bộ dữ liệu hiệu suất chiến dịch marketing cung cấp những thông tin chi tiết có giá trị về hiệu quả của các chiến dịch tiếp thị khác nhau. Bộ dữ liệu này ghi lại các số liệu hiệu suất, đối tượng mục tiêu, thời lượng, kênh được sử dụng và các yếu tố thiết yếu khác góp phần vào sự thành công của các sáng kiến tiếp thị. Với 200.000 hàng dữ liệu riêng biệt trong một năm, bộ dữ liệu này cung cấp cái nhìn toàn diện về hiệu suất chiến dịch trên nhiều công ty và phân khúc khách hàng khác nhau.
  • Ý nghĩa của các biến trong bộ dữ liệu:
    • Company: Tên công ty triển khai chiến dịch.
    • Campaign_Type: Loại chiến dịch được sử dụng.
    • Target_Audience: Đối tượng muục tiêu của chiến dịch.
    • Duration: Thời lượng chiến dịch (ngày).
    • Channel_Used: Kênh quảng cáo được sử dụng.
    • Conversion_Rate: Tỷ lệ chuyển đổi (của khách hàng tiềm năng hoặc lượt hiển thị được chuyển đổi thành hành động mục tiêu mà doanh nghiệp hướng đến)
    • Location: Địa điểm triển khai chiến dịch.
    • Language: Ngôn ngữ quảng cáo.
    • Acquisition_Cost: Chi phí thu hút khách hàng.
    • ROI: Tỷ suất hoàn vốn của chiến dịch.
    • Clicks: Số lần nhấp chuột vào quảng cáo.
    • Impressions: Số lần hiển thị quảng cáo.
    • Engagement_Score: Điểm số đánh giá mức độ tương tác của người dùng với quảng cáo.
    • Customer_Segment: Phân khúc khách hàng mục tiêu..
    • Date: Ngày triển khai chiến dịch.

1.1.4 Xem cấu trúc dữ liệu

cau_truc <- data.frame(
  `Ten bien` = names(kd),
  `Kieu du lieu` = sapply(kd, function(x) class(x)[1]),
  `Gia tri dau` = sapply(kd, function(x) as.character(x[1])),
  check.names = FALSE)
kable(cau_truc,
    caption = "Cấu trúc dữ liệu",
    row.names = FALSE,
    col.names = c("Tên biến", "Kiểu dữ liệu", "Giá trị đầu tiên")) %>%
  kable_styling(
    latex_options = c("hold_position", "scale_down"))
Cấu trúc dữ liệu
Tên biến Kiểu dữ liệu Giá trị đầu tiên
Company character Innovate Industries
Campaign_Type character Email
Target_Audience character Men 18-24
Duration character 30 days
Channel_Used character Google Ads
Conversion_Rate numeric 0.04
Acquisition_Cost character $16,174.00
ROI numeric 6.29
Location character Chicago
Language character Spanish
Clicks numeric 506
Impressions numeric 1922
Engagement_Score numeric 6
Customer_Segment character Health & Wellness
Date character 01/01/2021
  • Giải thích kỹ thuật:
    • 1 - 5: Tạo bảng cấu trúc dữ liệu với tên biến, kiểu dữ liệu và giá trị đầu tiên của mỗi biến.
      • Sapply(): Áp dụng hàm cho từng cột trong data frame.
      • 5: Giữ nguyên tên trong cột.
    • 6 - 11: Hiển thị bảng cấu trúc dữ liệu với định dạng đẹp.
      • 8: Bỏ qua cột tên, không in ra.
  • Các Cột Ký tự: Company, Campaign_Type, Target_Audience, Duration, Channel_Used, Acquisition_Cost, Location, Language, Customer_Segment, Date.
  • Các Cột Số: Conversion_Rate, ROI, Clicks, Impressions, Engagement_Score.

1.1.5 Chuẩn hóa tên biến

kd <- clean_names(kd) 
  • Giải thích kỹ thuật:
    • Sử dụng hàm clean_names() từ gói janitor để chuẩn hóa tên biến:
      • Viết thường tất cả các ký tự.
      • Thay khoảng trắng và ký tự đặc biệt bằng dấu gạch dưới (_).
      • Giúp tên biến dễ đọc, dễ sử dụng trong phân tích và trực quan hóa dữ liệu.

1.1.6 Xem lại tên biến sau khi chuẩn hóa

names(kd)
 [1] "company"          "campaign_type"    "target_audience"  "duration"        
 [5] "channel_used"     "conversion_rate"  "acquisition_cost" "roi"             
 [9] "location"         "language"         "clicks"           "impressions"     
[13] "engagement_score" "customer_segment" "date"            
  • Tên khi đã được chuẩn hóa, đặt tên rõ ràng, nhất quán (viết thường, nối bằng dấu gạch dưới)

1.1.7 Kích thước dữ liệu

dim(kd)
[1] 200000     15
  • Bộ dữ liệu có cấu trúc lớn và chi tiết, 200.000 quan sát (dòng) và 15 biến (cột)

1.1.8 Thống kê mô tả cơ bản

skim(kd)
Data summary
Name kd
Number of rows 200000
Number of columns 15
_______________________
Column type frequency:
character 10
numeric 5
________________________
Group variables None

Variable type: character

skim_variable n_missing complete_rate min max empty n_unique whitespace
company 0 1 8 19 0 5 0
campaign_type 0 1 5 12 0 5 0
target_audience 0 1 8 11 0 5 0
duration 0 1 7 7 0 4 0
channel_used 0 1 5 10 0 6 0
acquisition_cost 0 1 9 10 0 15001 0
location 0 1 5 11 0 5 0
language 0 1 6 8 0 5 0
customer_segment 0 1 7 19 0 5 0
date 0 1 10 10 0 365 0

Variable type: numeric

skim_variable n_missing complete_rate mean sd p0 p25 p50 p75 p100 hist
conversion_rate 0 1 0.08 0.04 0.01 0.05 0.08 0.12 0.15 ▆▇▇▇▆
roi 0 1 5.00 1.73 2.00 3.50 5.01 6.51 8.00 ▇▇▇▇▇
clicks 0 1 549.77 260.02 100.00 325.00 550.00 775.00 1000.00 ▇▇▇▇▇
impressions 0 1 5507.30 2596.86 1000.00 3266.00 5517.50 7753.00 10000.00 ▇▇▇▇▇
engagement_score 0 1 5.49 2.87 1.00 3.00 5.00 8.00 10.00 ▇▇▇▇▇
  • Giải thích kỹ thuật:
    • Sử dụng hàm skim() từ gói skimr để tạo bảng thống kê mô tả nhanh cho toàn bộ bộ dữ liệu.
    • Cung cấp thông tin về kiểu dữ liệu, số lượng quan sát, giá trị thiếu, các thống kê cơ bản như trung bình, độ lệch chuẩn, min, max của các biến định lượng.
    • Hàm Skim() gchia kết quả thành 3 bảng nhỏ:
      • Bảng 1: (Data summary) thông tin chung về bộ dữ liệu.
      • Bảng 2: (Variable type: character) thống kê mô tả cho các biến định tính.
      • Bảng 3: (Variable type: numeric) thống kê mô tả cho các biến định lượng.
  • Kết quả:
    • Có biến Acquisition_Cost là chi phí nhưng lại ở kiểu character và Date chưa phải là ở định dạng ngày tháng năm.
    • ROI: Mean = 5, chiến dịch tạo ra lợi nhuận cao gấp 5 lần chi phí trung bình.
    • Clicks: Mean = 500 trong khoảng 325 đến 775. Quảng cáo thu hút được số lượng nhấp chuột khá tốt.
    • Conversion_Rate: Mean = 8% trong khoảng 5% đến 12%. Tỷ lệ chuyển đổi trung bình khá ổn định.

1.1.9 Kiểm tra dữ liệu bị thiếu

sum(is.na(kd))
[1] 0
colSums(is.na(kd))  
         company    campaign_type  target_audience         duration 
               0                0                0                0 
    channel_used  conversion_rate acquisition_cost              roi 
               0                0                0                0 
        location         language           clicks      impressions 
               0                0                0                0 
engagement_score customer_segment             date 
               0                0                0 
  • Giải thích kỹ thuật:
    • Sử dụng hàm is.na() để kiểm tra các giá trị bị thiếu trong toàn bộ bộ dữ liệu.
    • Hàm sum() tính tổng số giá trị bị thiếu trong toàn bộ data frame.
    • Hàm colSums() kết hợp với is.na() để đếm số giá trị bị thiếu cho từng cột riêng biệt.
  • Kết quả: Không có giá trị bị thiếu trong bất kỳ biến nào, dữ liệu hoàn chỉnh và không cần xử lý.

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

sum(duplicated(kd))
[1] 0
  • Giải thích kỹ thuật:
    • Sử dụng hàm duplicated() để xác định các hàng trùng lặp trong data frame.
    • Hàm sum() tính tổng số hàng trùng lặp được tìm thấy.
  • Kết quả: Không có hàng trùng lặp nào trong bộ dữ liệu, đảm bảo tính duy nhất của mỗi quan sát.

1.1.11 Kiểm tra giá trị và tần suất xuất hiện trong các biến phân loại

1.1.11.1 Các công ty xuất hiện bao nhiêu lần

freq_company <- kd %>% 
  count(company, sort = TRUE)  
kable(freq_company, caption = "Tần suất theo Company",
      col.names =  c("Công ty", "Số lượng"))
Tần suất theo Company
Công ty Số lượng
TechCorp 40237
Alpha Innovations 40051
DataTech Solutions 40012
NexGen Systems 39991
Innovate Industries 39709
  • Giải thích kỹ thuật:
    • 2: count(): Đếm tần suất xuất hiện của mỗi công ty trong biến company.
    • sort = TRUE: Sắp xếp kết quả theo tần suất giảm dần.
    • 3 - 4: Hiển thị bảng tần suất.
  • Kết quả: Cả 5 công ty đều có số lượng chiến dịch xấp xỉ nha, cao nhất là TechCorp: 40237 và thấp nhất là Innovate Industries: 39709 với mức chênh lệch là 528 chiến dịch.

1.1.11.2 Loại chiến dịch phổ biến

freq_campaign_type <- kd %>%
  count(campaign_type, sort = TRUE)
kable(freq_campaign_type, caption = "Tần suất theo loại chiến dịch",
      col.names = c("Loại chiến dịch", "Số lượng chiến dịch"))
Tần suất theo loại chiến dịch
Loại chiến dịch Số lượng chiến dịch
Influencer 40169
Search 40157
Display 39987
Email 39870
Social Media 39817
  • Kết quả:
    • Influencer có tần suất cao nhất (40.169): đây là loại chiến dịch được sử dụng nhiều nhất.
    • Các chiến dịch phân bố khá đồng đều, không có loại chiến dịch nào quá vượt trội.
  • Điều này cho thấy các công ty đa dạng hóa chiến lược tiếp thị của mình bằng cách sử dụng nhiều loại chiến dịch khác nhau.

1.1.11.3 Ngôn ngữ phổ biến

freq_language <- kd %>%
  count(language, sort = TRUE)
kable(freq_language, caption = "Tần suất theo ngôn ngữ quảng cáo",
      col.names = c("Ngôn ngữ", "Số lượng"))
Tần suất theo ngôn ngữ quảng cáo
Ngôn ngữ Số lượng
Mandarin 40255
Spanish 40102
German 39983
English 39896
French 39764
  • Kết quả:
    • Mandarin là ngôn ngữ được sử dụng nhiều nhất trong các chiến dịch.
    • Tiếng Anh và Tây Ban Nha cũng được sử dụng phổ biến, cho thấy sự đa dạng về ngôn ngữ trong các chiến dịch quảng cáo.

1.1.11.4 Đối tượng nghiên cứu

freq_target_audience <- kd %>%
  count(target_audience, sort = TRUE)
kable(freq_target_audience, caption = "Tần suất theo đối tượng nghiên cứu",
      col.names = c("Đối tượng nghiên cứu", "Số lượng"))
Tần suất theo đối tượng nghiên cứu
Đối tượng nghiên cứu Số lượng
Men 18-24 40258
Men 25-34 40023
All Ages 40019
Women 25-34 40013
Women 35-44 39687
  • Kết quả:
    • Men 18–24 (40.258): nhóm được nhắm mục tiêu nhiều nhất trong các chiến dịch.
    • Men 25–34, All Ages, Women 25–34 có số lượng chiến dịch gần tương đương.

1.1.11.5 Địa điểm triển khai quảng cáo

freq_location <- kd %>%
  count(location, sort = TRUE)
kable(freq_location, caption = "Tần suất theo địa điểm triển khai quảng cáo",
      col.names = c("Địa điểm", "Số lượng"))
Tần suất theo địa điểm triển khai quảng cáo
Địa điểm Số lượng
Miami 40269
New York 40024
Chicago 40010
Los Angeles 39947
Houston 39750
  • Kết quả:
    • Miami (40.269) → đây là nơi triển khai quảng cáo nhiều nhất.
    • Houston thấp nhất (39.750) nhưng mức chênh lệch không quá lớn.

1.1.11.6 Phân khúc khách hàng

freq_customer_segment <- kd %>%
  count(customer_segment, sort = TRUE)
kable(freq_customer_segment, caption = "Tần suất theo phân khúc khách hàng",
      col.names = c("Nhóm khách hàng", "Số lượng"))
Tần suất theo phân khúc khách hàng
Nhóm khách hàng Số lượng
Foodies 40208
Tech Enthusiasts 40151
Outdoor Adventurers 40011
Health & Wellness 39888
Fashionistas 39742
  • Kết quả:
    • Foodies (40.208): đây là nhóm khách hàng được nhắm tới nhiều nhất trong các chiến dịch.
    • Phân khúc khách hàng được phân bổ khá đồng đều, cho thấy chiến dịch hướng đến nhiều nhóm sở thích khác nhau, không tập trung quá mạnh vào một phân khúc
  • Kết quả chung: Việc phân tần suất của các biến trên cho thấy bộ dữ liệu có cấu trúc cân bằng tuyệt đối một chiến lược tiếp thị rất đa dạng.. Các công ty không chỉ tập trung vào một loại chiến dịch hay một nhóm khách hàng duy nhất, mà có xu hướng phân bổ đều nguồn lực để tiếp cận nhiều phân khúc khác nhau.Việc sử dụng nhiều ngôn ngữ và địa điểm khác nhau phản ánh khả năng thích ứng với thị trường đa dạng và nhu cầu của khách hàng. Điều này giúp tăng khả năng tiếp cận và hiệu quả của các chiến dịch quảng cáo.

1.2 Xử lý và mã hóa dữ liệu

1.2.1 Chuyển đổi kiểu dữ liệu

kd$date <- as.Date(kd$date, format = "%d/%m/%Y")
kd$acquisition_cost <- as.numeric(gsub("[\\$,]", "", kd$acquisition_cost))
kd$duration <- as.numeric(gsub(" days", "", kd$duration))
cau_truc_moi <- data.frame(
  `Tên biến` = names(kd),
  `Kiểu dữ liệu` = sapply(kd, function(x) class(x)[1]),
  `Giá trị đầu` = sapply(kd, function(x) as.character(x[1])),
  check.names = FALSE,
  row.names = NULL)
kable(cau_truc_moi, caption = "Cấu trúc dữ liệu sau khi chuyển đổi kiểu dữ liệu",
      col.names = c("Tên biến", "Kiểu dữ liệu", "Giá trị đầu tiên")) %>%
  kable_styling(
    latex_options = c("hold_position", "scale_down"))
Cấu trúc dữ liệu sau khi chuyển đổi kiểu dữ liệu
Tên biến Kiểu dữ liệu Giá trị đầu tiên
company character Innovate Industries
campaign_type character Email
target_audience character Men 18-24
duration numeric 30
channel_used character Google Ads
conversion_rate numeric 0.04
acquisition_cost numeric 16174
roi numeric 6.29
location character Chicago
language character Spanish
clicks numeric 506
impressions numeric 1922
engagement_score numeric 6
customer_segment character Health & Wellness
date Date 2021-01-01
  • Giải thích kỹ thuật:
    • 1: Chuyển đổi biến date từ chuỗi ký tự sang định dạng ngày tháng năm.
    • 2: Chuyển đổi biến acquisition_cost từ chuỗi ký tự sang số thực. gsub():loại bỏ ký tự $ và dấu phẩy.
    • 3: Chuyển đổi biến duration từ chuỗi ký tự sang số nguyên bằng cách loại bỏ chuỗi ” days”.
    • 4 - 13: Tạo bảng cấu trúc dữ liệu mới sau khi chuyển đổi kiểu dữ liệu và hiển thị bảng.
  • Kết quả: Giúp chuẩn hóa dữ liệu và là bước quan trọng trong quy trình làm sạch dữ liệu.

1.2.2 Mã hóa mức độ tương tác (engagement_level)

q <- quantile(kd$engagement_score, probs = c(0.25, 0.75))
kd <- kd %>% mutate(  
    engagement_level = cut(  
      engagement_score,
      breaks = c(-Inf, q[1], q[2], Inf),
      labels = c("Thấp", "Trung bình", "Cao"),
      right = TRUE ))
kd %>%
  select(engagement_score, engagement_level) %>%
  distinct() %>%   
  arrange(engagement_score) %>%   
  kable(
    caption = "Bảng: Mã hóa mức độ tương tác (engagement_level)", 
    col.names = c("Engagement Score", "Engagement Level"))
Bảng: Mã hóa mức độ tương tác (engagement_level)
Engagement Score Engagement Level
1 Thấp
2 Thấp
3 Thấp
4 Trung bình
5 Trung bình
6 Trung bình
7 Trung bình
8 Trung bình
9 Cao
10 Cao
  • Kết quả:
    • 1: Tính các phân vị 25% và 75% của biến engagement_score để xác định ngưỡng phân loại.
    • 2: Mutate(): tạo biến mới
    • 3: cut(): phân nhóm theo khoảng phân vị đã tính.
    • 7: Khoảng bên phải được lấy vào nhóm.
    • 9: Chọn cột cần trình bày.
    • 10: Lấy các giá trị duy nhất để tránh lặp lại.
    • 11: Sắp xếp bảng theo mức độ tương tác tăng dần.
  • Kết quả:
    • Engagement Score 1–3: “Thấp”
      Các điểm thấp thể hiện mức độ tương tác yếu, người dùng ít quan tâm hoặc phản hồi.
    • Engagement Score 4–8: “Trung bình”
      Nhóm điểm này cho thấy mức độ tương tác vừa phải, người dùng có tương tác nhưng không nổi bật.
    • Engagement Score 9–10: “Cao” Đây là mức tương tác mạnh, thể hiện người dùng quan tâm tích cực và có khả năng chuyển đổi cao.
  • Việc phân loại này giúp các doanh nghiệp dễ xác định được chiến dịch nào đang hoạt động hiệu quả và chiến dịch nào cần cải thiện. Từ đó đưa ra quyết định phân bổ ngân sách, tối ưu hóa nội dung.

1.2.3 Mã hóa đối tượng nghiên cứu (target_audience) theo độ tuổi

kd <- kd %>% mutate(
  age_group = case_when(   
    grepl("18-24", target_audience) ~ "18-24", 
    grepl("25-34", target_audience) ~ "25-34",
    grepl("35-44", target_audience) ~ "35-44",
    TRUE ~ "All" ))
kd %>% 
  select(target_audience, age_group) %>%
  distinct() %>%
  arrange(age_group) %>%
  kable(
    caption = "Nhóm tuổi từ đối tượng nghiên cứu (target_audience)",
    col.names = c("Target Audience", "Age Group"))
Nhóm tuổi từ đối tượng nghiên cứu (target_audience)
Target Audience Age Group
Men 18-24 18-24
Men 25-34 25-34
Women 25-34 25-34
Women 35-44 35-44
All Ages All
  • Giải thích kỹ thuật:
    • 1: Mutate(): tạo biến mới.
    • 2: case_when(): xác định điều kiện để gán giá trị cho biến mới.
    • 3 - 5: grepl(): kiểm tra xem chuỗi ký tự có chứa độ tuổi cụ thể không.
    • 6: Nếu không khớp với bất kỳ độ tuổi nào, gán giá trị “All”.
    • 7 - 12: Chọn cột cần trình bày, lấy giá trị duy nhất, sắp xếp và hiển thị bảng.
  • Kết quả:
    • Nhóm All: Bao gồm các đối tượng nghiên cứu không xác định độ tuổi cụ thể.
    • Các nhóm như Men 18–24, Men 25–34, Women 25–34, Women 35–44 được tách lấy phần độ tuổi (18–24, 25–34, 35–44) để tạo biến age_group.

1.2.4 Mã hóa cấp độ thời lượng chiến dịch (duration_level)

kd <- kd %>% mutate(
    duration_level = case_when(
      duration == 15 ~ "Rất ngắn",
      duration == 30 ~ "Ngắn",
      duration == 45 ~ "Trung bình",
      duration == 60 ~ "Dài",
      TRUE ~ NA_character_ )) 
kd %>%
  select(duration, duration_level) %>%
  distinct() %>%
  arrange(duration) %>%
  kable(
    caption = "Cấp độ thời lượng chiến dịch (duration_level)",
    col.names = c("Duration (days)", "Duration Level"))
Cấp độ thời lượng chiến dịch (duration_level)
Duration (days) Duration Level
15 Rất ngắn
30 Ngắn
45 Trung bình
60 Dài
  • Giải thích kỹ thuật:
    • 3 - 6: == lọc các dòng có giá triị duration tương ứng và gán nhãn cấp độ.
    • 7: Nếu giá trị duration không khớp với bất kỳ mức nào, gán NA.
    • 8 - 14: Chọn cột cần trình bày, lấy giá trị duy nhất, sắp xếp và hiển thị bảng.
  • Kết quả: Biết được mục đích thực sự của các chiến dịch đang được triển khai.
    • 5 ngày “Rất ngắn”: Thời gian triển khai rất hạn chế, thường phù hợp các chiến dịch thử nghiệm hoặc khuyến mãi nhanh.
    • 30 ngày “Ngắn”: Khoảng thời gian vừa đủ cho các chiến dịch nhỏ, tập trung vào một mục tiêu cụ thể.
    • 45 ngày “Trung bình”: Thời lượng tiêu chuẩn, cho phép chiến dịch có đủ thời gian tiếp cận và tạo tương tác.
    • 60 ngày “Dài”: Chiến dịch có quy mô lớn hơn, cần thời gian dài để tiếp cận nhiều nhóm khách hàng và tối ưu hiệu quả.

1.2.5 Mã hóa số lần nhấp chuột vào quảng cáo (clicks_level)

q1 <- quantile(kd$clicks, probs = c(0.25, 0.75), na.rm = TRUE)
kd <- kd %>% mutate(
    clicks_level = cut(
      clicks,
      breaks = c(-Inf, q1[1], q1[2], Inf),
      labels = c("Thấp", "Trung bình", "Cao"),
      right = TRUE ))
kd %>%
  count(clicks_level) %>%
  arrange(match(clicks_level, c("Thấp", "Trung bình", "Cao"))) %>%
  distinct() %>%
  kable(caption = "Cấp độ số lần nhấp (clicks) theo phân vị",
      col.names = c("Clicks_level", "Số lượng quan sát"))
Cấp độ số lần nhấp (clicks) theo phân vị
Clicks_level Số lượng quan sát
Thấp 50142
Trung bình 99987
Cao 49871
  • Giải thích kỹ thuật:
    • 1: tính phân vị 25% và 75%
    • 2 - 7: Tạo biến mới clicks_level bằng cách phân loại số lần nhấp dựa trên phân vị đã tính.
    • 8 - 13: Đếm số lượng quan sát trong mỗi nhóm, sắp xếp và hiển thị bảng.
  • Kết quả:
    • Thấp (50.142 quan sát): Nhóm có số lần nhấp thấp nhất, nằm ở phân vị dưới cùng. Mức độ quan tâm của người dùng trong nhóm này còn hạn chế.
    • Trung bình (99.987 quan sát): Đây là nhóm lớn nhất, phản ánh phần lớn chiến dịch có mức nhấp ở mức trung bình, ổn định.
    • Cao (49.871 quan sát): Nhóm có số lần nhấp cao nhất, thuộc phân vị trên; thể hiện các chiến dịch thu hút mạnh và hiệu quả tốt về tương tác.
  • Phân nhóm Clicks giúp các nhà phân tích phân loại rủi ro và cơ hội, từ đó tối ưu hóa ngân sách bằng cách giảm đầu tư vào nhóm Thấp và nhân rộng các yếu tố thành công của nhóm Cao.

1.2.6 Mã hóa ngôn ngữ quảng cáo (language). Phân nhóm theo mức độ phổ biến

kd <- kd %>%
  add_count(language, name = "Frequency") %>%
  mutate(
    Language_Level = case_when(
      Frequency > quantile(Frequency, 0.75, na.rm = TRUE) ~ "Phổ biến cao",
      Frequency > quantile(Frequency, 0.25, na.rm = TRUE) ~ "Phổ biến trung bình",
      TRUE ~ "Ít phổ biến"))
kd %>%
  count(Language_Level, name = "Số lượng") %>%
  distinct() %>%
  arrange(factor(Language_Level, levels = c(
    "Ít phổ biến", "Phổ biến trung bình", "Phổ biến cao"))) %>%
  kable(caption = "Mức độ phổ biến của ngôn ngữ quảng cáo theo phân vị tần suất",
      col.names = c("Language_Level", "Số lượng"))
Mức độ phổ biến của ngôn ngữ quảng cáo theo phân vị tần suất
Language_Level Số lượng
Ít phổ biến 79660
Phổ biến trung bình 80085
Phổ biến cao 40255
  • Giải thích kỹ thuật:
    • 2: add_count(): Đếm tần suất xuất hiện của mỗi ngôn ngữ và lưu vào biến Frequency.
    • 3 - 7: Tạo biến mới Language_Level dựa trên tần suất xuất hiện so với các phân vị 25% và 75%.
    • 8 - 14: Đếm số lượng ngôn ngữ trong mỗi nhóm, sắp xếp và hiển thị bảng.
  • Kết quả:
    • Ít phổ biến (79.660 quan sát): Nhóm ngôn ngữ có tần suất xuất hiện thấp nhất, được sử dụng hạn chế trong các chiến dịch.
    • Phổ biến trung bình (80.085 quan sát): Chiếm tỷ trọng lớn nhất, phản ánh các ngôn ngữ được sử dụng ở mức phổ biến vừa phải và ổn định.
    • Phổ biến cao (40.255 quan sát): Nhóm ngôn ngữ xuất hiện nhiều nhất, thể hiện mức độ ưu tiên và sử dụng cao trong hoạt động quảng cáo.
  • Đánh giá rủi ro đa dạng hóa thị trường và điều chỉnh ngân sách, đảm bảo rằng các chiến dịch được phân bổ hiệu quả nhất cho các thị trường ngôn ngữ mang lại lợi nhuận cao nhất.

1.2.7 Tỷ lệ người xem quảng cáo khi đã nhấp chuột vào.(CTR)

\[CTR = \frac{clicks}{impressions}\]

kd <- kd %>%
  mutate(CTR = ifelse(impressions > 0, clicks / impressions, NA))
kd %>%
  select(clicks, impressions, CTR) %>%
  head(10) %>%
  arrange(CTR) %>%
  kable(caption = "Tỷ lệ người xem quảng cáo khi đã nhấp chuột vào (CTR)",
      col.names = c("Clicks", "Impressions", "CTR"))
Tỷ lệ người xem quảng cáo khi đã nhấp chuột vào (CTR)
Clicks Impressions CTR
116 7523 0.0154194
100 1643 0.0608643
584 7698 0.0758639
624 7854 0.0794500
379 4201 0.0902166
817 8749 0.0933821
217 1820 0.1192308
642 3856 0.1664938
506 1922 0.2632674
861 1754 0.4908780
  • Giải thích kỹ thuật:
    • 2: Nếu số lần hiển thị > 0 thì sẽ tính CTR, còn = 0 thì sẽ gán giá trị NA.
    • 4: Chọn cột clicks, impressions và CTR để hiển thị.
    • 5: Lấy 10 dòng đầu tiên để trình bày.
    • 6: Sắp xếp bảng theo CTR tăng dần.
  • Kết quả:
    • Tỷ lệ nhấp chuột (CTR)(%) biến động mạnh giữa các chiến dịch.
    • Một số chiến dịch có CTR rất thấp (khoảng 1,5%), trong khi những chiến dịch khác đạt CTR cao tới gần 50%.
    • CTR tăng dần theo mức độ hấp dẫn hoặc phù hợp của quảng cáo với đối tượng mục tiêu.
    • CTR cao phản ánh quảng cáo thu hút người xem và hiệu quả trong việc chuyển lượt xem thành nhấp chuột.
    • Giá trị CTR càng cao thể hiện quảng cáo càng hấp dẫn.

1.2.8 Chi phí trung bình cho mỗi clicks (CPC)

\[CPC = \frac{acquisition\_cost}{clicks}\]

kd <- kd %>%
  mutate(CPC = ifelse(clicks > 0, acquisition_cost / clicks, NA))
kd %>%
  select(acquisition_cost, clicks, CPC) %>%
  head(10) %>%
  arrange(CPC) %>%
  kable(caption = "Chi phí trung bình cho mỗi clicks (CPC)",
      col.names = c("Acquisition Cost", "Clicks", "CPC"))
Chi phí trung bình cho mỗi clicks (CPC)
Acquisition Cost Clicks CPC
11067 817 13.54590
10200 584 17.46575
18066 861 20.98258
13280 624 21.28205
13766 642 21.44237
16174 506 31.96443
16452 379 43.40897
12724 217 58.63594
9716 100 97.16000
11566 116 99.70690
  • Giải thích kỹ thuật:
    • 2: Nếu số lần nhấp chuột > 0 thì sẽ tính CPC, còn = 0 thì sẽ gán giá trị NA.
    • 4: Chọn cột acquisition_cost, clicks và CPC để hiển thị.
    • 5: Lấy 10 dòng đầu tiên để trình bày.
    • 6: Sắp xếp bảng theo CPC tăng dần.
    • 7: Hiển thị bảng kết quả.
  • Kết quả:
    • Chi phí trung bình cho mỗi nhấp chuột (CPC)($/Click) biến động rất lớn, từ khoảng 13,5 đến gần 100.
    • Những chiến dịch có nhiều nhấp chuột thường có CPC thấp hơn (ví dụ 817 clicks sẽ có CPC 13,55), ngược lại chiến dịch ít nhấp chuột có CPC rất cao (100 clicks có CPC 97,16).
    • Điều này cho thấy hiệu quả chi phí tăng khi quảng cáo thu hút được nhiều nhấp chuột, đồng thời các chiến dịch ít hấp dẫn hoặc tiếp cận đối tượng hẹp sẽ tốn kém hơn cho mỗi nhấp chuột.
    • CPC cao kết hợp với CTR thấp cảnh báo quảng cáo kém hiệu quả về mặt chi phí.
    • CPC càng thấp, chiến dịch càng tiết kiệm chi phí.

1.2.9 Chi phí cho mỗi 1000 lần hiển thị quảng cáo (CPM)

\[CPM = \frac{acquisition\_cost}{impressions} \times 1000\]

kd <- kd %>%
  mutate(CPM = ifelse(impressions > 0,(acquisition_cost / impressions) * 1000, NA)) 
kd %>%
  select(acquisition_cost, impressions, CPM) %>%
  head(10) %>%
  arrange(CPM) %>%
  kable(caption = "Chi phí cho mỗi 1000 lần hiển thị quảng cáo (CPM)",
      col.names = c("Acquisition Cost", "Impressions", "CPM"))
Chi phí cho mỗi 1000 lần hiển thị quảng cáo (CPM)
Acquisition Cost Impressions CPM
11067 8749 1264.945
10200 7698 1325.019
11566 7523 1537.419
13280 7854 1690.858
13766 3856 3570.021
16452 4201 3916.210
9716 1643 5913.573
12724 1820 6991.209
16174 1922 8415.193
18066 1754 10299.886
  • Giải thích kỹ thuật:
    • 2: Nếu số lần hiển thị > 0 thì sẽ tính CPM, còn = 0 thì sẽ gán giá trị NA.
    • 4: Chọn cột acquisition_cost, impressions và CPM để hiển thị.
    • 5: Lấy 10 dòng đầu tiên để trình bày.
    • 6: Sắp xếp bảng theo CPM tăng dần.
    • 7: Hiển thị bảng kết quả.
  • Kết quả:
    • Chi phí cho mỗi 1000 lần hiển thị (CPM)($) tăng mạnh khi số lượt hiển thị thấp.
    • Các chiến dịch có nhiều lượt hiển thị (7500 – 8700) có CPM thấp, khoảng 1200 – 1500.
    • Chiến dịch ít lượt hiển thị (1600 – 1900) dẫn đến CPM rất cao, lên tới hơn 10000.
    • Điều này cho thấy quảng cáo tiếp cận được nhiều người giúp giảm chi phí trên mỗi 1000 hiển thị, còn quảng cáo ít hiển thị sẽ tốn kém hơn để tiếp cận cùng lượng khán giả.
    • CPM thường dùng để đánh giá hiệu quả hiển thị trên Google Ads, Facebook Ads, ..

1.2.10 Mức độ tương tác trung bình trên mỗi click.

\[Engagement\_Rate = \frac{engagement\_score}{clicks}\]

kd <- kd %>%
  mutate(Engagement_Rate = ifelse(clicks > 0, engagement_score / clicks, NA))
kd %>%
  select(engagement_score, clicks, Engagement_Rate) %>%
  head(10) %>%
  arrange(Engagement_Rate) %>%
  kable(caption = "Mức độ tương tác trung bình trên mỗi click (Engagement Rate)",
      col.names = c("Engagement Score", "Clicks", "Engagement Rate"))
Mức độ tương tác trung bình trên mỗi click (Engagement Rate)
Engagement Score Clicks Engagement Rate
1 584 0.0017123
3 642 0.0046729
6 861 0.0069686
3 379 0.0079156
1 100 0.0100000
7 624 0.0112179
6 506 0.0118577
10 817 0.0122399
7 217 0.0322581
7 116 0.0603448
  • Giải thích kỹ thuật:
    • 2: Nếu số lần nhấp chuột > 0 thì sẽ tính Engagement Rate, còn = 0 thì sẽ gán giá trị NA.
    • 4: Chọn cột engagement_score, clicks và Engagement_Rate để hiển thị.
    • 5: Lấy 10 dòng đầu tiên để trình bày.
    • 6: Sắp xếp bảng theo Engagement Rate tăng dần.
    • 7: Hiển thị bảng kết quả.
  • Kết quả:
    • Mức độ tương tác trung bình trên mỗi nhấp chuột (Engagement Rate) biến động lớn giữa các chiến dịch.
    • Các chiến dịch có nhiều nhấp chuột thường có Engagement Rate thấp, trong khi chiến dịch ít nhấp chuột lại có Engagement Rate cao.
    • Điều này cho thấy số lượng nhấp chuột không đồng nghĩa với tương tác cao, chiến dịch nhỏ nhưng phù hợp hoặc hấp dẫn có thể tạo ra tỷ lệ tương tác trên mỗi lần nhấp chuột lớn hơn.
    • Chiến dịch với Engagement Rate cao cho thấy quảng cáo gây chú ý và thúc đẩy hành vi tương tác sâu hơn từ người dùng.

1.2.11 Phân loại mức hiệu quả đầu tư.

q2 <- quantile(kd$roi, probs = c(0.25, 0.75), na.rm = TRUE)
kd <- kd %>% mutate(
    roi_level = cut(
      roi,
      breaks = c(-Inf, q2[1], q2[2], Inf),
      labels = c("Thấp", "Trung bình", "Cao"),
      right = TRUE ))
kd %>%
  count(roi_level) %>%
  arrange(factor(roi_level, levels = c("Thấp", "Trung bình", "Cao"))) %>%
  kable(caption = "Phân loại mức hiệu quả đầu tư theo phân vị",
      col.names = c("ROI_Level", "Số lượng quan sát"))
Phân loại mức hiệu quả đầu tư theo phân vị
ROI_Level Số lượng quan sát
Thấp 50259
Trung bình 100047
Cao 49694
  • Giải thích kỹ thuật:
    • 1: Tính phân vị 25% và 75% của biến roi để xác định ngưỡng phân loại.
    • 2 - 7: Tạo biến mới roi_level bằng cách phân loại mức hiệu quả đầu tư dựa trên phân vị đã tính.
    • 8 - 12: Đếm số lượng quan sát trong mỗi nhóm, sắp xếp và hiển thị bảng.
  • Kết quả:
    • Mức hiệu quả đầu tư (ROI) được phân loại theo phân vị thành ba nhóm: Thấp, Trung bình, Cao.
    • Số lượng quan sát ở mức Trung bình chiếm nhiều nhất (100047), gấp khoảng 2 lần so với mỗi nhóm Thấp (50259) và Cao (49694).
    • Điều này cho thấy phần lớn chiến dịch nằm ở mức hiệu quả trung bình, chỉ một số ít đạt hiệu quả rất cao hoặc rất thấp.
    • Phân loại giúp xác định chiến dịch cần tối ưu hoặc giữ nguyên chiến lược dựa trên ROI.

1.2.12 Phân loại sức hấp dẫn của quảng cáo

q3 <- quantile(kd$engagement_score, probs = c(0.25, 0.75), na.rm = TRUE)
kd <- kd %>% mutate(
    ad_attractiveness = cut(
      engagement_score,
      breaks = c(-Inf, q3[1], q3[2], Inf),
      labels = c("Kém hấp dẫn", "Hấp dẫn vừa phải", "Rất hấp dẫn"),
      right = TRUE ))
kd %>%
  count(ad_attractiveness) %>%
  arrange(factor(ad_attractiveness, 
                 levels = c("Kém hấp dẫn", "Hấp dẫn vừa phải", "Rất hấp dẫn"))) %>%
  kable(caption = "Phân loại sức hấp dẫn của quảng cáo theo phân vị",
      col.names = c("Ad Attractiveness", "Số lượng quan sát"))
Phân loại sức hấp dẫn của quảng cáo theo phân vị
Ad Attractiveness Số lượng quan sát
Kém hấp dẫn 60087
Hấp dẫn vừa phải 99923
Rất hấp dẫn 39990
  • Giải thích kỹ thuật:
    • 1: Tính phân vị 25% và 75%, bỏ qua giá trị bị thiếu.
    • 2 - 7: chia engagement_score thành 3 nhóm và gán nhãn tương ứng.
    • 8 - 13: Đếm số lượng quan sát trong mỗi nhóm, sắp xếp và hiển thị bảng.
  • Kết quả:
    • Sức hấp dẫn của quảng cáo được phân loại theo phân vị thành ba nhóm: Kém hấp dẫn, Hấp dẫn vừa phải, Rất hấp dẫn
    • Phần lớn quảng cáo thuộc nhóm Hấp dẫn vừa phải (99923), chiếm gần gấp đôi so với nhóm Kém hấp dẫn (60087) và gấp khoảng 2,5 lần nhóm Rất hấp dẫn (39990).
    • Chỉ một số ít quảng cáo đạt mức Rất hấp dẫn, trong khi nhiều quảng cáo vẫn còn ở mức Kém hấp dẫn, cho thấy hệ thống quảng cáo đang hoạt động ổn định nhưng chưa tối ưu hóa tiềm năng cao nhất.
    • Phân loại này giúp tập trung tối ưu các chiến dịch kém hấp dẫn để nâng cao hiệu quả tổng thể.

1.3 Phân tích dữ liệu và trực quan hóa dữ liệu

1.3.1 Thiết lâp mặc định cho biểu đồ

theme_set(theme_minimal(base_family = "Times New Roman"))
font_add_google("Roboto", "roboto")
showtext_auto()
theme_custom <- function() {
  theme_minimal(
    base_size = 14,  
    base_family = "Times New Roman") +
    theme(   
      plot.title = element_text(face = "bold", size = 16, 
                                hjust = 0.5, color = "#000"), 
      strip.text = element_text(face = "bold", size = 10),
      axis.title = element_text(size = 10, face = "bold"),
      axis.text = element_text(size = 10),
      legend.title = element_text(size = 12, face = "bold"),
      legend.text = element_text(size = 10),
      panel.grid.minor = element_blank(),
      panel.grid.major.x = element_blank())}
  • Giải thích kỹ thuật:
    • 1: Function(): Khai báo một hàm ẩn.
    • 2: Hàm này ấp dụng một chủ đề tối giản cho biểu đồ. Loại bỏ nền xám, đường kẻ ngang, đường kẻ dọc, …
    • 3: Cỡ chữ mặc định cho toàn biểu đồ.
    • 4: Kiểu chữ Times New Roman cho toàn biểu đồ.
    • 6: Tiêu đề biểu đồ: in đậm, cỡ chữ 16, căn giữa và chữ màu đen.
    • element_text(): Dùng để định dạng các thành phần văn bản khác nhau trong biểu đồ.
    • 8: Chữ in đậm, cỡ chữ 10 cho nhãn trục (tiêu đề phụ của biểu đồ).
    • 9: Tiêu đề trục: cỡ chữ 10, in đậm.
    • 10: Chữ trên trục: cỡ chữ 10.
    • 11: Tiêu đề chú giải: cỡ chữ 12, in đậm.
    • 12: Chữ trong chú giải: cỡ chữ 10.
    • 13: Loại bỏ các đường lưới nhỏ trong biểu đồ.
    • 14: Loại bỏ các đường lưới dọc lớn trong biểu đồ.
  • Kết quả: Tạo ra một chủ đề biểu đồ tùy chỉnh giúp thao tác gọn gàng hơn cho các biểu đồ sau.

1.3.2 Tần suất các biến đã được mã hóa

bang_tan_suat <- list(
  "Engagement Level" = kd %>% count(engagement_level) %>%   
    mutate(Variable = "Mức độ tương tác", Category = engagement_level) %>%  
    select(Variable, Category, n) %>%  
    arrange(factor(Category, levels = c("Thấp", "Trung bình", "Cao"))),   
  "Age Group" = kd %>% count(age_group) %>%
    mutate(Variable = "Nhóm tuổi", Category = age_group) %>%
    select(Variable, Category, n) %>%
    arrange(factor(Category, levels = c("18-24", "25-34", "35-44", "All"))),
  "Duration Level" = kd %>% count(duration_level) %>%
    mutate(Variable = "Thời lượng chiến dịch", Category = duration_level) %>%
    select(Variable, Category, n) %>%
    arrange(factor(Category, levels = c("Rất ngắn", "Ngắn", "Trung bình", "Dài"))),
  "Clicks Level" = kd %>% count(clicks_level) %>%
    mutate(Variable = "Số lần nhấp chuột", Category = clicks_level) %>%
    select(Variable, Category, n) %>%
    arrange(factor(Category, levels = c("Thấp", "Trung bình", "Cao"))),
  "Language Level" = kd %>% count(Language_Level) %>%
    mutate(Variable = "Mức độ phổ biến ngôn ngữ", Category = Language_Level) %>%
    select(Variable, Category, n) %>%
    arrange(factor(Category, levels = c("Ít phổ biến", 
                                        "Phổ biến trung bình", "Phổ biến cao"))),
  "ROI Level" = kd %>% count(roi_level) %>%
    mutate(Variable = "Mức hiệu quả đầu tư", Category = roi_level) %>%
    select(Variable, Category, n) %>%
    arrange(factor(Category, levels = c("Thấp", "Trung bình", "Cao"))),
  "Ad Attractiveness" = kd %>% count(ad_attractiveness) %>%
    mutate(Variable = "Sức hấp dẫn quảng cáo", Category = ad_attractiveness) %>%
    select(Variable, Category, n) %>%
    arrange(factor(Category, levels = c("Kém hấp dẫn", 
                                     "Hấp dẫn vừa phải", "Rất hấp dẫn"))))
combined_df <- bind_rows(bang_tan_suat) %>%
  select(Variable, Category, n) %>%
  group_by(Variable) %>%
  mutate(Percentage = round(100 * n / sum(n), 2)) %>% 
  ungroup()
kableExtra::kable(combined_df, format = "latex",
  caption = "Tần suất các biến đã được mã hóa",
  col.names = c("Biến", "Mức phân loại", "Số lượng quan sát", "Tỷ lệ (%)")) %>%
  kable_styling(
      latex_options = c("hold_position", "scale_down"),
      position = "center" )
  • Giải thích kỹ thuật:
    • 1: Tạo danh sách để lưu trữ các bảng tần suất cho từng biến đã mã hóa.
    • 2; 6; 10; 14; 18; 23; 27: Đếm số lượng quan sát trong mỗi nhóm của biến đã mã hóa.
    • 3; 7; 11; 15; 19; 24; 28: Tạo cột Variable để ghi tên biến, tạo cột Category để gán các cột dữ liệu cần phân loại.
    • 4; 8; 12; 16; 20; 25; 29: Sắp xếp theo thứ tự mong muốn.
    • 5; 9; 13; 17; 21; 26; 30: Chọn các cột cần trình bày.
    • 32 - 39: Sử dụng kable để tạo bảng tần suất và áp dụng định dạng cho bảng.
      • 35: Tạo bảng đẹp
      • 37: Bảng chiếm toàn bộ chiều rộng trang.
      • 38: Căn giữa bảng.
      • 39: Áp dụng các tùy chọn LaTeX để làm bảng đẹp
  • Kết quả:
    • Các chiến dịch marketing ổn định với hiệu suất (ROI, Clicks) tập trung ở mức Trung bình (50%).
    • Chiến lược nhắm tới mục tiêu cho thấy sự ưu tiên rõ ràng cho nhóm tuổi 25-34.
    • Chiến lược đa dạng hóa thị trường ngôn ngữ bằng cách tập trung vào các ngôn ngữít phổ biến và phổ biến trung bình.
    • Bộ dữ liệu phản ánh một chiến lược marketing tổng thể có tính ổn định cao và quản lý rủi ro tốt, ưu tiên sự cân bằng và tập trung nguồn lực chiến lược.

1.3.3 Biểu đồ tần suất các biến

bieu_do_tan_suat <- list(
  ggplot(kd, aes(x = engagement_level, fill = engagement_level)) +
    geom_bar() +   
    geom_text(stat = "count", aes(label = ..count..), vjust = 1.5,  
              color = "black", size = 4) +  
    labs(title = "Mức độ tương tác", x = "Engagement Level", y = "Số lượng") +
    theme_custom() + 
    theme(legend.position = "none"), 
  ggplot(kd, aes(x = age_group, fill = age_group)) + 
    geom_bar() +
    geom_text(stat = "count", aes(label = ..count..), vjust = 1.5, 
              color = "black", size = 4) +
    labs(title = "Nhóm tuổi", x = "Age Group", y = "Số lượng") +
    theme_custom() +
    theme(legend.position = "none"),
  ggplot(kd, aes(x = duration_level, fill = duration_level)) +
    geom_bar() +
    geom_text(stat = "count", aes(label = ..count..), vjust = 1.5, 
              color = "black", size = 4) +
    labs(title = "Cấp độ thời lượng chiến dịch", 
         x = "Duration Level", y = "Số lượng") +
    theme_custom() +
    theme(legend.position = "none",
          axis.text.x = element_text(angle = 0, hjust = 0.5, size = 8)),
  ggplot(kd, aes(x = clicks_level, fill = clicks_level)) +
    geom_bar() +
    geom_text(stat = "count", aes(label = ..count..), vjust = 1.5, 
              color = "black", size = 4) +
    labs(title = "Cấp độ số lần nhấp chuột", x = "Clicks Level", y = "Số lượng") +
    theme_custom() +
    theme(legend.position = "none"))
(bieu_do_ket_hop <- wrap_plots(bieu_do_tan_suat, ncol = 2) +
  plot_annotation(  
    title = "Biểu đồ tần suất các biến đã được mã hóa",
    theme = theme(plot.title = element_text(size = 18, face = "bold",   
                                hjust = 0.5, family = "Times New Roman") )))

  • Giải thích kỹ thuật:
    • 1: tạo danh sách cho các biểu đồ tần suất.
    • 2; 9; 16; 24: Tạo biểu đồ cột cho từng biến đã mã hóa.
    • 3; 10; 17; 25: vẽ biều đồ cột đếm số lượng từng nhóm.
    • 4; 11; 18; 26: Thêm nhãn số lượng lên trên mỗi cột.
      • vjust = 1.5: điều chỉnh số ghi chú vào trong cột.
      • color = “black”: màu chữ đen
      • size = 4: cỡ chữ 4
    • 6; 13; 20; 28: Thêm tiêu đề và nhãn trục cho biểu đồ.
    • 7; 14; 22; 29: Áp dụng chủ đề đã thiết lập trước đó.
    • 8; 15; 23; 30: Ẩn chú thích để biểu đồ gọn gàng hơn.
    • 31: wrap_plots(): Kết hợp các biểu đồ thành một bố cục 2x2.
    • 32: Thêm tiêu đề tổng cho biểu đồ gộp.
    • 34 - 35: Tùy chỉnh cỡ chữ, kiểu chữ, in đậm và acwn giữa cho tiêu đề tổng.
  • Kết quả biểu đồ:
    • Mức độ tương tác: Phần lớn người dùng được phân vào nhóm mức độ tương tác trung bình.
    • Tập trung mạnh vào nhóm tuổi 25-34.
    • Thời lượng chiến dịch được phân bổ khá đồng đều giữa các mức.
    • Số lần nhấp chuột: Chiến dịch rơi vào cấp độ nhấp chuột Trung bình.
  • Mức độ Tương tác, Nhóm Tuổi, và Cấp độ Số lần Nhấp chuột có sự mất cân bằng đáng kể về tần suất giữa các cấp độ, với cấp độ Trung bình hoặc nhóm 25-34 tuổi chiếm ưu thế.
  • Cấp độ Thời lượng Chiến dịch lại rất cân bằng về số lượng giữa các cấp độ.
bieu_do_tan_suat1 <- list(
  ggplot(kd, aes(x = Language_Level, fill = Language_Level)) +
    geom_bar() +
    geom_text(stat = "count", aes(label = ..count..), vjust = 1.5, 
              color = "black", size = 4) +
    labs(title = "Mức độ phổ biến ngôn ngữ", x = "Language Level", y = "Số lượng") +
    theme_custom() +
    theme(legend.position = "none",
          axis.text.x = element_text(angle = 0, hjust = 0.5, size = 6)),
  ggplot(kd, aes(x = roi_level, fill = roi_level)) +
    geom_bar() +
    geom_text(stat = "count", aes(label = ..count..), vjust = 1.5, 
              color = "black", size = 4) +
    labs(title = "Mức hiệu quả đầu tư", x = "ROI Level", y = "Số lượng") +
    theme_custom() +
    theme(legend.position = "none"),
  ggplot(kd, aes(x = ad_attractiveness, fill = ad_attractiveness)) +
    geom_bar()+
    geom_text(stat = "count", aes(label = ..count..), vjust = 1.5, 
              color = "black", size = 4) +
    labs(title = "Sức hấp dẫn quảng cáo", x = "Ad Attractiveness", y = "Số lượng") +
    theme_custom() +
    theme(legend.position = "none",
          axis.text.x = element_text(angle = 0, hjust = 0.5, size = 6)))
(bieu_do_ket_hop1 <- wrap_plots(bieu_do_tan_suat1, ncol = 2) +
  plot_annotation(
    title = "Biểu đồ tần suất các biến đã được mã hóa (tiếp)",
    theme = theme(plot.title = element_text(size = 18, face = "bold", 
                              hjust = 0.5, family = "Times New Roman"))))

  • Kết luận:
    • Phần lớn các chiến dịch đạt mức hiệu quả đầu tư (ROI) trung bình, phản ánh tính ổn định của hoạt động marketing.
    • Về sức hấp dẫn quảng cáo, đa số được đánh giá ở mức vừa phải, trong khi số chiến dịch có sức hút cao chiếm tỷ trọng nhỏ, cho thấy cần cải thiện nội dung sáng tạo.
    • Mức độ phổ biến ngôn ngữ có phân bố tương đối đối xứng giữa các nhóm ít phổ biến và phổ biến cao, thể hiện sự đa dạng trong cách tiếp cận đối tượng người dùng qua ngôn ngữ quảng cáo.

1.3.4 Phân tích ROI theo mức độ tương tác

kd_summary <- kd %>%
  group_by(engagement_level) %>%  
  summarise(Mean_ROI = mean(roi, na.rm = TRUE), N = n()) %>%
  arrange(desc(Mean_ROI))
kd_summary %>%
  kable(
    caption = "ROI trung bình theo mức độ tương tác",
    col.names = c("Mức độ tương tác", "ROI trung bình", "Số lượng quan sát"))
ROI trung bình theo mức độ tương tác
Mức độ tương tác ROI trung bình Số lượng quan sát
Trung bình 5.003452 99923
Cao 5.002360 39990
Thấp 5.000803 60087
  • Giải thích kỹ thuật:
    • 2: Nhóm dữ liệu theo mức độ tương tác.
    • 3: Tính ROI trung bình và số lượng quan sát trong mỗi nhóm, bỏ qua giá trị bị thiếu.
    • 4: Sắp xếp kết quả theo ROI trung bình giảm dần.
    • 5 - 8: Hiển thị bảng kết quả.
  • Kết quả:
    • ROI trung bình theo mức độ tương tác tương đối đều nhau:
      • Thấp: 5,0008
      • Trung bình: 5,0035
      • Cao: 5,0024
    • Số lượng quan sát lớn nhất thuộc mức Trung bình (99.923), tiếp theo là Thấp (60.087) và Cao (39.990).
    • ROI không thay đổi nhiều theo mức độ tương tác, cho thấy mức độ tương tác cao chưa hẳn mang lại lợi nhuận đầu tư vượt trội trong dữ liệu này.

1.3.5 Biểu đồ ROI theo mức độ tương tác

ggplot(kd_summary, aes(x = reorder(engagement_level, -Mean_ROI),y = Mean_ROI,
                       color = engagement_level)) + 
  geom_segment(aes(x = reorder(engagement_level, -Mean_ROI), 
                   xend = reorder(engagement_level,-Mean_ROI),y = 0, yend = Mean_ROI)) +
 geom_point(size = 5) +
 geom_text(aes(label = sprintf("%.4f", Mean_ROI)),vjust = -1,size = 4, color="black") +
 labs(title = "ROI trung bình theo mức độ tương tác",
      x = "Mức độ tương tác",
      y = "ROI trung bình") +
  theme_custom() +
  theme(plot.title = element_text(face = "bold", size = 16, hjust = 0.5),
        legend.position = "none") + scale_y_continuous(limits = c(0, 7))

  • Giải thích kỹ thuật:
    • 1: Tạo biểu đồ điểm thể hiện ROI trung bình theo mức độ tương tác.
    • 2: Sắp xếp trục x theo ROI trung bình giảm dần.
    • 3: Vẽ các đoạn thẳng từ trục x đến điểm ROI trung bình.
    • 4: Vẽ các điểm đại diện cho ROI trung bình.
    • 5: Thêm nhãn giá trị ROI lên trên mỗi điểm.
    • 6: Thêm tiêu đề và nhãn trục cho biểu đồ.
    • 7: Áp dụng chủ đề tùy chỉnh đã thiết lập trước đó.
    • 8: Tùy chỉnh cỡ chữ, kiểu chữ, in đậm và căn giữa cho tiêu đề biểu đồ.
    • 9: Loại bỏ chú giải vì không cần thiết.
    • 10: Giới hạn trục y từ 0 đến 7 để hiển thị rõ ràng hơn.
  • Kết quả:
    • Các chiến dịch đang hoạt động với một mức hiệu quả cơ bản rất ổn định, với ROI trung bình luôn dao động quanh mốc 5.0. Mức độ tương tác mà chiến dịch tạo ra, dù là cao hay thấp, cũng không làm thay đổi đáng kể kết quả tài chính này.
    • Thay vì tập trung vào việc đẩy mức tương tác lên cao, doanh nghiệp nên tối ưu hóa các yếu tố khác có thể tạo ra sự khác biệt lớn hơn cho ROI,hoặc chấp nhận rằng mức tương tác Trung bình hoặc Thấp đã là mức hiệu quả tối ưu cho các chiến dịch hiện tại.

1.3.6 CPC theo nhóm tuổi

kd_age <- kd %>%
  group_by(age_group) %>%
  summarise(Mean_CPC = mean(CPC, na.rm = TRUE), N = n()) %>%
  arrange(Mean_CPC) 
kd_age %>%
  kable(
    caption = "CPC trung bình theo nhóm tuổi",
    col.names = c("Nhóm tuổi", "CPC trung bình", "Số lượng quan sát"))
CPC trung bình theo nhóm tuổi
Nhóm tuổi CPC trung bình Số lượng quan sát
25-34 31.90798 80036
35-44 31.97152 39687
18-24 32.05411 40258
All 32.20028 40019
  • Giải thích kỹ thuật:
    • 2: Nhóm dữ liệu theo nhóm tuổi.
    • 3: Tính CPC trung bình và số lượng quan sát trong mỗi nhóm, bỏ qua giá trị bị thiếu.
    • 4: Sắp xếp kết quả theo CPC trung bình tăng dần.
    • 5 - 8: Hiển thị bảng kết quả với tiêu đề và tên cột.
  • Kết quả:
  • CPC trung bình theo nhóm tuổi khá đồng đều, từ 31,91 đến 32,20.
    • 25–34: 31,91 (nhiều nhất, 80.036 quan sát)
    • 35–44: 31,97 (39.687 quan sát)
    • 18–24: 32,05 (40.258 quan sát)
    • All: 32,20 (40.019 quan sát)
  • Chi phí trên mỗi nhấp chuột không thay đổi nhiều giữa các nhóm tuổi, cho thấy độ tuổi không ảnh hưởng đáng kể đến CPC.

1.3.7 Biểu đồ CPC theo nhóm tuổi

ggplot(kd_age, aes(x = Mean_CPC, y = reorder(age_group, Mean_CPC), fill = age_group)) + 
  geom_col(width = 0.6) + 
  geom_text(aes(label = sprintf("%.4f", Mean_CPC, 2)), 
            hjust = 1.2, size = 5, color="#000") + 
  labs(title = "CPC trung bình theo nhóm tuổi", x = "CPC trung bình", y = "Nhóm tuổi") +   
  theme_custom() +  
  theme(plot.title = element_text(face = "bold", size = 16, hjust = 0.5),
        legend.position = "none")

  • Giải thích kỹ thuật:
    • 1: Tạo biểu đồ cột thể hiện CPC trung bình theo nhóm tuổi.
    • 2: Vẽ các cột với chiều rộng 0.6.
    • 3: Thêm nhãn giá trị CPC lên bên trong mỗi cột.
      • label = sprintf(“%.4f”, Mean_CPC, 2): định dạng nhãn với 4 chữ số thập phân.
      • hjust = 1.2: điều chỉnh vị trí ngang của nhãn.
      • size = 5: cỡ chữ 5.
      • color = “#000”: màu chữ đen.
    • 5: Thêm tiêu đề và nhãn trục cho biểu đồ.
    • 6: Áp dụng chủ đề tùy chỉnh đã thiết lập trước đó.
    • 7: Tùy chỉnh cỡ chữ, kiểu chữ, in đậm và căn giữa cho tiêu đề biểu đồ.
    • 8: Loại bỏ chú giải vì không cần thiết.
  • Kết quả:
    • Nhóm tuổi không tạo ra sự khác biệt lớn về chi phí quảng cáo trên mỗi lượt nhấp. Chi phí để thu hút một lượt nhấp từ người dùng ở các độ tuổi 18-24, 25-34, hay 35-44 là gần như tương đương nhau. Điều này phản ánh một sự ổn định về một thị trường có tính cạnh tranh đồng đều khi nhắm mục tiêu đến các nhóm nhân khẩu học này.
    • CPC trung bình dao động trong khoảng hẹp từ 31,91 đến 32,20, cho thấy các chiến dịch quảng cáo không gặp phải sự biến động lớn về chi phí dựa trên độ tuổi của đối tượng mục tiêu.
    • Do đó, các doanh nghiệp tập trung vào việc tối ưu hóa nội dung và chiến lược tiếp cận thay vì lo lắng về sự khác biệt chi phí dựa trên nhóm tuổi.

1.3.8 CTR theo cấp độ thời lượng chiến dịch

kd_cd <- kd %>%
  group_by(duration_level) %>%
  summarise( Mean_CTR = mean(CTR, na.rm = TRUE),N = n()) %>%
  arrange(factor(duration_level, levels = c("Rất ngắn", "Ngắn", "Trung bình", "Dài"))) 
kd_cd %>%
  kable(
    caption = "CTR trung bình theo cấp độ thời lượng chiến dịch",
    col.names = c("Cấp độ thời lượng chiến dịch", "CTR trung bình", "Số lượng quan sát"))
CTR trung bình theo cấp độ thời lượng chiến dịch
Cấp độ thời lượng chiến dịch CTR trung bình Số lượng quan sát
Rất ngắn 0.1394716 49779
Ngắn 0.1408740 50255
Trung bình 0.1403701 50100
Dài 0.1409012 49866
  • Giải thích kỹ thuật:
    • 2: Nhóm dữ liệu theo cấp độ thời lượng chiến dịch.
    • 3: Tính CTR trung bình và số lượng quan sát trong mỗi nhóm, bỏ qua giá trị bị thiếu.
    • 4: Sắp xếp kết quả theo thứ tự mong muốn của cấp độ thời lượng.
    • 5 - 8: Hiển thị bảng kết quả với tiêu đề và tên cột.
  • Kết quả:
    • CTR trung bình theo cấp độ thời lượng chiến dịch gần như không đổi, dao động từ 0,1395 đến 0,1409.
      • Rất ngắn: 0,1395 (49.779 quan sát)
      • Ngắn: 0,1409 (50.255 quan sát)
      • Trung bình: 0,1404 (50.100 quan sát)
      • Dài: 0,1409 (49.866 quan sát)
    • Thời lượng chiến dịch hầu như không ảnh hưởng đến CTR trung bình, cho thấy hiệu quả nhấp chuột ổn định bất kể chiến dịch ngắn hay dài.

1.3.9 Biểu đồ CTR theo cấp độ thời lượng chiến dịch

ggplot(kd_cd, aes(x = duration_level, 
                   y = Mean_CTR, 
                   size = Mean_CTR,       
                   color = duration_level)) + 
  geom_point() + 
  geom_text(aes(label = sprintf("%.5f", Mean_CTR)),
            vjust = -1.5, size = 5, color = "#000000") + 
 labs(title = "CTR trung bình theo mức độ thời lượng",
      x = "Mức độ thời lượng",
      y = "CTR trung bình", size = "Giá trị CTR", color = "Thời lượng") +  
 theme_custom() +
 theme(plot.title = element_text(face = "bold", size = 16, hjust = 0.5)) +
         scale_y_continuous(limits = c(0.1390, 0.1415))

  • Giải thích kỹ thuật:
    • 1: Tạo biểu đồ điểm thể hiện CTR trung bình theo cấp độ thời lượng chiến dịch.
    • 5: Vẽ các điểm đại diện cho CTR trung bình.
    • 6: Thêm nhãn giá trị CTR lên trên mỗi điểm.
      • label = sprintf(“%.5f”, Mean_CTR): định dạng nhãn với 5 chữ số thập phân.
      • vjust = -1.5: điều chỉnh vị trí dọc của nhãn.
      • size = 5: cỡ chữ 5.
      • color = “#000000”: màu chữ đen.
    • 8: Thêm tiêu đề và nhãn trục cho biểu đồ.
    • 11: Áp dụng chủ đề tùy chỉnh đã thiết lập trước đó.
    • 12: Tùy chỉnh cỡ chữ, kiểu chữ, in đậm và căn giữa cho tiêu đề biểu đồ.
    • 13: Giới hạn trục y từ 0.13900 đến 0.14155 để hiển thị rõ ràng hơn.
  • Kết quả:
    • Dữ liệu chỉ ra rằng cả thời lượng “Dài” và “Ngắn” đều là những lựa chọn hiệu quả nhất để tối ưu hóa tỷ lệ nhấp chuột. Trong khi đó, việc sử dụng thời lượng “Rất ngắn” dường như là chiến lược kém hiệu quả nhất, không thu hút được sự tương tác của người xem bằng các nhóm thời lượng khác.
    • Mặc dù có sự khác biệt về mặt thống kê nhỏ, sự chênh lệch này không đủ lớn để thay đổi triệt để chiến lược kinh doanh. Do đó, các nhà tiếp thị nên cân nhắc kỹ lưỡng khi lựa chọn thời lượng chiến dịch dựa trên mục tiêu cụ thể và đối tượng mục tiêu của họ.
    • Thời lượng Dài thường phù hợp với chiến dịch duy trì thương hiệu, trong khi Ngắn hoặc Trung bình có thể phù hợp với chiến dịch khuyến mãi theo sự kiện.
    • Mục tiêu là tối đa hóa lưu lượng truy cập với chi phí thấp nhất, nên ưu tiên thời lượng Dài hoặc Ngắn vì chúng có CTR nhỉnh hơn.

1.3.10 Phân tích CPC theo mức độ phổ biến ngôn ngữo

kd_la <- kd %>%
  group_by(Language_Level) %>%
  summarise(Mean_CPC = mean(CPC, na.rm = TRUE),N = n()) %>%
  arrange(Mean_CPC) 
kd_la %>%
  kable(
    caption = "CPC trung bình theo mức độ phổ biến ngôn ngữ",
    col.names = c("Mức độ phổ biến ngôn ngữ", "CPC trung bình", "Số lượng quan sát"))
CPC trung bình theo mức độ phổ biến ngôn ngữ
Mức độ phổ biến ngôn ngữ CPC trung bình Số lượng quan sát
Ít phổ biến 31.86422 79660
Phổ biến cao 32.07894 40255
Phổ biến trung bình 32.11659 80085
  • Giải thích kỹ thuật:
    • 2: Nhóm dữ liệu theo mức độ phổ biến ngôn ngữ.
    • 3: Tính CPC trung bình và số lượng quan sát trong mỗi nhóm, bỏ qua giá trị bị thiếu.
    • 4: Sắp xếp kết quả theo CPC trung bình tăng dần.
    • 5 - 8: Hiển thị bảng kết quả.
  • Kết quả:
    • CPC trung bình theo mức độ phổ biến ngôn ngữ tương đối đồng đều, dao động từ 31,86 đến 32,12.
      • Ít phổ biến: 31,86 (79.660 quan sát)
      • Phổ biến cao: 32,08 (40.255 quan sát)
      • Phổ biến trung bình: 32,12 (80.085 quan sát)
    • Mức độ phổ biến ngôn ngữ không ảnh hưởng đáng kể đến chi phí trên mỗi nhấp chuột, CPC duy trì ổn định giữa các nhóm.

1.3.11 Biểu đồ CPC theo mức độ phổ biến ngôn ngữ

ggplot(kd_la, aes(x = 1, y = Mean_CPC, fill = Language_Level)) +
  geom_bar(stat = "identity", width = 1, color = "white") +
  coord_polar("y", start = 0) +  xlim(c(0, 1.5)) +
  geom_text(aes(label = paste0(round(Mean_CPC / sum(Mean_CPC) * 100, 1), "%")),
            position = position_stack(vjust = 0.5),
            size = 5, color = "black") +
  labs(
    title = "Tỷ trọng CPC trung bình theo mức độ phổ biến ngôn ngữ",
    fill = "Trình độ ngôn ngữ"
  ) +
  theme_void(base_family = "Times New Roman") +
  scale_fill_brewer(palette = "Set2") +
  theme(
    plot.title = element_text(face = "bold", size = 16, hjust = 0),
    legend.title = element_text(size = 13, face = "bold"),
    legend.text = element_text(size = 12))

  • Giải thích kỹ thuật:
    • 1: Tạo biểu đồ tròn thể hiện tỷ trọng CPC trung bình theo mức độ phổ biến ngôn ngữ.
    • 2: Vẽ các phần của biểu đồ tròn với màu sắc khác nhau cho từng mức độ phổ biến ngôn ngữ.
    • 3: Chuyển đổi biểu đồ cột thành biểu đồ tròn bằng cách sử dụng tọa độ cực.
    • 3: xlim() Giới hạn trục x để tạo khoảng trống bên phải biểu đồ tròn.
    • 4: Thêm nhãn tỷ lệ phần trăm lên mỗi phần của biểu đồ tròn.
    • 4: label = paste0(round(Mean_CPC / sum(Mean_CPC) * 100, 1), “%”): tính toán và định dạng nhãn tỷ lệ phần trăm.
      • position = position_stack(vjust = 0.5): đặt nhãn ở giữa mỗi phần.
      • size = 5: cỡ chữ 5.
      • color = “black”: màu chữ đen.
    • 7: Thêm tiêu đề và chú giải cho biểu đồ.
    • 11:Loại bỏ toàn bộ nền, trục, lưới chỉ giữ biểu đồ và nhãn.
    • 12: Sử dụng bảng màu Set2 để tạo màu sắc hài hòa cho các phần của biểu đồ bánh.
    • 13 - 16: Làm nổi bật tiêu đề và chú thích bằng cách tăng cỡ chữ và in đậm.
  • Kết quả:
    • Dữ liệu cho thấy không có sự thiên vị hay tập trung vào bất kỳ mức độ phổ biến ngôn ngữ nào. Thay vào đó, có một sự phân bố rất đồng đều. Điều này phản ánh một sự cân bằng hoặc đa dạng trong chiến lược tiếp cận đối tượng người dùng qua các nhóm ngôn ngữ khác nhau, không có nhóm nào chiếm ưu thế rõ rệt so với các nhóm còn lại.
    • CPC trung bình dao động trong khoảng hẹp từ 31,86 đến 32,12, cho thấy các chiến dịch quảng cáo không gặp phải sự biến động lớn về chi phí dựa trên mức độ phổ biến ngôn ngữ.

1.3.12 Mức hiểu quả theo kênh (Roi_level, channel_used)

roi_channel <- kd %>%
  filter(!is.na(roi_level) & !is.na(channel_used)) %>%    
  count(channel_used, roi_level) %>%  
  group_by(channel_used) %>%  
  mutate(percentage = n / sum(n)) %>% 
  ungroup()   
roi_channel %>%
  kable(
    caption = "Phân phối mức ROI theo Kênh",
    col.names = c("Kênh", "Mức ROI", "Số lượng", "Tỷ lệ"))
Phân phối mức ROI theo Kênh
Kênh Mức ROI Số lượng Tỷ lệ
Email Thấp 8529 0.2538468
Email Trung bình 16684 0.4965624
Email Cao 8386 0.2495908
Facebook Thấp 8226 0.2506475
Facebook Trung bình 16314 0.4970901
Facebook Cao 8279 0.2522624
Google Ads Thấp 8323 0.2489084
Google Ads Trung bình 16829 0.5032897
Google Ads Cao 8286 0.2478019
Instagram Thấp 8508 0.2547916
Instagram Trung bình 16632 0.4980834
Instagram Cao 8252 0.2471251
Website Thấp 8257 0.2475120
Website Trung bình 16875 0.5058453
Website Cao 8228 0.2466427
YouTube Thấp 8416 0.2520364
YouTube Trung bình 16713 0.5005091
YouTube Cao 8263 0.2474545
  • Giải thích kỹ thuật:
    • 2: Lọc bỏ các quan sát có giá trị thiếu trong roi_level và channel_used.
    • 3: Đếm số lượng quan sát cho mỗi kết hợp của channel_used và roi_level.
    • 4: Nhóm dữ liệu theo channel_used.
    • 5: Tính tỷ lệ phần trăm của mỗi mức roi_level trong từng kênh.
    • 6: Bỏ nhóm để chuẩn bị cho việc hiển thị bảng.
    • 7 - 10: Hiển thị bảng kết quả.
  • Kết quả:
    • Phân phối ROI theo kênh cho thấy tỷ lệ quan sát ở mỗi mức Thấp, Trung bình, Cao gần như đồng đều trong tất cả các kênh.
    • Cụ thể:
      • Mức Trung bình chiếm khoảng 50% số quan sát trên mỗi kênh.
      • Mức Thấp và Cao đều chiếm khoảng 25% số quan sát.
    • Không có kênh nào vượt trội về ROI, tất cả kênh đều có phân phối ROI tương đối cân bằng, nghĩa là hiệu quả đầu tư trung bình khá đồng đều giữa Email, Facebook, Google Ads, Instagram, Website và YouTube.

1.3.13 Biểu đồ mức hiệu quả đầu tư theo kênh

(p_roi_channel <- ggplot(roi_channel, 
                       aes(x = channel_used, y = percentage, fill = roi_level)) + 
  geom_col(position = "fill", width = 0.7) + 
  geom_text(aes(label = scales::percent(percentage, accuracy = 0.1)),
            position = position_fill(vjust = 0.5), size = 3.5, color = "black") +   
  scale_y_continuous(labels = scales::percent) +  
  scale_fill_brewer(palette = "RdYlGn", direction = 1) + 
  labs(
    title = "Tỷ lệ mức ROI theo Kênh sử dụng",
    x = "Kênh",
    y = "Tỷ lệ",
    fill = "Mức ROI" ) +  
  coord_flip() +    
  theme_custom())   

  • Giải thích kỹ thuật:
    • 1: Tạo biểu đồ cột thể hiện tỷ lệ mức ROI theo kênh sử dụng.
    • 3: Vẽ các cột với tỷ lệ phần trăm cho mỗi mức ROI trong từng kênh.
    • 4: Thêm nhãn tỷ lệ phần trăm lên mỗi phần của cột.
      • label = scales::percent(percentage, accuracy = 0.1): định dạng nhãn dưới dạng phần trăm với độ chính xác 0.1%.
      • position = position_fill(vjust = 0.5): đặt nhãn ở giữa mỗi phần.
      • size = 3.5: cỡ chữ 3.5.
      • color = “black”: màu chữ đen.
    • 6: Chuyển đổi trục y thành định dạng phần trăm.
    • 7: Sử dụng bảng màu RdYlGn để tạo màu sắc hài hòa cho các mức ROI.
    • 8: Thêm tiêu đề và nhãn trục cho biểu đồ.
    • 13: Xoay biểu đồ để hiển thị kênh trên trục y và tỷ lệ trên trục x.
    • 14: Áp dụng chủ đề tùy chỉnh đã thiết lập trước đó.
  • Kết quả:
    • không thể tìm kiếm lợi thế vượt trội bằng cách chuyển đổi ngân sách từ kênh này sang kênh khác, vì hiệu suất ROI trung bình là tương đương nhau.
    • Sự đồng đều này có thể phản ánh một chiến lược marketing tổng thể hiệu quả, hoặc có thể là dấu hiệu của một thị trường cạnh tranh cao, nơi mà không có kênh nào thực sự nổi bật hơn so với các kênh khác về mặt hiệu quả đầu tư.

1.3.14 Loại chiến dịch theo nhóm tuổi (campaign_type, age_group)

campaign_age<- kd %>%
  filter(!is.na(age_group) & !is.na(campaign_type)) %>%
  count(age_group, campaign_type) %>%
  group_by(age_group) %>%
  mutate(percentage = n / sum(n)) %>%
  ungroup()
campaign_age %>%
  kable(
    caption = "Phân phối Loại chiến dịch theo Nhóm tuổi",
    col.names = c("Nhóm tuổi", "Loại chiến dịch", "Số lượng", "Tỷ lệ"))
Phân phối Loại chiến dịch theo Nhóm tuổi
Nhóm tuổi Loại chiến dịch Số lượng Tỷ lệ
18-24 Display 8119 0.2016742
18-24 Email 8012 0.1990163
18-24 Influencer 8195 0.2035620
18-24 Search 7965 0.1978489
18-24 Social Media 7967 0.1978986
25-34 Display 15967 0.1994977
25-34 Email 15948 0.1992603
25-34 Influencer 15944 0.1992104
25-34 Search 16174 0.2020841
25-34 Social Media 16003 0.1999475
35-44 Display 7975 0.2009474
35-44 Email 8013 0.2019049
35-44 Influencer 7798 0.1964875
35-44 Search 7986 0.2012246
35-44 Social Media 7915 0.1994356
All Display 7926 0.1980559
All Email 7897 0.1973313
All Influencer 8232 0.2057023
All Search 8032 0.2007047
All Social Media 7932 0.1982059
  • Giải thích kỹ thuật:
    • 2: Lọc bỏ các quan sát có giá trị thiếu trong age_group và campaign_type.
    • 3: Đếm số lượng quan sát cho mỗi kết hợp của age_group và campaign_type.
    • 4: Nhóm dữ liệu theo age_group.
    • 5: Tính tỷ lệ phần trăm của mỗi loại chiến dịch trong từng nhóm tuổi.
    • 6: Bỏ nhóm để chuẩn bị cho việc hiển thị bảng.
    • 7 - 10: Hiển thị bảng kết quả với tiêu .
  • Kết quả:
    • Phân phối loại chiến dịch theo nhóm tuổi khá đồng đều trong từng nhóm tuổi:
      • Nhóm 18–24:
        • Display: 20,17%
        • Email: 19,90%
        • Influencer: 20,36%
        • Search: 19,78%
        • Social Media: 19,79%
      • Nhóm 25–34: Tất cả loại chiến dịch chiếm khoảng 19,9–20,2%, Search cao nhất (20,21%).
      • Nhóm 35–44: Chiếm tương tự, dao động 19,65–20,19%, Email cao nhất (20,19%).
      • Nhóm All:
        • Influencer cao nhất (20,57%), các loại khác khoảng 19,7–20,0%.
    • Mỗi nhóm tuổi nhận được phân bổ chiến dịch gần như đồng đều, không có loại chiến dịch nào chiếm ưu thế quá lớn, cho thấy chiến lược tiếp cận được phân bổ đều trên các kênh.

1.3.15 Biểu đồ loại chiến dịch theo nhóm tuổi

(p_camp_age <- ggplot(campaign_age, 
                     aes(x = age_group, y = percentage, fill = campaign_type)) +
  geom_col(position = "fill", width = 0.7) +
  geom_text(aes(label = scales::percent(percentage, accuracy = 0.1)),
            position = position_fill(vjust = 0.5), 
            size = 4, color = "black", fontface = "bold") +
  scale_y_continuous(labels = scales::percent) +
  scale_fill_brewer(palette = "Set2") +
  labs(
    title = "Phân phối Loại chiến dịch theo Nhóm tuổi",
    x = "Nhóm tuổi",
    y = "Tỷ lệ",
    fill = "Loại chiến dịch" )  +
  theme_custom())

  • Giải thích kỹ thuật:
    • 1: Tạo biểu đồ cột thể hiện tỷ lệ loại chiến dịch theo nhóm tuổi.
    • 3: Vẽ các cột với tỷ lệ phần trăm cho mỗi loại chiến dịch trong từng nhóm tuổi.
    • 4: Thêm nhãn tỷ lệ phần trăm lên mỗi phần của cột.
      • label = scales::percent(percentage, accuracy = 0.1): định dạng nhãn dưới dạng phần trăm với độ chính xác 0.1%.
      • position = position_fill(vjust = 0.5): đặt nhãn ở giữa mỗi phần.
      • size = 4: cỡ chữ 4.
      • color = “black”: màu chữ đen.
      • fontface = “bold”: in đậm chữ.
    • 7: Chuyển đổi trục y thành định dạng phần trăm.
    • 8: Sử dụng bảng màu Set2 để tạo màu sắc hài hòa cho các loại chiến dịch.
    • 9: Thêm tiêu đề và nhãn trục cho biểu đồ.
    • 14: Áp dụng chủ đề tùy chỉnh đã thiết lập trước đó.
  • Kết quả:
    • Một chiến lược marketing đa dạng hóa và cân bằng đang được áp dụng. Quan trọng hơn, chiến lược cân bằng này được triển khai đồng nhất cho mọi nhóm tuổi, thay vì tùy chỉnh (ví dụ: tập trung Social Media cho nhóm trẻ hoặc Email cho nhóm lớn tuổi). Mọi nhóm tuổi đều nhận được một “hỗn hợp” (mix) các loại chiến dịch gần như giống hệt nhau.
    • Sự đồng đều này có thể phản ánh một chiến lược marketing tổng thể hiệu quả, hoặc có thể là dấu hiệu của một thị trường cạnh tranh cao, nơi mà không có loại chiến dịch nào thực sự nổi bật hơn so với các loại khác về mặt hiệu quả đầu tư.

1.3.16 Cross-tab: Age Group × Click Level

age_clicks_table <- table(kd$age_group, kd$clicks_level)   
age_clicks_df <- as.data.frame(age_clicks_table) %>%   
  arrange(desc(Freq))  
age_clicks_df %>%
  kable(
    caption = "Bảng chéo: Nhóm tuổi × Cấp độ số lần nhấp chuột",
    col.names = c("Nhóm tuổi", "Cấp độ số lần nhấp chuột", "Số lượng quan sát"))
Bảng chéo: Nhóm tuổi × Cấp độ số lần nhấp chuột
Nhóm tuổi Cấp độ số lần nhấp chuột Số lượng quan sát
25-34 Trung bình 39894
18-24 Trung bình 20185
All Trung bình 20136
25-34 Thấp 20109
25-34 Cao 20033
35-44 Trung bình 19772
18-24 Thấp 10090
18-24 Cao 9983
All Thấp 9972
35-44 Thấp 9971
35-44 Cao 9944
All Cao 9911
  • Giải thích kỹ thuật:
    • 1: Tạo bảng chéo giữa nhóm tuổi và cấp độ số lần nhấp chuột.
    • 2: Chuyển đổi bảng chéo thành bảng dữ liệu để dễ dàng xử lý.
    • 3: Sắp xếp bảng dữ liệu theo số lượng quan sát giảm dần.
    • 4 - 7: Hiển thị bảng kết quả với tiêu đề và tên cột.
  • Kết quả:
    • Phân phối số lần nhấp chuột theo nhóm tuổi cho thấy:
      • Nhóm 25–34:
        • Trung bình: 39.894 (chiếm nhiều nhất)
        • Thấp: 20.109
        • Cao: 20.033
      • Nhóm 18–24:
        • Trung bình: 20.185
        • Thấp: 10.090
        • Cao: 9.983
      • Nhóm 35–44:
        • Trung bình: 19.772
        • Thấp: 9.971
        • Cao: 9.944
      • Nhóm All:
        • Trung bình: 20.136
        • Thấp: 9.972
        • Cao: 9.911
    • Nhóm tuổi 25–34 có số lần nhấp chuột ở mức Trung bình cao hơn hẳn các nhóm khác, cho thấy đây là nhóm hoạt động nhấp chuột tích cực nhất. Các nhóm khác và nhóm “All” phân bố nhấp chuột gần như đều ở các mức Thấp, Trung bình và Cao.
  • Bảng này phản ánh mối quan hệ giữa tuổi và hành vi nhấp chuột, giúp tập trung chiến dịch vào nhóm 25–34 để tăng hiệu quả.

1.3.17 Mối quan hệ giữa Engagement Score và Clicks

cor_matrix <- cor(kd[, c("engagement_score", "clicks")],
  use = "complete.obs", method = "pearson")
as.data.frame(cor_matrix) %>%
  kable(
    caption = "Ma trận hệ số tương quan giữa Engagement Score và Clicks",
    col.names = c("Engagement Score", "Clicks"))
Ma trận hệ số tương quan giữa Engagement Score và Clicks
Engagement Score Clicks
engagement_score 1.0000000 -0.0019081
clicks -0.0019081 1.0000000
cor_data <- melt(cor_matrix)
ggplot(cor_data, aes(x = Var1, y = Var2, fill = value)) +
  geom_tile(color = "white") +                               
  geom_text(aes(label = round(value, 2)), color = "black", 
            size = 5, fontface = "bold") +
  scale_fill_gradient2(low = "#d73027", mid = "white", high = "#1a9850",
                       midpoint = 0, limit = c(-1, 1),
                       name = "Hệ số Tương quan") +
  labs(title = " Ma trận hệ số tương quan giữa Engagement Score và Clicks",
    x = "", y = "" ) +
  theme_custom()

  • Giải thích kỹ thuật:
    • 1: Tính ma trận tương quan giữa Engagement Score và Clicks sử dụng phương pháp Pearson.

    • 3: Chuyển đổi ma trận tương quan thành bảng dữ liệu để dễ dàng hiển thị.

    • 4 - 6: Hiển thị bảng ma trận tương quan.

    • 1: Chuyển đổi ma trận tương quan thành định dạng dài để vẽ biểu đồ nhiệt.

    • 2: Tạo biểu đồ nhiệt thể hiện ma trận tương quan.

      • 3: vẽ các ô màu đại diện cho hệ số tương quan.
      • 4: thêm nhãn hệ số tương quan lên mỗi ô.
      • 6: thiết lập thang màu từ đỏ (âm) qua trắng (0) đến xanh (dương).
    • 8: Thêm tiêu đề và nhãn trục cho biểu đồ.

    • 11: Áp dụng chủ đề tùy chỉnh đã thiết lập trước đó.

  • Kết quả:
    • Ma trận tương quan giữa Engagement Score và Clicks cho thấy: *Hệ số tương quan = -0,0019, gần bằng 0.
  • Nhận xét: Engagement Score và số lần nhấp chuột gần như không liên quan trong dữ liệu này.
    • Điều này có nghĩa là tăng số nhấp chuột không đồng nghĩa với tăng mức độ tương tác trên mỗi click.

1.3.18 Ma trận tương quan các chỉ số Marketing

\[\text{Các chỉ số: } \text{roi, CTR, CPC, CPM, acquisition\_cost, clicks, impressions}\]

numeric_metrics <- kd %>%
  select(roi, CTR, CPC, CPM, acquisition_cost, clicks, impressions, engagement_score) %>%
  filter_all(all_vars(!is.infinite(.)))
cor_matrix_metrics <- cor(numeric_metrics, use = "complete.obs", method = "pearson")
as.data.frame(cor_matrix_metrics) %>%
  kable(format = "latex", booktabs = TRUE,
    caption = "Ma trận hệ số tương quan các chỉ số Marketing",
    col.names = c("ROI", "CTR", "CPC", "CPM",
                  "Acquisition Cost", "Clicks", "Impressions", "Engagement Score")) %>%
    kable_styling(
      latex_options = c("hold_position", "scale_down"),
      position = "center" )
(p_corr_metrics <- ggcorrplot(cor_matrix_metrics,
  hc.order = TRUE,
  type = "lower",
  lab = TRUE,
  lab_size = 3.5,
  method = "square",
  colors = c("#d73027", "white", "#1a9850"),
  title = "Ma trận tương quan các chỉ số Marketing") +
  theme(plot.title = element_text(face = "bold", size = 16, 
                                  hjust = 0.5, family="Times New Roman"),
        axis.text.x = element_text(angle = 45, vjust = 1, 
                                   hjust = 1, family="Times New Roman"),
        axis.text.y = element_text(family="Times New Roman")))

  • Giải thích kỹ thuật:
    • 1: Tạo biểu đồ ma trận tương quan sử dụng ggcorrplot.
    • 2: sắp xếp lại các biến theo cụm để dễ nhìn hơn.
    • 3: chỉ hiển thị nửa dưới của ma trận.
    • 4: hiển thị giá trị hệ số tương quan trên biểu đồ.
    • 5: cỡ chữ cho nhãn hệ số tương quan.
    • 6: sử dụng hình vuông để biểu diễn các ô trong ma trận.
    • 7 colors = c(“#d73027”, “white”, “#1a9850”): thiết lập thang màu từ đỏ (âm) qua trắng (0) đến xanh (dương).
    • 8: Thêm tiêu đề và tùy chỉnh kiểu chữ, cỡ chữ cho biểu đồ.
    • 9: in đậm , cỡ chữ 16, căn giữa, font Times New Roman cho tiêu đề.
    • 11: xoay trục x 45 độ và sử dụng font Times New Roman cho nhãn trục.
    • 13: sử dụng font Times New Roman cho nhãn trục y.
  • Kết quả:
    • Ma trận tương quan các chỉ số Marketing cho thấy mối quan hệ đáng chú ý như sau:
    1. ROI: Hầu như không tương quan với các chỉ số khác (hệ số gần 0), nghĩa là lợi nhuận đầu tư không phụ thuộc rõ rệt vào CTR, CPC, CPM, clicks, impressions hay engagement score.
    2. CTR:
    • Tương quan dương trung bình với Clicks (0,507), nghĩa là CTR cao thường đi kèm nhiều nhấp chuột hơn.
    • Tương quan dương mạnh với CPM (0,670) và âm mạnh với Impressions (-0,658), phản ánh mối quan hệ chi phí – hiển thị: CTR cao khi lượng hiển thị thấp, chi phí hiển thị cao hơn.
    • Tương quan âm với CPC (-0,369), nghĩa là CTR cao thường đi kèm chi phí trên mỗi click thấp hơn.
    1. CPC:
    • Tương quan âm mạnh với Clicks (-0,727), tức là nhiều nhấp chuột giúp giảm chi phí trung bình trên mỗi click.
    • Tương quan dương với Acquisition Cost (0,410) và CPM (0,169), chi phí tổng và chi phí hiển thị tăng khi CPC tăng.
    1. CPM:
    • Tương quan âm mạnh với Impressions (-0,726), phản ánh chi phí cho mỗi 1000 lần hiển thị tăng khi lượng hiển thị thấp.
    • Tương quan dương với Acquisition Cost (0,411) và CTR (0,670), phù hợp với các chiến dịch có chi phí hiển thị cao nhưng CTR cao.
    1. Clicks và Impressions:
    • Hầu như không tương quan trực tiếp với nhau (gần 0) trong dữ liệu này.
    • Clicks âm với CPC (-0,727), Impressions âm với CPM (-0,726).
    1. Engagement Score:
    • Hệ số gần 0 với hầu hết các chỉ số, tương quan thấp nhất, cho thấy mức độ tương tác gần như không phụ thuộc vào ROI, CTR, CPC, CPM, Clicks hay Impressions.

1.3.19 ROI theo Kênh và Nhóm tuổi

roi_mul <- kd %>%
  filter(!is.na(roi) & !is.na(channel_used) & !is.na(age_group)) %>%
  group_by(channel_used, age_group) %>%
  summarise( Mean_ROI = mean(roi, na.rm = TRUE),N = n()) %>%
  ungroup()
roi_mul %>%
  kable(
    caption = "ROI trung bình theo Kênh và Nhóm tuổi",
    col.names = c("Kênh", "Nhóm tuổi", "ROI Trung bình", "Số lượng"))
ROI trung bình theo Kênh và Nhóm tuổi
Kênh Nhóm tuổi ROI Trung bình Số lượng
Email 18-24 4.987819 6772
Email 25-34 5.008481 13353
Email 35-44 5.014520 6719
Email All 4.963529 6755
Facebook 18-24 4.959734 6727
Facebook 25-34 5.034363 12999
Facebook 35-44 5.033879 6476
Facebook All 5.033015 6617
Google Ads 18-24 5.009151 6658
Google Ads 25-34 4.998661 13450
Google Ads 35-44 4.987042 6656
Google Ads All 5.022231 6674
Instagram 18-24 4.958137 6763
Instagram 25-34 5.000456 13279
Instagram 35-44 4.985893 6679
Instagram All 4.999123 6671
Website 18-24 4.998221 6661
Website 25-34 5.018644 13493
Website 35-44 5.007805 6561
Website All 5.027339 6645
YouTube 18-24 4.984590 6677
YouTube 25-34 4.994060 13462
YouTube 35-44 5.009632 6596
YouTube All 4.986596 6657
  • Giải thích kỹ thuật:
    • 2: Lọc bỏ các quan sát có giá trị thiếu trong roi, channel_used và age_group.
    • 3: Nhóm dữ liệu theo channel_used và age_group.
    • 4: Tính ROI trung bình và số lượng quan sát trong mỗi nhóm, bỏ qua giá trị bị thiếu.
    • 5: Bỏ nhóm để chuẩn bị cho việc hiển thị bảng.
    • 6 - 9: Hiển thị bảng kết quả với tiêu đề và tên cột.
  • Kết quả:
  • ROI trung bình theo Kênh và Nhóm tuổi cho thấy một số xu hướng:
  1. Nhóm tuổi 25–34 thường có ROI trung bình cao hơn hoặc tương đương các nhóm khác trên hầu hết kênh:
    • Email: 5,008
    • Facebook: 5,034
    • Google Ads: 4,999
    • Instagram: 5,000
    • Website: 5,019
    • YouTube: 4,994
  2. Nhóm tuổi 18–24 thường có ROI trung bình thấp hơn, dao động từ 4,959 đến 5,009.
  3. Nhóm 35–44 và nhóm All dao động quanh mức 4,985–5,027, không chênh lệch quá lớn.

Nhận xét tổng quan:
* Facebook và Website có ROI trung bình cao nhất cho nhóm 25–34 (khoảng 5,034 – 5,019), cho thấy đây là đối tượng tiềm năng để tập trung chiến dịch.
* Sự khác biệt ROI giữa các nhóm tuổi không quá lớn, nhưng nhóm 25–34 vẫn nổi bật hơn, đặc biệt trên các kênh hiệu quả như Facebook và Website.
* Nhóm 18–24 và nhóm All có ROI trung bình thấp hơn, có thể cần tối ưu chiến lược hoặc nội dung quảng cáo cho các đối tượng này.

1.3.20 Biểu đồ ROI theo Kênh và Nhóm tuổi

(p_roi_facet <- ggplot(roi_mul, 
  aes(x = reorder(channel_used, Mean_ROI), y = Mean_ROI, fill = channel_used)) +
  geom_col() +
  geom_text(aes(label = sprintf("%.4f", Mean_ROI)), 
            vjust = 1.5, size = 4, color="black") +
  facet_wrap(~ age_group, ncol = 1) +
  labs(
    title = "ROI trung bình theo Kênh và Nhóm tuổi",
    x = "Kênh",
    y = "ROI trung bình") +
  theme_custom() + 
  theme(
    legend.position = "none",
    axis.text.x = element_text(angle = 0, hjust = 0.5),
    axis.text.y = element_text(angle = 0, hjust = 0.5, size = 8, fontface="bold" )))

  • Giải thích kỹ thuật:
    • 1: Tạo biểu đồ cột thể hiện ROI trung bình theo kênh và nhóm tuổi.
    • 3: Vẽ các cột với ROI trung bình cho mỗi kênh.
    • 4: Thêm nhãn ROI trung bình lên mỗi cột.
      • label = sprintf(“%.4f”, Mean_ROI): định dạng nhãn với 4 chữ số thập phân.
      • vjust = 1.5: đặt nhãn phía trên cột.
      • size = 4: cỡ chữ 4.
      • color=“black”: màu chữ đen.
    • 6: Tạo các biểu đồ con cho từng nhóm tuổi, xếp theo cột.
    • 7: Thêm tiêu đề và nhãn trục cho biểu đồ.
    • 11: Áp dụng chủ đề tùy chỉnh đã thiết lập trước đó.
    • 12: Tùy chỉnh giao diện biểu đồ:
    • 13: ẩn chú giải.
    • 14: giữ trục x nằm ngang, căn giữa.
    • 15: giữ trục y nằm ngang, căn giữa, cỡ chữ 8, in đậm.
  • Kết quả:
    • Sự ổn định là yếu tố chủ đạo: Phát hiện quan trọng nhất không phải là sự khác biệt, mà là sự thiếu vắng của nó. Dù nhìn vào nhóm 18-24 hay 35-44, dù xem xét kênh Email hay Google Ads, ROI trung bình vẫn gần như không thay đổi, chỉ chênh lệch ở mức độ thập phân rất nhỏ (ví dụ: thấp nhất khoảng 4.95 và cao nhất khoảng 5.03).
    • Kênh và Tuổi không phải là yếu tố tạo khác biệt lớn: Dữ liệu chỉ ra rằng việc lựa chọn kênh hoặc nhắm mục tiêu vào một nhóm tuổi cụ thể không tạo ra sự đột phá hay sụt giảm đáng kể nào về ROI. Mọi chiến lược kết hợp đều mang lại hiệu quả tương đương nhau.
    • Các khác biệt nhỏ (nếu có): Mặc dù rất nhỏ, nhưng có thể thấy:
      • Facebook, Website, và Google Ads có xu hướng nhỉnh hơn một chút, thường xuyên nằm ở mức trên 5.0. Đặc biệt, Facebook đạt ROI cao nhất ở nhóm 25-34 (5.0344) và 35-44 (5.0339).
      • Email, YouTube, và Instagram có xu hướng nằm ngay tại mốc 5.0 hoặc thấp hơn một chút (ví dụ: 4.96 đến 4.99).

2 PHẦN 2: BỘ DỮ LIỆU BÁO CÁO TÀI CHÍNH CÔNG TY CỔ PHẦN DƯỢC PHẨM IMEXPHARM (IMP)

2.1 Giới thiệu dữ liệu

2.1.1 Đọc dữ liệu

bc <- read_excel("D:/HK3-2025/NGON_NGU_LAP_TRINH/bctc IMP.xlsx")
  • Giải thích kỹ thuật:
    • Sử dụng hàm read_excel từ gói readxl để đọc dữ liệu từ file Excel có tên “bctc IMP.xlsx” và lưu vào biến bc.
    • Đảm bảo rằng đường dẫn đến file Excel là chính xác để tránh lỗi khi đọc dữ liệu.
  • Kết quả:
    • Dữ liệu báo cáo tài chính của Công ty Cổ phần Dược phẩm Imexpharm (IMP) được tải thành công vào biến bc dưới dạng bảng dữ liệu.

2.1.2 Đổi tên cột “year” thành “Năm”

bc <- bc %>% rename(Năm = year)
  • Giải thích kỹ thuật:
    • Sử dụng hàm rename từ gói dplyr để đổi tên cột “year” thành “Năm” trong bảng dữ liệu bc.
  • Vì tên biến “year” khác tên các biến khác trong bộ dữ liệu nên sẽ đổi tên.

2.1.3 Thiết lập dấu phân cách hàng nghìn

kbl_default1<- function(data, caption = NULL, col.names = NULL, align = NULL) {
  num_cols <- names(data)[sapply(data, is.numeric) & !(names(data) %in% 
                                                         c("Năm", "year"))]
  data_fmt <- data %>%
    mutate(across(all_of(num_cols),
                  ~ format(., big.mark = ".", decimal.mark = ",", 
                           scientific = FALSE))) 
return(data_fmt) } 
format_vn <- function(x, digits = 0) {
  format(round(x, digits),
         big.mark = ".",
         decimal.mark = ",",
         scientific = FALSE)}
  • Giải thích kỹ thuật:
    • Hàm kbl_default1 định dạng các cột số trong bảng dữ liệu để sử dụng dấu chấm (.) làm dấu phân cách hàng nghìn và dấu phẩy (,) làm dấu thập phân, phù hợp với chuẩn Việt Nam.
    • Hàm format_vn định dạng một giá trị số cụ thể với số chữ số thập phân tùy chọn, sử dụng cùng quy tắc dấu phân cách.

2.1.4 Xem trước dữ liệu

bc %>% kbl_default1() %>%
    kable(format = "latex", booktabs = TRUE,  
    caption = "Bộ dữ liệu Báo cáo tài chính IMP") %>%
    kable_styling(
      latex_options = c("hold_position", "scale_down"),
      position = "center")
  • Giải thích kỹ thuât:
    • 1: Áp dụng hàm kbl_default1 để định dạng dữ liệu bc.
    • 2: Sử dụng hàm kable từ gói knitr để tạo bảng và tùy chọn booktabs để cải thiện định dạng bảng.
    • 3: Đặt tên cho tiêu đề bảng.
    • 4: Sử dụng hàm kable_styling từ gói kableExtra để tùy chỉnh giao diện bảng.
    • 5: hold_position: giữ vị trí bảng ở vị trí hiện tại; scale_down:ự động thu nhỏ bảng nếu quá rộng.
    • 6: Căn giữa bảng trong trang.
  • Báo cáo tài chính của IMP được xây dựng theo cấu trúc bảng, bao gồm các chỉ số tài chính trọng yếu như lợi nhuận sau thuế, dòng tiền từ hoạt động kinh doanh, đầu tư và tài trợ. Các biến động về công nợ, tồn kho, khấu hao và chi phí tài chính được phản ánh rõ ràng, giúp đánh giá toàn diện hiệu quả hoạt động và khả năng tạo dòng tiền của doanh nghiệp qua từng năm. Việc phân tích các chỉ số này hỗ trợ việc ra quyết định chiến lược, quản trị tài chính và dự báo dòng tiền trong tương lai.

2.1.5 Xem cấu trúc dữ liệu

cau_truc1 <- data.frame(
  `Tên biến` = names(bc),
  `Kiểu dữ liệu` = sapply(bc, function(x) class(x)[1]),
  `xuất giá trị đầu` = sapply(bc, function(x) if(is.numeric(x)) x[1] 
                              else as.character(x[1])),
  check.names = FALSE,
  row.names = NULL)
cau_truc1 %>%
  kbl_default1() %>% kable( 
    caption = "Cấu trúc dữ liệu của bộ Marketing Campaign Performance Dataset", 
      col.name= c("Tên biến", "Kiểu dữ liệu", "Giá trị đầu"), 
    align = "lrr")
Cấu trúc dữ liệu của bộ Marketing Campaign Performance Dataset
Tên biến Kiểu dữ liệu Giá trị đầu
Năm numeric 2.015
LoiNhuanSauThue numeric 92.275.349.999
Tien_DauKy numeric 178.550.050.326
Tien_CuoiKy numeric 87.841.659.460
CFO_Tong numeric 81.130.888.888
KhauHao numeric 38.402.557.093
BienDong_PhaiThu numeric -41.333.661.601
BienDong_TonKho numeric 50.382.333.195
BienDong_PhaiTra numeric -35.669.331.173
LaiVay_DaTra numeric -605.820.692
Thue_DaNop numeric -26.094.252.845
CFI_Tong numeric -204.828.797.781
Chi_MuaTSCD numeric -116.451.196.931
Chi_TienGui numeric -100.000.000.000
Thu_TienGui numeric NA
CFF_Tong numeric 32.990.722.640
VayMoi numeric 95.894.850.000
TraNoGoc numeric -95.894.850.000
  • Giải thích kỹ thuật:
    • 1: Tạo bảng dữ liệu cau_truc1 với các cột:
      • 2: Tên của mỗi biến trong bộ dữ liệu bc.
      • 3: Kiểu dữ liệu của mỗi biến (ví dụ: numeric, character).
      • 4: Giá trị đầu tiên của mỗi biến để cung cấp cái nhìn tổng quan về nội dung.
    • 5: Để giữ nguyên tên cột có dấu tiếng Việt.
    • 6: Để bỏ tên dòng mặc định.
    • 8: Định dạng bảng theo thiết lập kbl_default1.
    • 9: Đặt tên cho các tiêu đề cột trong bảng.
    • 11: Sử dụng hàm kable_styling để tùy chỉnh.
    • 12: Căn giữa bảng trong trang.
    • 13: Đặt bảng chiếm toàn bộ chiều rộng trang.
    • 14: Thiết lập cỡ chữ 12 cho bảng.
    • 15: Sử dụng các tùy chọn hold_position và scale_down để giữ vị trí và thu nhỏ bảng nếu cần.
  • Kết quả:
    • Bộ dữ liệu bao gồm các biến với kiểu dữ liệu chủ yếu là numeric, phản ánh các chỉ số tài chính quan trọng như lợi nhuận, dòng tiền và chi phí. Việc hiểu rõ cấu trúc dữ liệu giúp xác định các biến cần phân tích và đảm bảo tính chính xác trong quá trình xử lý và phân tích dữ liệu.

2.1.6 Kích thước dữ liệu

kich_thuoc1 <- data.frame(
  Thông_tin = c("Số dòng (Quan sát)", "Số cột (Biến)"),
  Kết_quả = c(nrow(bc), ncol(bc)) )
kich_thuoc1 %>%
  kbl_default1() %>% kable( 
    caption = "Cấu trúc dữ liệu của bộ Marketing Campaign Performance Dataset", 
      col.name= c("Thông tin", "Kết quả"),
                  align = "lr")
Cấu trúc dữ liệu của bộ Marketing Campaign Performance Dataset
Thông tin Kết quả
Số dòng (Quan sát) 10
Số cột (Biến) 18
  • Giải thích kỹ thuật:
    • 1: Tạo bảng dữ liệu kich_thuoc1 với hai cột:
    • 2: Mô tả thông tin về kích thước dữ liệu (số dòng và số cột).
    • 3: Chứa giá trị tương ứng (số dòng và số cột) bằng cách sử dụng hàm nrow và ncol trên bảng dữ liệu bc.
    • 5: Định dạng bảng theo thiết lập kbl_default1.
    • 6: Đặt tên cho bảng và tiêu đề cột trong bảng.
  • Kết quả:
    • Bộ dữ liệu bao gồm 10 quan sát là 10 năm và 18 biến.

2.1.7 Thống kê mô tả cơ bản

bc1 <- bc %>% select(-Năm)
skimmed1 <- skim(bc1)  
thong_ke_mota1 <- skimmed1 %>%
  select(skim_variable, numeric.mean, numeric.p50, numeric.sd, numeric.p0, numeric.p100)
thong_ke_mota1 %>% kbl_default1() %>% kable(
  caption = "Bảng Thống kê Mô tả (Đơn vị: VND)",
  col.names = c("Biến", "Trung bình", "Trung vị", "Độ lệch chuẩn", "Min", "Max"),
  align = "lrrrrr") %>%
  kable_styling(latex_options = c("hold_position", "scale_down"),
                position = "center")
Bảng Thống kê Mô tả (Đơn vị: VND)
Biến Trung bình Trung vị Độ lệch chuẩn Min Max
LoiNhuanSauThue 185.461.493.333 175.740.780.878 79.267.830.966 92.275.349.999 320.862.393.082
Tien_DauKy 138.003.577.443 106.328.850.442 63.705.340.836 75.035.614.726 271.272.865.376
Tien_CuoiKy 136.346.904.294 106.328.850.442 62.741.887.421 75.035.614.726 271.272.865.376
CFO_Tong 133.427.627.649 90.931.774.710 114.796.209.903 -30.308.428.665 378.603.401.448
KhauHao 54.126.367.933 47.284.812.141 24.306.276.907 30.515.092.412 105.636.226.909
BienDong_PhaiThu -12.166.658.588 -14.460.118.668 57.502.782.383 -83.306.984.898 115.759.490.658
BienDong_TonKho -41.154.397.712 -34.622.213.581 89.038.343.861 -260.749.238.157 52.398.387.880
BienDong_PhaiTra -7.535.274.795 -8.277.667.477 29.924.095.334 -49.525.833.086 39.579.670.960
LaiVay_DaTra -3.046.559.562 -3.598.954.722 2.272.372.959 -6.699.943.903 -55.154.439
Thue_DaNop -45.519.649.979 -41.597.605.868 20.095.203.893 -83.992.016.063 -22.380.008.319
CFI_Tong -130.736.762.123 -123.911.128.404 137.798.621.319 -443.246.932.809 69.894.262.492
Chi_MuaTSCD -130.057.173.899 -101.628.608.786 78.923.882.881 -274.456.614.870 -52.856.687.303
Chi_TienGui -198.920.800.808 -176.494.739.041 162.819.741.002 -451.171.580.000 -12.000.000.000
Thu_TienGui 204.128.349.936 195.897.929.613 146.900.902.612 26.297.761.332 491.300.000.000
CFF_Tong -4.797.122.112 -33.304.800.015 168.195.912.399 -271.718.144.243 352.371.798.300
VayMoi 142.392.327.092 160.076.187.844 202.072.945.620 -315.649.453.686 387.993.511.872
TraNoGoc -183.894.915.875 -181.308.771.504 92.990.583.989 -351.293.662.887 -49.387.359.000
  • Giải thích kỹ thuật:
    • 1: Loại bỏ cột “Năm” khỏi bảng dữ liệu bc để chỉ tập trung vào các biến số.
    • 2: Sử dụng hàm skim từ gói skimr để tạo bảng thống kê mô tả cho các biến số trong bc1.
    • 3: Chọn các cột quan trọng từ kết quả skim.
    • 5: Định dạng bảng theo thiết lập kbl_default1.
    • 6: Đặt tên cho bảng và tiêu đề cột trong bảngg.
  • Kết quả:
    • Thông qua các chỉ số như trung bình, độ lệch chuẩn, giá trị nhỏ nhất và lớn nhất, có thể đánh giá được mức độ biến động, xu hướng và sự ổn định của các chỉ số tài chính.

2.1.8 Kiểm tra dữ liệu bị thiếu

du_lieu_bi_thieu1 <- bc %>% summarise_all(~ sum(is.na(.))) %>%
  pivot_longer(everything(), names_to = "Tên biến", values_to = "Số giá trị bị thiếu")
du_lieu_bi_thieu1 %>% kbl_default1() %>% 
  kable(caption = "Kiểm tra dữ liệu bị thiếu",
      col.names = c("Tên biến", "Số giá trị bị thiếu"), align = "lr")
Kiểm tra dữ liệu bị thiếu
Tên biến Số giá trị bị thiếu
Năm 0
LoiNhuanSauThue 0
Tien_DauKy 0
Tien_CuoiKy 0
CFO_Tong 0
KhauHao 0
BienDong_PhaiThu 0
BienDong_TonKho 0
BienDong_PhaiTra 0
LaiVay_DaTra 0
Thue_DaNop 0
CFI_Tong 0
Chi_MuaTSCD 0
Chi_TienGui 0
Thu_TienGui 1
CFF_Tong 1
VayMoi 1
TraNoGoc 1
  • Giải thích kỹ thuật:
    • 1: Sử dụng hàm summarise_all kết hợp với sum(is.na(.)) để tính tổng số giá trị bị thiếu (NA) cho mỗi biến trong bảng dữ liệu bc.
    • 2: Chuyển đổi kết quả thành định dạng dài với pivot_longer để dễ dàng hiển thị.
    • 3: Định dạng bảng theo thiết lập kbl_default1.
    • 5: Đặt tên cho các bảng và tiêu đề cột trong bảng.
  • Kết quả:
    • Phần lớn các biến tài chính đều đầy đủ và không có giá trị bị thiếu. Tuy nhiên, có bốn biến xuất hiện thiếu một giá trị, bao gồm: Thu_TienGui, CFF_Tong, VayMoi và TraNoGoc. Việc nhận diện các giá trị thiếu giúp đảm bảo độ tin cậy của phân tích thống kê và là cơ sở để xử lý dữ liệu phù hợp.

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

trung_lap1 <- bc %>%
  duplicated() %>% sum()
trung_lap_df <- data.frame('Số bản dòng bị trùng lặp' = trung_lap1)
trung_lap_df %>%
 kbl_default1() %>%
 kable(caption = "Kiểm tra dữ liệu trùng lặp",
     col.names = c("Số dòng bị trùng lặp"), align = "c")
Kiểm tra dữ liệu trùng lặp
Số dòng bị trùng lặp
0
  • Giải thích kỹ thuật:
    • 2: Sử dụng hàm duplicated để kiểm tra các dòng trùng lặp trong bảng dữ liệu bc và tính tổng số dòng trùng lặp bằng sum().
    • 3: Tạo bảng dữ liệu để lưu trữ kết quả số dòng trùng lặp.
    • 5: Định dạng bảng theo thiết lập kbl_default1.
    • 6: Đặt tên cho các bảng và tiêu đề cột trong bảng.
  • Không có dòng nào bị lặp lại trong bộ dữ liệu.

2.1.10 Top 3 năm có giá trị lợi nhuận sau thuế cao nhất

bc %>%
  select(Năm, LoiNhuanSauThue) %>%
  arrange(desc(LoiNhuanSauThue)) %>%
  head(3) %>%
  kbl_default1() %>%
  kable(
    caption = "Top 3 năm có giá trị lợi nhuận sau thuế cao nhất",
    col.names = c("Năm", "Lợi nhuận sau thuế (VND)"), align = "lr")
Top 3 năm có giá trị lợi nhuận sau thuế cao nhất
Năm Lợi nhuận sau thuế (VND)
2024 320.862.393.082
2023 299.556.005.542
2022 223.540.317.602
  • Giải thích kỹ thuật:
    • 2: Chọn cột “Năm” và “LoiNhuanSauThue” từ dữ liệu gốc.
    • 3: Sắp xếp dữ liệu theo cột “LoiNhuanSauThue” theo thứ tự giảm dần.
    • 4: Lấy 3 dòng đầu tiên sau khi sắp xếp để có được top 3 năm có lợi nhuận sau thuế cao nhất.
    • 5: Định dạng bảng theo thiết lập kbl_default1.
    • 6: Đặt tên cho các bảng và tiêu đề cột trong bảng.
  • Kết quả thống kê cho thấy ba năm gần nhất (2022–2024) là giai đoạn doanh nghiệp IMP đạt được mức lợi nhuận sau thuế cao nhất. Xu hướng tăng trưởng lợi nhuận liên tục trong giai đoạn này cho thấy sự cải thiện rõ rệt về hiệu quả vận hành, quản trị chi phí và chiến lược tài chính của doanh nghiệp.

2.1.11 Top 3 năm có dòng tiền từ hoạt động kinh doanh cao nhất

bc %>%
  select(Năm, CFO_Tong) %>%
  arrange(desc(CFO_Tong)) %>%
  head(3) %>%
  kbl_default1() %>%
  kable(
    caption = "Top 3 năm có dòng tiền từ hoạt động kinh doanh cao nhất",
    col.names = c("Năm", "Dòng tiền từ hoạt động kinh doanh (VND)"), align = "lr")
Top 3 năm có dòng tiền từ hoạt động kinh doanh cao nhất
Năm Dòng tiền từ hoạt động kinh doanh (VND)
2022 378.603.401.448
2021 234.881.036.040
2024 216.267.565.018
  • Giải thích kỹ thuật:
    • 2: Chọn cột “Năm” và “CFO_Tong” từ dữ liệu gốc.
    • 3: Sắp xếp dữ liệu theo cột “CFO_Tong” theo thứ tự giảm dần.
    • 4: Lấy 3 dòng đầu tiên sau khi sắp xếp để có được top 3 năm có dòng tiền từ hoạt động kinh doanh cao nhất.
    • 5: Định dạng bảng theo thiết lập kbl_default1.
    • 6: Đặt tên cho các bảng và tiêu đề cột trong bảng.
  • Kết quả: Năm 2022 ghi nhận mức dòng tiền từ hoạt động kinh doanh cao nhất, đạt hơn 378 nghìn tỷ đồng. Đây là dấu hiệu tích cực cho thấy doanh nghiệp IMP đã vận hành hiệu quả, tối ưu hóa nguồn thu từ hoạt động cốt lõi.

2.1.12 Top 3 năm có biến động hàng tồn kho thấp nhất

bc %>%
  select(Năm, BienDong_TonKho) %>%
  arrange(BienDong_TonKho) %>% 
  head(3) %>% kbl_default1() %>%
  kable(
    caption = "Top 3 năm có biến động hàng tồn kho thấp nhất",
    col.names = c("Năm", "Biến động hàng tồn kho (VND)"), align = "lr")
Top 3 năm có biến động hàng tồn kho thấp nhất
Năm Biến động hàng tồn kho (VND)
2023 -260.749.238.157
2020 -75.247.048.473
2021 -66.128.977.456
  • Giải thích kỹ thuật:
    • 2: Chọn cột “Năm” và “BienDong_TonKho” từ khung dữ liệu gốc.
    • 3: Sắp xếp dữ liệu theo cột “BienDong_TonKho” theo thứ tự tăng dần để tìm các năm có biến động hàng tồn kho thấp nhất.
    • 4: Lấy 3 dòng đầu tiên sau khi sắp xếp để có được top 3 năm có biến động hàng tồn kho thấp nhất.
    • 5: Định dạng bảng theo thiết lập kbl_default1.
    • 6: Đặt tên cho các bảng và tiêu đề cột trong bảng.
  • Năm 2023 ghi nhận mức giảm hàng tồn kho lớn nhất với hơn 260 nghìn tỷ đồng, cho thấy doanh nghiệp đã thực hiện điều chỉnh mạnh mẽ trong hoạt động sản xuất hoặc tiêu thụ hàng hóa.

2.2 Xử lý và mã hóa dữ liệu

2.2.1 Xử lý dữ liệu bị thiếu

bctc <- bc %>%
  mutate(across(where(is.numeric), 
                ~ifelse(is.na(.), median(., na.rm = TRUE), .)))
du_lieu_bi_thieu2 <- bctc %>% summarise_all(~ sum(is.na(.))) %>%
  pivot_longer(everything(), names_to = "Tên biến", values_to = "Số giá trị bị thiếu")
du_lieu_bi_thieu2 %>%kbl_default1() %>%
 kable(caption = "Kiểm tra dữ liệu bị thiếu sau khi xử lý",
     l.names = c("Tên biến", "Số giá trị bị thiếu"), align = "lr")
Kiểm tra dữ liệu bị thiếu sau khi xử lý
Tên biến Số giá trị bị thiếu
Năm 0
LoiNhuanSauThue 0
Tien_DauKy 0
Tien_CuoiKy 0
CFO_Tong 0
KhauHao 0
BienDong_PhaiThu 0
BienDong_TonKho 0
BienDong_PhaiTra 0
LaiVay_DaTra 0
Thue_DaNop 0
CFI_Tong 0
Chi_MuaTSCD 0
Chi_TienGui 0
Thu_TienGui 0
CFF_Tong 0
VayMoi 0
TraNoGoc 0
  • Giải thích kỹ thuật:
    • 2: Sử dụng hàm mutate kết hợp với across để áp dụng hàm ifelse cho tất cả các cột số trong khung dữ liệu bc.
    • 3: Trong hàm ifelse, kiểm tra nếu giá trị là NA (bị thiếu), thì thay thế bằng trung vị của cột đó (tính bằng median(., na.rm = TRUE)), ngược lại giữ nguyên giá trị ban đầu.
    • 4: Tạo khung dữ liệu bctc mới với các giá trị bị thiếu đã được xử lý.
    • 4: Kiểm tra lại số lượng giá trị bị thiếu trong bctc sau khi xử lý, tương tự như trước đó.
    • 5: Chuyển đổi kết quả thành định dạng dài với pivot_longer để dễ dàng hiển thị.
    • 6: Định dạng bảng theo thiết lập kbl_default1.
    • 7: Đặt tên cho các bảng và tiêu đề cột trong bảng.
  • Kết quả:
    • Bộ dữ liệu đã được xử lí hoàn chỉnh, không còn giá trị nào bị thiếu nữa, thay thế những vị trí thiếu bằng cách tính trung vị.

2.2.2 Mã hóa lợi nhuận sau thuế

q4 <- quantile(bctc$LoiNhuanSauThue, probs = c(0.25, 0.75), na.rm = TRUE)
bctc <- bctc %>% mutate(
    Nhom_loi_nhuan = cut(
      LoiNhuanSauThue,
      breaks = c(-Inf, q4[1], q4[2], Inf),
      labels = c("Thấp", "Trung bình", "Cao"),
      right = TRUE ))
bctc %>%
  select(Năm, LoiNhuanSauThue, Nhom_loi_nhuan) %>%
  arrange(factor(Nhom_loi_nhuan, levels = c("Thấp", "Trung bình", "Cao"))) %>%
  kbl_default1() %>%
kable(caption = "Phân loại lợi nhuận sau thuế theo phân vị",
      col.names = c("Năm", "Lợi Nhuận Sau Thuế", "Nhóm lợi nhuận"), align = "lrl")
Phân loại lợi nhuận sau thuế theo phân vị
Năm Lợi Nhuận Sau Thuế Nhóm lợi nhuận
2015 92.275.349.999 Thấp
2016 101.159.344.647 Thấp
2017 117.360.040.786 Thấp
2018 138.683.041.628 Trung bình
2019 162.386.686.793 Trung bình
2020 209.696.878.289 Trung bình
2021 189.094.874.963 Trung bình
2022 223.540.317.602 Cao
2023 299.556.005.542 Cao
2024 320.862.393.082 Cao
  • Giải thích kỹ thuật:
    • 1: Tính các phân vị thứ 25% và 75% của cột “LoiNhuanSauThue” trong dữ liệu gốc.
    • 2: Sử dụng hàm mutate kết hợp với cut để tạo một cột mới “Nhom_loi_nhuan” phân loại lợi nhuận sau thuế thành ba nhóm: “Thấp”, “Trung bình” và “Cao” dựa trên các phân vị đã tính.
    • 7: Khoảng phân chia bao gồm cả giá trị biên phải
    • 9: Chọn cột “Năm”, “LoiNhuanSauThue” và “Nhom_loi_nhuan” để hiển thị.
    • 10: Sắp xếp dữ liệu theo thứ tự nhóm lợi nhuận từ thấp đến cao.
    • 11: Định dạng bảng theo thiết lập kbl_default1.
    • 12: Đặt tên cho các bảng và tiêu đề cột trong bảng.
  • Kết quả:
    • Giai đoạn từ 2022 đến 2024 thuộc nhóm lợi nhuận cao, phản ánh sự tăng trưởng mạnh mẽ về hiệu quả kinh doanh. Trong khi đó, các năm từ 2015 đến 2017 thuộc nhóm thấp, cho thấy giai đoạn đầu còn nhiều hạn chế về khả năng sinh lời

2.2.3 Mã hóa dòng tiền từ hoạt động kinh doanh

q5 <- quantile(bctc$CFO_Tong, probs = c(0.25, 0.75), na.rm = TRUE)
bctc <- bctc %>% mutate(
    Nhom_CFO = cut(
      CFO_Tong,
      breaks = c(-Inf, q5[1], q5[2], Inf),
      labels = c("Thấp", "Trung bình", "Cao"),
      right = TRUE ))
bctc %>%
  select(Năm, CFO_Tong, Nhom_CFO) %>%
  arrange(Năm) %>% kbl_default1() %>%
  kable(caption = "Phân loại dòng tiền từ hoạt động kinh doanh theo phân vị",
        col.names = c("Năm" ,"Dòng tiền từ hoạt động kinh doanh", "Nhóm CFO"), 
        align = "lrl")
Phân loại dòng tiền từ hoạt động kinh doanh theo phân vị
Năm Dòng tiền từ hoạt động kinh doanh Nhóm CFO
2015 81.130.888.888 Trung bình
2016 84.650.650.055 Trung bình
2017 97.212.899.364 Trung bình
2018 132.094.454.149 Trung bình
2019 66.590.387.415 Thấp
2020 73.153.422.780 Thấp
2021 234.881.036.040 Cao
2022 378.603.401.448 Cao
2023 -30.308.428.665 Thấp
2024 216.267.565.018 Cao
  • Giải thích kỹ thuật:
    • 1: Tính các phân vị thứ 25% và 75% của cột “CFO_Tong” trong khung dữ liệu bctc sử dụng hàm quantile.
    • 2: Sử dụng hàm mutate kết hợp với cut để tạo một cột mới “Nhom_CFO” phân loại dòng tiền từ hoạt động kinh doanh thành ba nhóm: “Thấp”, “Trung bình” và “Cao” dựa trên các phân vị đã tính.
    • 9: Chọn cột “Năm”, “CFO_Tong” và “Nhom_CFO” để hiển thị.
    • 10: Sắp xếp dữ liệu theo năm.
    • 10: Định dạng bảng theo thiết lập kbl_default1.
    • 11: Đặt tên cho các bảng và tiêu đề cột trong bảng.
  • Kết quả:
    • Các năm 2021, 2022 và 2024 thuộc nhóm dòng tiền cao, phản ánh khả năng vận hành hiệu quả và tạo tiền mạnh mẽ từ hoạt động cốt lõi. Ngược lại, năm 2023 ghi nhận giá trị âm, doanh nghiệp gặp khó khăn trong việc duy trì dòng tiền từ hoạt động kinh doanh.

2.2.4 Mã hóa khấu hao

q6 <- quantile(bctc$KhauHao, probs = c(0.25, 0.75), na.rm = TRUE)
bctc <- bctc %>% mutate( 
    Nhom_KhauHao = cut( 
      KhauHao, 
      breaks = c(-Inf, q6[1], q6[2], Inf), 
      labels = c("Thấp", "Trung bình", "Cao"), 
      right = TRUE ))
bctc %>%
  select(Năm, KhauHao, Nhom_KhauHao) %>%
 arrange(Năm) %>%
  kbl_default1() %>%
  kable(caption = "Phân loại biên độ khấu hao theo phân vị",
    col.names = c("Năm" ,"Khấu hao" , "Nhóm Khấu Hao"), align = "lrl")
Phân loại biên độ khấu hao theo phân vị
Năm Khấu hao Nhóm Khấu Hao
2015 38.402.557.093 Trung bình
2016 37.320.990.534 Thấp
2017 31.379.088.060 Thấp
2018 30.515.092.412 Thấp
2019 41.208.658.249 Trung bình
2020 53.360.966.033 Trung bình
2021 60.412.122.442 Cao
2022 60.385.696.030 Trung bình
2023 82.642.281.566 Cao
2024 105.636.226.909 Cao
  • Giải thích kỹ thuật:
    • 1: Tính các phân vị thứ 25% và 75% của cột “KhauHao” trong khung dữ liệu bctc sử dụng hàm quantile.
    • 2: Sử dụng hàm mutate kết hợp với cut để tạo một cột mới “Nhom_KhauHao” phân loại khấu hao thành ba nhóm: “Thấp”, “Trung bình” và “Cao” dựa trên các phân vị đã tính.
    • 9: Chọn cột “Năm”, “KhauHao” và “Nhom_KhauHao” để hiển thị.
    • 10: Sắp xếp dữ liệu theo năm.
    • 11: Định dạng bảng theo thiết lập kbl_default1.
    • 12: Đặt tên cho các bảng và tiêu đề cột trong bảng.
  • Kết quả:
    • Các năm 2021, 2023 và 2024 thuộc nhóm khấu hao cao, phản ánh giai đoạn doanh nghiệp đầu tư mạnh vào tài sản cố định hoặc có quy mô tài sản lớn. Ngược lại, các năm 2016 đến 2018 thuộc nhóm thấp, cho thấy mức độ đầu tư hoặc quy mô tài sản cố định còn hạn chế.
    • Việc phân tích khấu hao giúp đánh giá chiến lược đầu tư dài hạn và ảnh hưởng đến chi phí vận hành của doanh nghiệp.

2.2.5 Mã hóa Thuế đã nộp

q7 <- quantile(bctc$Thue_DaNop, probs = c(0.25, 0.75), na.rm = TRUE)
bctc <- bctc %>% mutate( 
    Nhom_ThueDaNop = cut( 
      Thue_DaNop, 
      breaks = c(-Inf, q7[1], q7[2], Inf), 
      labels = c("Cao", "Trung bình", "Thấp"), 
      right = TRUE ))
bctc %>%
  select(Năm, Thue_DaNop, Nhom_ThueDaNop) %>%
  arrange(Năm) %>%
  kbl_default1() %>%
  kable(caption = "Phân loại Thuế đã nộp theo phân vị",
    col.names = c("Năm" ,"Thuế đã nộp", "Nhóm Thuế Đã Nộp"), align = "lrl")
Phân loại Thuế đã nộp theo phân vị
Năm Thuế đã nộp Nhóm Thuế Đã Nộp
2015 -26.094.252.845 Thấp
2016 -22.380.008.319 Thấp
2017 -31.711.798.648 Thấp
2018 -32.083.024.291 Trung bình
2019 -42.339.105.925 Trung bình
2020 -40.856.105.810 Trung bình
2021 -45.310.346.424 Trung bình
2022 -59.623.516.944 Cao
2023 -83.992.016.063 Cao
2024 -70.806.324.518 Cao
  • Giải thích kỹ thuật:
    • 1: Tính các phân vị thứ 25% và 75% của cột “Thue_DaNop” trong khung dữ liệu bctc sử dụng hàm quantile.
    • 2: Sử dụng hàm mutate kết hợp với cut để tạo một cột mới “Nhom_ThueDaNop” phân loại thuế đã nộp thành ba nhóm: “Cao”, “Trung bình” và “Thấp” dựa trên các phân vị đã tính.
    • 9: Chọn cột “Năm”, “Thue_DaNop” và “Nhom_ThueDaNop” để hiển thị.
    • 10: Sắp xếp dữ liệu theo năm.
    • 11: Định dạng bảng theo thiết lập kbl_default1.
    • 12: Đặt tên cho các bảng và tiêu đề cột trong bảng.
  • Kết quả:
    • Các năm 2022 đến 2024 thuộc nhóm thuế cao, phản ánh quy mô hoạt động lớn và mức lợi nhuận cao dẫn đến nghĩa vụ thuế tăng
    • Việc phân tích thuế đã nộp giúp đánh giá mức độ đóng góp tài chính của doanh nghiệp cho ngân sách nhà nước và phản ánh gián tiếp hiệu quả hoạt động kinh doanh.

2.2.6 Hiệu quả hoạt động (Tính Tổng điểm hiệu quả hoạt động)

\[ \text{Tổng điểm} = \text{Điểm lợi nhuận} + \text{Điểm CFO} + \text{Điểm Khấu hao} + \text{Điểm Thuế} \]

bctc <- bctc %>%
  mutate(
    LoiNhuan_diem = recode(Nhom_loi_nhuan, "Thấp" = 1, "Trung bình" = 2, "Cao" = 3),
    CFO_diem      = recode(Nhom_CFO, "Thấp" = 1, "Trung bình" = 2, "Cao" = 3),
    KhauHao_diem  = recode(Nhom_KhauHao, "Thấp" = 1, "Trung bình" = 2, "Cao" = 3),
    Thue_diem     = recode(Nhom_ThueDaNop, "Thấp" = 1, "Trung bình" = 2, "Cao" = 3),
    TongDiem_HieuQua = LoiNhuan_diem + CFO_diem + KhauHao_diem + Thue_diem)
bctc %>%
  select(Năm, TongDiem_HieuQua) %>% arrange(Năm) %>%
  kbl_default1() %>%
  kable(caption = "Tổng điểm hiệu quả hoạt động theo Năm",
     col.names = c("Năm", "Tổng điểm hiệu quả"), align = "rl")
Tổng điểm hiệu quả hoạt động theo Năm
Năm Tổng điểm hiệu quả
2015 6
2016 5
2017 5
2018 7
2019 7
2020 7
2021 10
2022 11
2023 10
2024 12
  • Giải thích kỹ thuật:
    • 2: Sử dụng hàm mutate để tạo các cột điểm số cho từng chỉ số hiệu quả hoạt động dựa trên nhóm đã mã hóa trước đó.
    • 3 - 6: Sử dụng hàm recode để chuyển đổi nhóm thành điểm số tương ứng (Thấp = 1, Trung bình = 2, Cao = 3).
    • 7: Tính tổng điểm hiệu quả hoạt động bằng cách cộng các điểm số của từng chỉ số.
    • 9: Chọn cột “Năm” và “TongDiem_HieuQua” để hiển thị.
    • 9: Sắp xếp dữ liệu theo năm.
    • 10: Định dạng bảng theo thiết lập kbl_default1.
    • 11: Đặt tên cho các bảng và tiêu đề cột trong bảng.
  • Kết quả:
    • Tổng điểm hiệu quả hoạt động được xây dựng dựa trên việc phân loại và quy đổi các chỉ số tài chính trọng yếu thành điểm số.
    • Kết quả cho thấy xu hướng tăng trưởng rõ rệt từ năm 2021 trở đi, với năm 2024 đạt mức điểm cao nhất (12 điểm), phản ánh hiệu quả toàn diện về lợi nhuận, dòng tiền, đầu tư tài sản và nghĩa vụ thuế.

2.2.7 Phân loại nhóm hiệu quả hoạt động

bctc <- bctc %>%
  mutate(
    Nhom_HieuQua = case_when(
      TongDiem_HieuQua <= 5 ~ "Thấp",
      TongDiem_HieuQua <= 8 ~ "Trung bình",
      TRUE ~ "Cao"))
bctc %>%
  select(Năm, TongDiem_HieuQua, Nhom_HieuQua) %>%
  arrange(Năm) %>%
  kbl_default1() %>%
  kable(caption = "Phân loại nhóm hiệu quả hoạt động theo tổng điểm",
    col.names = c("Năm" ,"Tổng điểm hiệu quả", "Nhóm Hiệu Quả"), align = "lrl") %>%
  kable_styling(latex_options = c("hold_position", "scale_down"),
 position = "center")
Phân loại nhóm hiệu quả hoạt động theo tổng điểm
Năm Tổng điểm hiệu quả Nhóm Hiệu Quả
2015 6 Trung bình
2016 5 Thấp
2017 5 Thấp
2018 7 Trung bình
2019 7 Trung bình
2020 7 Trung bình
2021 10 Cao
2022 11 Cao
2023 10 Cao
2024 12 Cao
  • Giải thích kỹ thuật:
    • 2: Sử dụng hàm mutate kết hợp với case_when để tạo một cột mới “Nhom_HieuQua” phân loại hiệu quả hoạt động thành ba nhóm: “Thấp”, “Trung bình” và “Cao” dựa trên tổng điểm hiệu quả hoạt động.
    • 3: case_when(): để phân loại hiệu quả hoạt động
    • 8: Chọn cột “Năm”, “TongDiem_HieuQua” và “Nhom_HieuQua” để hiển thị.
    • 9: Sắp xếp dữ liệu theo năm.
    • 10: Định dạng bảng theo thiết lập kbl_default1.
    • 12: Đặt tên cho các bảng và tiêu đề cột trong bảng. *Kết quả:
  • Kết quả phân loại nhóm hiệu quả hoạt động dựa trên tổng điểm cho thấy sự cải thiện liên tục từ năm 2020 trở đi. Năm 2021 đánh dấu bước chuyển mình quan trọng khi doanh nghiệp đạt nhóm hiệu quả “Cao” lần đầu tiên, tiếp tục duy trì và nâng cao hiệu quả trong các năm sau đó. Điều này phản ánh sự phát triển bền vững và khả năng quản trị tài chính hiệu quả của doanh nghiệp IMP.

2.2.7.1 Tổng hợp các Chỉ số Hiệu quả hoạt động kinh doanh đã được Mã hóa theo Năm

bctc %>%
  select(Năm, Nhom_loi_nhuan, Nhom_CFO, Nhom_KhauHao, 
         Nhom_ThueDaNop, TongDiem_HieuQua, Nhom_HieuQua) %>% arrange(Năm) %>%
  kbl_default1() %>%
  kable( format = "latex", booktabs = TRUE, 
  caption = "Tổng hợp các chỉ số hiệu quả hoạt động kinh doanh đã được mã hóa theo Năm",
      col.names = c("Năm", "Nhóm Lợi nhuận", "Nhóm CFO", "Nhóm Khấu hao",
                    "Nhóm Thuế đã nộp", "Tổng điểm hiệu quả", "Nhóm Hiệu quả"),
  align = "lllllrl") %>%
  kable_styling(latex_options = c("hold_position", "scale_down"),
 position = "center")
  • Giải thích kỹ thuật:
    • 2: Chọn các cột liên quan đến nhóm hiệu quả hoạt động đã được mã hóa và tổng điểm từ khung dữ liệu bctc.
    • 3: Sắp xếp dữ liệu theo năm.
    • 4: Định dạng bảng theo thiết lập kbl_default1.
    • 5: Đặt tên cho các bảng và tiêu đề cột trong bảng.
    • 9: căn trái phải các cột.
    • 10: Sử dụng hàm kable_styling để tùy chỉnh bảng, giữ bảng ở đúng vị trí,tự động thu nhỏ bảng nếu bảng quá rộng.
    • 11: Căn giữa bảng trong trang.
  • Kết quả tổng hợp cho thấy sự cải thiện liên tục về hiệu quả hoạt động kinh doanh của doanh nghiệp IMP qua các năm. Từ năm 2021 trở đi, doanh nghiệp đã đạt được nhóm hiệu quả “Cao”, phản ánh sự tăng trưởng mạnh mẽ về lợi nhuận, dòng tiền, đầu tư tài sản và nghĩa vụ thuế. Việc duy trì và nâng cao hiệu quả này sẽ là yếu tố then chốt để đảm bảo sự phát triển bền vững trong tương lai.

2.2.8 Tỷ lệ chất lượng thu nhập (Tạo biến QOE - Quality of Earnings Ratio)

  • Ý nghĩa: QOE (Quality of Earnings) dùng để đánh giá chất lượng lợi nhuận:
  • Nếu QOE > 1,0x → Thu nhập có chất lượng cao hơn.
  • Nếu QOE < 1,0x → Thu nhập có chất lượng thấp hơn.
  • Công thức tính QOE: \[\text{QOE} = \frac{\text{CFO Tổng}}{\text{Lợi nhuận sau thuế}}\]
bctc <- bctc %>%
  mutate(
    QOE = ifelse(LoiNhuanSauThue != 0, CFO_Tong / LoiNhuanSauThue, NA))
bctc %>%
  select(Năm, CFO_Tong, LoiNhuanSauThue, QOE) %>%
  arrange(Năm) %>%
   kbl_default1() %>%
  kable(caption = "Chất lượng thu nhập (QOE)",
      col.names = c("Năm", "Dòng tiền từ hoạt động kinh doanh (CFO Tổng)", 
                    "thuế", "QOE"), align = "lrrr")
Chất lượng thu nhập (QOE)
Năm Dòng tiền từ hoạt động kinh doanh (CFO Tổng) thuế QOE
2015 81.130.888.888 92.275.349.999 0,8792260
2016 84.650.650.055 101.159.344.647 0,8368050
2017 97.212.899.364 117.360.040.786 0,8283305
2018 132.094.454.149 138.683.041.628 0,9524918
2019 66.590.387.415 162.386.686.793 0,4100729
2020 73.153.422.780 209.696.878.289 0,3488532
2021 234.881.036.040 189.094.874.963 1,2421333
2022 378.603.401.448 223.540.317.602 1,6936694
2023 -30.308.428.665 299.556.005.542 -0,1011778
2024 216.267.565.018 320.862.393.082 0,6740197
  • Giải thích kỹ thuật:
    • 2: Sử dụng hàm mutate để tạo một cột mới “QOE” trong khung dữ liệu bctc.
    • 3: Sử dụng hàm ifelse để tính QOE bằng cách chia “CFO_Tong” cho “LoiNhuanSauThue”, đồng thời kiểm tra điều kiện để tránh chia cho 0.
    • 5: Chọn cột “Năm”, “CFO_Tong”, “LoiNhuanSauThue” và “QOE” để hiển thị.
    • 6: Sắp xếp dữ liệu theo năm.
    • 7: Định dạng bảng theo thiết lập kbl_default1.
    • 8: Đặt tên cho các bảng và tiêu đề cột trong bảng.
  • Giai đoạn 2015–2019: QOE dao động quanh mức 0.5–0.8: Chất lượng thu nhập trung bình đến thấp, lợi nhuận không được hỗ trợ tốt bởi dòng tiền từ hoạt động kinh doanh.
  • Phân tích chuỗi thời gian cho thấy QOE có xu hướng tăng mạnh từ năm 2020 đến 2022, phản ánh sự cải thiện rõ rệt về chất lượng thu nhập. Tuy nhiên, năm 2023 ghi nhận QOE âm, là dấu hiệu cần được xem xét kỹ lưỡng.

2.2.8.1 Phân loại chất lượng thu nhập

bctc <- bctc %>%
  mutate(QOE_Nhom = case_when(
    QOE >= 1 ~ "Chất lượng cao",
    QOE < 1 & QOE >= 0.7 ~ "Chất lượng trung bình",
    TRUE ~ "Chất lượng thấp"))
bctc %>%
  select(Năm, QOE, QOE_Nhom) %>%
  arrange(Năm) %>%
  kbl_default1() %>%
  kable(caption = "Phân loại chất lượng lợi nhuận",
    col.names = c("Năm" ,"QOE", "Kết quả"), align = "rrl")
Phân loại chất lượng lợi nhuận
Năm QOE Kết quả
2015 0,8792260 Chất lượng trung bình
2016 0,8368050 Chất lượng trung bình
2017 0,8283305 Chất lượng trung bình
2018 0,9524918 Chất lượng trung bình
2019 0,4100729 Chất lượng thấp
2020 0,3488532 Chất lượng thấp
2021 1,2421333 Chất lượng cao
2022 1,6936694 Chất lượng cao
2023 -0,1011778 Chất lượng thấp
2024 0,6740197 Chất lượng thấp
  • Giải thích kỹ thuật:
    • 2: Sử dụng hàm mutate kết hợp với case_when để tạo một cột mới “QOE_Nhom” phân loại chất lượng thu nhập thành ba nhóm: “Chất lượng cao”, “Chất lượng trung bình” và “Chất lượng thấp” dựa trên giá trị QOE.
    • 7: Chọn cột “Năm”, “QOE” và “QOE_Nhom” để hiển thị.
    • 8: Sắp xếp dữ liệu theo năm.
    • 9: Định dạng bảng theo thiết lập kbl_default1.
    • 10: Đặt tên cho các bảng và tiêu đề cột trong bảng.
  • Kết quả:
    • Thu nhập chất lượng cao lợi nhuận sau thuế có tính ổn định, bền vững, được hỗ trợ tốt bởi dòng tiền từ hoạt động kinh doanh (CFO).
    • Nếu QOE < 1, tức là lợi nhuận cao nhưng không đi kèm dòng tiền thật, dễ bị điều chỉnh hoặc biến động, nên chất lượng thấp.

2.2.9 Dòng tiền tự do (Free Cash Flow - FCF)

  • Ý nghĩa:
  • FCF cho biết công ty thật sự còn bao nhiêu tiền sau khi duy trì tài sản.
  • FCF dương: công ty có khả năng mở rộng, trả nợ, chia cổ tức.
  • FCF âm: đang đầu tư mạnh hoặc có rủi ro thiếu tiền.
  • Công thức tính FCF: \[\text{FCF} = \text{CFO\_Tổng} - \text{Chi\_MuaTSCD}\]
bctc <- bctc %>% 
  mutate( FCF = CFO_Tong - (- Chi_MuaTSCD))
bctc %>%
  select(Năm, CFO_Tong, Chi_MuaTSCD, FCF) %>%
  arrange(Năm) %>%
  kbl_default1() %>%
  kable(caption = "Dòng tiền tự do (FCF)",
      col.names = c("Năm", "Dòng tiền từ hoạt động kinh doanh (CFO Tổng)",
                    "Chi Mua TSCD", "FCF"), align = "rrrr")
Dòng tiền tự do (FCF)
Năm Dòng tiền từ hoạt động kinh doanh (CFO Tổng) Chi Mua TSCD FCF
2015 81.130.888.888 -116.451.196.931 -35.320.308.043
2016 84.650.650.055 -103.904.028.053 -19.253.377.998
2017 97.212.899.364 -274.456.614.870 -177.243.715.506
2018 132.094.454.149 -272.440.024.143 -140.345.569.994
2019 66.590.387.415 -131.124.961.007 -64.534.573.592
2020 73.153.422.780 -89.684.662.322 -16.531.239.542
2021 234.881.036.040 -52.856.687.303 182.024.348.737
2022 378.603.401.448 -99.353.189.519 279.250.211.929
2023 -30.308.428.665 -63.529.641.231 -93.838.069.896
2024 216.267.565.018 -96.770.733.614 119.496.831.404
  • Giải thích kỹ thuật:
    • 2: Sử dụng hàm mutate để tạo một cột mới “FCF” trong khung dữ liệu bctc.
    • 2: Tính FCF bằng cách lấy “CFO_Tong” trừ đi giá trị âm của “Chi_MuaTSCD” (do chi mua tài sản cố định thường được ghi nhận là số âm trong báo cáo dòng tiền).
    • 4: Chọn cột “Năm”, “CFO_Tong”, “Chi_MuaTSCD” và “FCF” để hiển thị.
    • 5: Sắp xếp dữ liệu theo năm.
    • 6: Định dạng bảng theo thiết lập kbl_default1.
    • 7: Đặt tên cho các bảng và tiêu đề cột trong bảng.
  • Kết quả:
    • Giai đoạn 2015–2020: FCF liên tục âm doanh nghiệp đang trong giai đoạn đầu tư mạnh vào tài sản cố định, chấp nhận “rút tiền túi” để xây nền.
    • Giai đoạn 2021–2022: FCF dương lớn đầu tư bắt đầu sinh quả ngọt, dòng tiền vượt chi phí đầu tư cực kỳ tích cực.
    • Năm 2023: FCF âm trở lại có thể do dòng tiền vận hành yếu hoặc đầu tư đột biến cần kiểm tra nguyên nhân.
    • Năm 2024: FCF dương tín hiệu phục hồi, doanh nghiệp quay lại trạng thái sinh tiền sau đầu tư.

2.2.9.1 Phân loại dòng tiền tự do

bctc <- bctc %>%
  mutate(FCF_Nhom = case_when(
    FCF > 0 ~ "Dòng tiền dương",
    FCF <= 0 ~ "Dòng tiền âm"))
bctc %>%
  select(Năm, FCF, FCF_Nhom) %>%
  arrange(Năm) %>%
  kbl_default1() %>%
  kable(caption = "Phân loại dòng tiền tự do theo FCF",
    col.names = c("Năm" ,"FCF", "Kết quả"), align = "rrl")
Phân loại dòng tiền tự do theo FCF
Năm FCF Kết quả
2015 -35.320.308.043 Dòng tiền âm
2016 -19.253.377.998 Dòng tiền âm
2017 -177.243.715.506 Dòng tiền âm
2018 -140.345.569.994 Dòng tiền âm
2019 -64.534.573.592 Dòng tiền âm
2020 -16.531.239.542 Dòng tiền âm
2021 182.024.348.737 Dòng tiền dương
2022 279.250.211.929 Dòng tiền dương
2023 -93.838.069.896 Dòng tiền âm
2024 119.496.831.404 Dòng tiền dương
  • Giải thích kỹ thuật:
    • 2: Sử dụng hàm mutate kết hợp với case_when để tạo một cột mới “FCF_Nhom” phân loại dòng tiền tự do thành hai nhóm: “Dòng tiền dương” và “Dòng tiền âm” dựa trên giá trị FCF.
    • 6: Chọn cột “Năm”, “FCF” và “FCF_Nhom” để hiển thị.
    • 7: Sắp xếp dữ liệu theo năm.
    • 8: Định dạng bảng theo thiết lập kbl_default1.
    • 9: Đặt tên cho các bảng và tiêu đề cột trong bảng.
  • Kết quả:
    • Dòng tiền tự do là thước đo quan trọng để đánh giá khả năng tạo giá trị thực của doanh nghiệp. IMP đã trải qua giai đoạn đầu tư mạnh mẽ, chấp nhận FCF âm để mở rộng năng lực sản xuất. Giai đoạn 2021–2022 cho thấy hiệu quả đầu tư rõ rệt với FCF dương vượt trội.
    • Việc duy trì FCF dương trong năm 2024 là tín hiệu tích cực, tuy nhiên cần tiếp tục theo dõi các yếu tố vận hành để đảm bảo tính bền vững của dòng tiền trong dài hạn.

2.2.10 Vốn lưu động thuần (Net Working Capital – NWC)

  • Ý nghĩa:
    • Xác định doanh nghiệp có bị kẹt vốn hay được chiếm dụng vốn.
      • ΔNWC dương → kẹt vốn
      • ΔNWC âm → được tài trợ bởi nhà cung cấp *Công thức tính ΔNWC: \[ \Delta NWC = \Delta \text{Phải thu} + \Delta \text{Tồn kho} - \Delta \text{Phải trả} \]
bctc <- bctc %>% 
  mutate(Delta_NWC = BienDong_PhaiThu + BienDong_TonKho - BienDong_PhaiTra)
bctc %>%
  select(Năm, BienDong_PhaiThu, BienDong_TonKho,  BienDong_PhaiTra, Delta_NWC) %>%
  arrange(Năm) %>%
   kbl_default1() %>%
   kable(caption = "Vốn lưu động thuần (Net Working Capital - NWC)",
       col.names = c("Năm", "Biến Động Phải thu", "Biến Động Tồn kho",
                     "Biến Động Phải trả", "ΔNWC"), align = "rrrrr")
Vốn lưu động thuần (Net Working Capital - NWC)
Năm Biến Động Phải thu Biến Động Tồn kho Biến Động Phải trả ΔNWC
2015 -41.333.661.601 50.382.333.195 -35.669.331.173 44.718.002.767
2016 -8.928.274.802 10.749.182.912 -25.111.605.021 26.932.513.131
2017 -1.437.631.837 -47.523.201.453 23.575.925.481 -72.536.758.771
2018 22.334.105.781 -43.797.978.420 -13.724.008.827 -7.739.863.812
2019 -64.061.938.449 -25.446.448.742 6.749.916.226 -96.258.303.417
2020 -60.835.559.336 -75.247.048.473 -49.525.833.086 -86.556.774.723
2021 115.759.490.658 -66.128.977.456 -2.831.326.127 52.461.839.329
2022 20.135.831.138 52.398.387.880 39.579.670.960 32.954.548.058
2023 -19.991.962.534 -260.749.238.157 -38.673.957.017 -242.067.243.674
2024 -83.306.984.898 -6.180.988.401 20.277.800.636 -109.765.773.935
  • Giải thích kỹ thuật:
    • 2: Sử dụng hàm mutate để tạo một cột mới “Delta_NWC” trong khung dữ liệu bctc.
    • 2: Tính ΔNWC bằng cách cộng biến động phải thu và biến động tồn kho, sau đó trừ đi biến động phải trả.
    • 4: Chọn cột “Năm”, “BienDong_PhaiThu”, “BienDong_TonKho”, “BienDong_PhaiTra” và “Delta_NWC” để hiển thị.
    • 5: Sắp xếp dữ liệu theo năm.
    • 6: Định dạng bảng theo thiết lập kbl_default1.
    • 7: Đặt tên cho các bảng và tiêu đề cột trong bảng.
  • Kết quả:
    • Biến động vốn lưu động thuần của IMP cho thấy sự linh hoạt trong chiến lược quản lý tài sản ngắn hạn.
    • Giai đoạn 2017–2020 và 2023–2024 là những thời điểm doanh nghiệp chủ động giải phóng dòng tiền từ vốn lưu động, hỗ trợ thanh khoản và hiệu quả vận hành.
    • Ngược lại, các năm 2015–2016 và 2021–2022 phản ánh xu hướng mở rộng quy mô, sử dụng tiền mặt để tăng tài sản lưu động.
    • Việc kiểm soát NWC đóng vai trò quan trọng trong cân bằng giữa tăng trưởng và dòng tiền thực tế.

2.2.10.1 Phân loại vốn lưu động thuần

bctc <- bctc %>%
  mutate(NWC_Nhom = case_when(
    Delta_NWC > 0 ~ "Kẹt vốn",
    Delta_NWC <= 0 ~ "Được chiếm dụng vốn"))
bctc %>%
  select(Năm, Delta_NWC, NWC_Nhom) %>%
  arrange(Năm) %>%  kbl_default1() %>%
  kable(caption = "Phân loại vốn lưu động thuần theo ΔNWC",
    col.names = c("Năm" ,"ΔNWC", "Kết quả"), align = "rrl")
Phân loại vốn lưu động thuần theo ΔNWC
Năm ΔNWC Kết quả
2015 44.718.002.767 Kẹt vốn
2016 26.932.513.131 Kẹt vốn
2017 -72.536.758.771 Được chiếm dụng vốn
2018 -7.739.863.812 Được chiếm dụng vốn
2019 -96.258.303.417 Được chiếm dụng vốn
2020 -86.556.774.723 Được chiếm dụng vốn
2021 52.461.839.329 Kẹt vốn
2022 32.954.548.058 Kẹt vốn
2023 -242.067.243.674 Được chiếm dụng vốn
2024 -109.765.773.935 Được chiếm dụng vốn
  • Giải thích kỹ thuật:
    • 2: Sử dụng hàm mutate kết hợp với case_when để tạo một cột mới “NWC_Nhom” phân loại vốn lưu động thuần thành hai nhóm: “Kẹt vốn” và “Được chiếm dụng vốn” dựa trên giá trị ΔNWC.
    • 6: Chọn cột “Năm”, “Delta_NWC” và “NWC_Nhom” để hiển thị.
    • 7: Sắp xếp dữ liệu theo năm.
    • 7: Định dạng bảng theo thiết lập kbl_default1.
    • 8: Đặt tên cho các bảng và tiêu đề cột trong bảng.
  • Kết quả:
    • Phân loại vốn lưu động thuần theo ΔNWC cho thấy doanh nghiệp IMP đã có những giai đoạn sử dụng vốn lưu động để mở rộng hoạt động (kẹt vốn), xen kẽ với các giai đoạn tối ưu hóa tài sản ngắn hạn để hỗ trợ dòng tiền (được chiếm dụng vốn).
    • Việc kiểm soát ΔNWC đóng vai trò quan trọng trong cân bằng giữa tăng trưởng và khả năng thanh toán ngắn hạn, đặc biệt trong bối cảnh đầu tư và biến động thị trường.

2.3 Phân tích dữ liệu và trực quan hóa dữ liệu

2.3.1 Phân phối các chỉ số hiệu quả hoạt động kinh doanh

plots_hist <- list(
  ggplot(bctc, aes(x = LoiNhuanSauThue / 1e9 , fill = ..count..)) +
    geom_histogram(bins = 10, color = "white") +
    scale_fill_gradient(low = "#caf0f8", high = "#0077b6") +
    labs(title = "Phân phối Lợi nhuận sau thuế", x = "Lợi nhuận sau thuế (tỷ đồng) ",
         y = "Tần suất") +
    scale_x_continuous(labels = format_vn) +
    theme_minimal() +
    theme(
      plot.title = element_text(face = "bold", hjust = 0.5, size = 12),
      axis.title = element_text(size = 12)),
  ggplot(bctc, aes(x = CFO_Tong / 1e9 , fill = ..count..)) +
    geom_histogram(bins = 10, color = "white") +
    scale_fill_gradient(low = "#ade8f4", high = "#023e8a") +
    labs(title = "Phân phối Dòng tiền hoạt động (CFO)", x = "CFO Tổng  (tỷ đồng) ",
         y = "Tần suất") +
    scale_x_continuous(labels = format_vn) +
    theme_minimal() +
    theme(
      plot.title = element_text(face = "bold", hjust = 0.5, size = 12),
      axis.title = element_text(size = 12)))
grid.arrange(
  grobs = plots_hist,ncol = 1, 
  top = textGrob(
    "Phân phối các chỉ số hiệu quả hoạt động kinh doanh",
    gp = grid::gpar(fontsize = 16, fontface = "bold")))

  • Giải thích Kỹ thuật:
    • 2; 12: Tạo biểu đồ và (fill= ..count..) dùng để tô màu theo tần suất.
    • 3; 13: Biểu đồ histogram với 10 cột, viền trắng.
    • 4; 14: Tạo dải màu từ xanh nhạt đến xanh đậm theo số lượng.
    • 5; 15: Đặt tiêu đề và nhãn trục.
    • 7; 17: Định dạng trục x theo chuẩn Việt Nam.
    • 8: Áp dụng giao diện tối giản.
    • 9: Tùy chỉnh font tiêu đề và trục: in đậm, căn giữa, cỡ chữ 12.
    • 22 - 26: Sắp xếp hai biểu đồ theo cột, thêm tiêu đề chính cho toàn bộ biểu đồ.
  • Kết quả:
    • Hiệu quả hoạt động của nhóm được khảo sát là không đồng đều. Lợi nhuận sau thuế phân thành hai nhóm riêng biệt, trong khi Dòng tiền hoạt động cho thấy đa số ở mức thấp nhưng có một vài trường hợp đột biến tạo ra dòng tiền rất mạnh.
plots_hist1 <- list( ggplot(bctc, aes(x = KhauHao / 1e9 , fill = ..count..)) +
    geom_histogram(bins = 10, color = "white") +
    scale_fill_gradient(low = "#90e0ef", high = "#0077b6") +
    labs(title = "Phân phối Khấu hao", x = "Khấu hao  (tỷ đồng)", y = "Tần suất") +
      scale_x_continuous(labels = format_vn) +
    theme_minimal() +
    theme(
      plot.title = element_text(face = "bold", hjust = 0.5, size = 14),
      axis.title = element_text(size = 12)),
  ggplot(bctc, aes(x = FCF / 1e9 , fill = ..count..)) +
    geom_histogram(bins = 10, color = "white") +
    scale_fill_gradient(low = "#48cae4", high = "#03045e") +
    labs(title = "Phân phối Dòng tiền tự do (FCF)", x = "FCF  (tỷ đồng) ",
         y = "Tần suất") +
    scale_x_continuous(labels = format_vn) +
    theme_minimal() +
    theme(
      plot.title = element_text(face = "bold", hjust = 0.5, size = 14),
      axis.title = element_text(size = 12)))
grid.arrange(grobs = plots_hist1, ncol = 1,
  top = textGrob(
    "Phân phối các chỉ số hiệu quả hoạt động kinh doanh (tiếp)",
    gp = gpar(fontsize = 16, fontface = "bold")))

  • Kết quả:
    • Phát hiện quan trọng nhất là đa số các quan sát (phần lớn dữ liệu) đều có Dòng tiền tự do (FCF) âm, với đỉnh phân phối nằm trong khoảng từ -100 tỷ đến 0 tỷ đồng.
    • Điều này cho thấy một tình trạng “đốt tiền” (cash burn) rõ rệt, khi chi tiêu vốn (CapEx) đang vượt quá dòng tiền tạo ra từ hoạt động kinh doanh. Trong khi đó, chi phí khấu hao (một khoản chi không dùng tiền) chỉ phân thành hai nhóm chính (quanh 40 tỷ và 60 tỷ), không có gì bất thường.

2.3.2 Tỷ lệ chất lượng thu nhập trung bình theo Năm

QOE_nam <- bctc %>%
  group_by(Năm) %>%
  summarise(QOE_trungbinh  = mean(QOE, na.rm = TRUE)) %>%
            mutate(QOE_trungbinh = round(QOE_trungbinh * 100, 2)) 
QOE_nam %>% arrange(Năm) %>%
  kbl_default1() %>%
  kable(caption = "Tỷ lệ chất lượng thu nhập (QOE) trung bình theo Năm",
    col.names = c("Năm", "QOE trung bình (%)"), align = "rr")
Tỷ lệ chất lượng thu nhập (QOE) trung bình theo Năm
Năm QOE trung bình (%)
2015 87,92
2016 83,68
2017 82,83
2018 95,25
2019 41,01
2020 34,89
2021 124,21
2022 169,37
2023 -10,12
2024 67,40
  • Giải thích kỹ thuật:
    • 1: Tạo bảng mới
    • 2: Nhóm dữ liệu theo “Năm” và tính toán giá trị trung bình của “QOE” cho mỗi năm, bỏ qua các giá trị NA.
    • 3: Làm tròn giá trị trung bình của QOE sau khi nhân với 100 để chuyển đổi thành phần trăm.
    • 5: Sắp xếp dữ liệu theo năm.
    • 6: Định dạng bảng theo thiết lập kbl_default1.
    • 7: Đặt tên cho các bảng và tiêu đề cột trong bảng.
  • Kết quả:
    • Tỷ lệ chất lượng thu nhập trung bình là chỉ số phản ánh tính thực chất của lợi nhuận. IMP đã có giai đoạn vận hành hiệu quả vượt chuẩn (2021–2022), nhưng cũng trải qua thời kỳ rủi ro về chất lượng thu nhập (2019–2020, 2023). Việc duy trì QOE ở mức cao là yếu tố then chốt để đảm bảo lợi nhuận không chỉ là con số kế toán mà còn là giá trị thực tế có thể chuyển hóa thành tiền mặt.

2.3.3 Xu hướng QOE theo năm

QOE_nam %>%
  ggplot(aes(x = Năm, y = QOE_trungbinh)) +
  geom_line(color = "red", size = 1.2) +
  geom_point(color = "#CC0000", size = 3) +
  geom_area(fill = "pink", alpha = 0.5) +
  geom_text(
    aes(label = format_vn(QOE_trungbinh, 2)), vjust = -0.8, size = 4, color = "black",               
    fontface = "bold") +
  geom_hline(yintercept = 0, linetype = "dashed", color = "#CC0000") +
  scale_y_continuous(limits = c(min(0, QOE_nam$QOE_trungbinh), 
                                max(QOE_nam$QOE_trungbinh) * 1.1)) +
  scale_x_continuous(breaks = breaks_pretty(n = 8)) +
  labs(title = "XU HƯỚNG CHẤT LƯỢNG THU NHẬP THEO NĂM",
    x = "Năm",
    y = "QOE trung bình (%)") +
  theme_minimal() +
  theme(plot.title = element_text(
      face = "bold",
      hjust = 0.5,     
      size = 16,
      color = "#2C3E50"),
    axis.title = element_text(size = 13),
    axis.text = element_text(size = 11))

  • Giải thích kỹ thuật:
    • 1-2: Thiết lập trục hoành là “Năm” và trục tung là “QOE_trungbinh”.
    • 3: Vẽ đường nối các điểm QOE trung bình theo năm với màu đỏ và độ dày 1.2.
    • 4: Thêm các điểm dữ liệu với màu đỏ đậm và kích thước 3.
    • 5: Tô màu vùng dưới đường xu hướng bằng màu hồng nhạt với độ trong suốt 0.5.
    • 6: Thêm nhãn giá trị QOE trung bình trên mỗi điểm dữ liệu, căn chỉnh phía trên điểm.
    • 9: Vẽ đường ngang tại y = 0 để làm nổi bật mức cơ sở.
    • 10: Điều chỉnh giới hạn trục y để bao gồm giá trị âm nếu có, và mở rộng tối đa lên 10% so với giá trị lớn nhất.
    • 12: Chia trục x thành các mốc đẹp với số lượng khoảng 8.
    • 13: Đặt các nhãn cho tiêu đề biểu đồ và trục.
    • 16: Áp dụng giao diện tối giản và tùy chỉnh font chữ cho tiêu đề và trục.
  • Kết quả:
    • Chất lượng thu nhập của đối tượng phân tích cực kỳ bất ổn. Điểm rủi ro lớn nhất là năm 2023 khi chỉ số QOE âm, cho thấy lợi nhuận trên giấy tờ hoàn toàn không được hỗ trợ bằng tiền mặt, tiềm ẩn nguy cơ nghiêm trọng về thanh khoản hoặc các vấn đề trong quản lý vốn lưu động.

2.3.4 Dòng tiền tự do (FCF) trung bình theo Năm

FCF_nam <- bctc %>%
  group_by(Năm) %>%
  summarise(FCF_trungbinh = mean(FCF, na.rm = TRUE)) %>%
            mutate(FCF_trungbinh = round(FCF_trungbinh)) 
FCF_nam %>% arrange(Năm) %>%
  kbl_default1() %>%
  kable(caption = "Dòng tiền tự do (FCF) trung bình theo Năm",
    col.names = c("Năm", "FCF trung bình (VND)"), align = "rr")
Dòng tiền tự do (FCF) trung bình theo Năm
Năm FCF trung bình (VND)
2015 -35.320.308.043
2016 -19.253.377.998
2017 -177.243.715.506
2018 -140.345.569.994
2019 -64.534.573.592
2020 -16.531.239.542
2021 182.024.348.737
2022 279.250.211.929
2023 -93.838.069.896
2024 119.496.831.404
  • Giải thích kỹ thuật:
    • 1: Tạo bảng mới
    • 2: Nhóm dữ liệu theo “Năm” và tính toán giá trị trung bình của “FCF” cho mỗi năm, bỏ qua các giá trị NA.
    • 3: Tính trung bình của biến FCF và bỏ qua giá trị NA.
    • 4: Làm tròn giá trị trung bình của FCF.
    • 5: Sắp xếp dữ liệu theo năm.
    • 6: Định dạng bảng theo thiết lập kbl_default1.
    • 7: Đặt tên cho các bảng và tiêu đề cột trong bảng.
  • Kết quả:
    • FCF trung bình là chỉ số phản ánh khả năng tạo ra tiền mặt thực tế sau đầu tư. IMP đã trải qua giai đoạn đầu tư mạnh mẽ, chấp nhận FCF âm để mở rộng năng lực sản xuất.
    • Giai đoạn 2021–2022 cho thấy hiệu quả đầu tư rõ rệt với FCF dương vượt trội. Việc duy trì FCF dương trong năm 2024 là tín hiệu tích cực, tuy nhiên cần tiếp tục theo dõi các yếu tố vận hành để đảm bảo tính bền vững của dòng tiền trong dài hạn.

2.3.5 Xu hướng FCF theo năm

FCF_nam %>%
  ggplot(aes(x = Năm, y = FCF_trungbinh / 1e9 )) +
  geom_area(fill = "lightgreen", alpha = 0.5) +
  geom_hline(yintercept = 0, linetype = "dashed", color = "red", size = 0.8) +
  geom_line(color = "darkgreen", size = 1.2) +
  geom_point(color = "#003300", size = 3) +
  geom_text(aes(
    label = paste0(round(FCF_trungbinh / 1e9, 2), " tỷ"),
    y = FCF_trungbinh / 1e9,),
    vjust = ifelse(FCF_nam$FCF_trungbinh >= 0, -1, 2),
  size = 4,
  color = "#000",
  fontface = "bold") +
  scale_x_continuous(breaks = unique(FCF_nam$Năm)) +
  scale_y_continuous(labels = scales::comma,
      limits = c(-250, 300)) +
  labs(
    title = "XU HƯỚNG DÒNG TIỀN TỰ DO THEO NĂM",
    x = "Năm",
    y = "FCF trung bình (tỷ đồng)") +
  theme_minimal() +
  theme(
    plot.title = element_text(face = "bold", hjust = 0.5, size = 16, color = "#2C3E50"),
    axis.title = element_text(size = 13),
    axis.text = element_text(size = 11))

  • Giải thích kỹ thuật:
    • 1: Thiết lập trục hoành là “Năm” và trục tung là “FCF_trungbinh” chia cho 1e9 để chuyển đổi sang tỷ đồng.
    • 3: Vẽ vùng dưới đường xu hướng với màu xanh lá cây nhạt và độ trong suốt 0.5.
    • 4: Vẽ đường ngang tại y = 0 để làm nổi bật mức cơ sở, với đường nét đứt và màu đỏ.
    • 5: Vẽ đường nối các điểm FCF trung bình theo năm với màu xanh đậm và độ dày 1.2.
    • 6: Thêm các điểm dữ liệu với màu xanh đậm và kích thước 3.
    • 7: Thêm nhãn giá trị FCF trung bình trên mỗi điểm dữ liệu, căn chỉnh phía trên hoặc dưới điểm tùy thuộc vào giá trị dương hay âm.
    • 14: Chia trục x thành các mốc tương ứng với các năm có trong dữ liệu.
    • 15: Định dạng trục y với dấu phẩy phân tách hàng nghìn và giới hạn từ -250 đến 300 tỷ đồng.
    • 17: Đặt các nhãn cho tiêu đề biểu đồ và trục.
    • 21: Áp dụng giao diện tối giản và tùy chỉnh font chữ cho tiêu đề và trục.
  • Kết quả:
    • Khả năng tạo ra dòng tiền tự do của doanh nghiệp là rất thất thường và có rủi ro cao. Việc FCF âm sâu trong nhiều năm, theo sau là sự bùng nổ ngắn ngủi rồi lại rơi vào mức âm (năm 2023) cho thấy sự phụ thuộc lớn vào các yếu tố chu kỳ, dự án lớn hoặc khả năng quản lý chi tiêu vốn chưa ổn định.

2.3.6 Mối quan hệ giữa chất lượng thu nhập (QOE) và dòng tiền tự do (FCF)

2.3.6.1 Thống kê mô tả hai biến QOE và FCF

QF <- bctc %>%
  select(QOE, FCF) %>%
  skim() %>%
  select(skim_variable, numeric.mean, numeric.sd, numeric.p0, numeric.p100)
QF %>%
  kbl_default1() %>%
  kable(caption = "Thống kê mô tả của hai biến QOE và FCF",
    col.names = c("Biến", "Trung bình", "Độ lệch chuẩn", "Min", "Max"), 
    align = "lrrrr")
Thống kê mô tả của hai biến QOE và FCF
Biến Trung bình Độ lệch chuẩn Min Max
QOE 0,7764424 0,4948439 -0,1011778 1,693669
FCF 3.370.453.749,9000001 145.775.110.047,9837646 -177.243.715.506,0000000 279.250.211.929,000000
  • Giải thích kỹ thuật:
    • 1: Tạo bảng QF bằng cách chọn hai biến số: QOE và FCF từ bảng bctc.
    • 3: Áp dụng hàm skim() từ gói skimr để tạo bảng thống kê mô tả chi tiết.
    • 4: Trích xuất các cột liên quan đến thống kê.
    • 6: Định dạng bảng theo thiết lập kbl_default1.
    • 7: Đặt tên cho các bảng và tiêu đề cột trong bảng.
  • Kết quả:
    • Thống kê mô tả cho thấy cả QOE và FCF đều có mức độ biến động cao, phản ánh sự chuyển dịch chiến lược tài chính của IMP qua các giai đoạn.
    • Việc kiểm soát chất lượng thu nhập và duy trì dòng tiền tự do dương là yếu tố then chốt để đảm bảo hiệu quả vận hành và khả năng tài trợ cho tăng trưởng trong dài hạn.

2.3.6.2 Ma trận tương quan

cor_matrix <- bctc %>%
  select(QOE, FCF) %>%
  cor(use = "complete.obs")
cor_matrix_df <- as.data.frame(as.table(cor_matrix))
cor_matrix_df %>%
  kbl_default1() %>%
  kable(caption = "Ma trận tương quan giữa QOE và FCF",
    col.names = c("Biến 1", "Biến 2", "Hệ số tương quan"),align = "llr")
Ma trận tương quan giữa QOE và FCF
Biến 1 Biến 2 Hệ số tương quan
QOE QOE 1,0000000
FCF QOE 0,6207924
QOE FCF 0,6207924
FCF FCF 1,0000000
  • Giải thích kỹ thuật:
    • 1: Tạo ma trận tương quan bằng cách chọn hai biến số: QOE và FCF từ bảng bctc.
    • 3: Sử dụng hàm cor() để tính toán hệ số tương quan, bỏ qua các giá trị NA.
    • 4: Chuyển đổi ma trận tương quan thành dạng bảng dữ liệu để dễ dàng trực quan hóa.
    • 5: Định dạng bảng theo thiết lập kbl_default1.
    • 7: Đặt tên cho các bảng và tiêu đề cột trong bảng.

2.3.6.3 Trực quan hóa ma trận tương quan

ggplot(cor_matrix_df, aes(x = Var1, y = Var2, fill = Freq)) +
  geom_tile(color = "white") +  
  geom_text(aes(label = scales::comma(Freq, big.mark = ".", decimal.mark = ",", accuracy = 0.0001))) +  
  scale_fill_gradient2(low = "blue", mid = "white", high = "orange", midpoint = 0,
                       limits = c(-1, 1), name = "Hệ số tương quan") +
  labs(title = "Heatmap ma trận tương quan giữa QOE và FCF",
       x = NULL, y = NULL) +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 0, hjust = 1))

  • Giải thích kỹ thuật:
    • 1: Thiết lập trục x và y dựa trên các biến trong ma trận tương quan, với màu sắc đại diện cho hệ số tương quan.
    • 2: Vẽ các ô hình chữ nhật (tiles) với viền trắng để tạo thành heatmap.
    • 3: Thêm nhãn hệ số tương quan vào mỗi ô, làm tròn đến 4 chữ số thập phân.
    • 4: Tạo dải màu từ xanh dương (tương quan âm) qua trắng (không tương quan) đến cam (tương quan dương), với giới hạn từ -1 đến 1.
    • 6: Đặt tiêu đề cho biểu đồ và loại bỏ nhãn trục x, y.
    • 8: Áp dụng giao diện tối giản và tùy chỉnh góc chữ trên trục x.
    • 9: Tùy chỉnh nhãn trục x: giữ nguyên góc (0 độ), căn chỉnh về bên phải để dễ đọc.
  • Kết quả:
    • Ma trận tương quan cho thấy mối liên hệ tích cực giữa chất lượng thu nhập và dòng tiền tự do. Với hệ số 0.62, có thể khẳng định rằng các năm IMP đạt QOE cao thường đi kèm với FCF dương, phản ánh hiệu quả vận hành thực chất và khả năng sinh tiền bền vững.
    • Đây là cơ sở quan trọng để đánh giá chiến lược tài chính dài hạn và ra quyết định đầu tư.

2.3.6.4 Biểu đồ phân tán giữa QOE và FCF với đường hồi quy tuyến tính

bctc %>%
  ggplot(aes(x = QOE, y = FCF / 1e9)) +
  geom_point(color = "#ff7f0e", size = 3, alpha = 0.7) +
  geom_smooth(method = "lm", color = "#1f77b4", se = TRUE, linewidth = 1.2) +
  labs(
    title = "MỐI QUAN HỆ GIỮA CHẤT LƯỢNG THU NHẬP (QOE) VÀ DÒNG TIỀN TỰ DO (FCF)",
    subtitle = "Đường màu xanh biểu diễn xu hướng tuyến tính giữa hai biến",
    x = "Chất lượng thu nhập (QOE)",
    y = "Dòng tiền tự do (FCF) (tỷ đồng)") +
  theme_minimal() +
  theme(
    plot.title = element_text(face = "bold", hjust = 0.5, size = 12, color = "#2C3E50"),
    plot.subtitle = element_text(hjust = 0.5, size = 10, color = "gray40"),
    axis.title = element_text(size = 13),
    axis.text = element_text(size = 11) )

  • Giải thích kỹ thuật:
    • 1: Thiết lập trục hoành là “QOE” và trục tung là “FCF” chia cho 1.000.000.000 để chuyển đổi sang tỷ đồng.
    • 3: Vẽ các điểm dữ liệu với màu cam, kích thước 3 và độ trong suốt 0.7.
    • 4: Thêm đường hồi quy tuyến tính với màu xanh dương, hiển thị khoảng tin cậy và độ dày 1.2.
    • 5: Đặt các nhãn cho tiêu đề biểu đồ, phụ đề và trục.
    • 10: Áp dụng giao diện tối giản và tùy chỉnh font chữ cho tiêu đề, phụ đề và trục.
    • 11–15: Tùy chỉnh font và kích thước: tiêu đề in đậm, căn giữa, màu xanh than; phụ đề màu xám, căn giữa; nhãn trục và số liệu có kích thước dễ đọc.
  • Kết quả:
    • Chất lượng thu nhập (QOE) là một chỉ báo tốt cho khả năng tạo ra dòng tiền tự do. Những quan sát có QOE cao (ví dụ: trên 1.0, nghĩa là CFO > Lợi nhuận) gần như luôn tạo ra FCF dương. Ngược lại, khi QOE thấp (đặc biệt là dưới 0.5) hoặc âm, FCF cũng thường xuyên ở mức âm, cho thấy tình trạng chi tiêu vốn vượt quá dòng tiền hoạt động.

2.3.7 Mối quan hệ giữa Lợi nhuận và Dòng tiền từ hoạt động kinh doanh

2.3.7.1 Thống kê mô tả của hai biến LoiNhuanSauThue và CFO_Tong

LN_CFO <- bctc %>%
  select(LoiNhuanSauThue, CFO_Tong) %>%
  skim() %>%
  select(skim_variable, numeric.mean, numeric.sd, numeric.p0, numeric.p100)
LN_CFO %>%
  kbl_default1() %>%
  kable(caption = "Thống kê mô tả của hai biến Lợi nhuận sau thuế và 
      Dòng tiền từ hoạt động kinh doanh",
    col.names = c("Biến", "Trung bình", "Độ lệch chuẩn", "Min", "Max"),
    align = "lrrrr")
Thống kê mô tả của hai biến Lợi nhuận sau thuế và Dòng tiền từ hoạt động kinh doanh
Biến Trung bình Độ lệch chuẩn Min Max
LoiNhuanSauThue 185.461.493.333 79.267.830.966 92.275.349.999 320.862.393.082
CFO_Tong 133.427.627.649 114.796.209.903 -30.308.428.665 378.603.401.448
  • Giải thích kỹ thuật:
    • 1: Tạo bảng LN_CFO bằng cách chọn hai biến số: LoiNhuanSauThue và CFO_Tong từ bảng bctc.
    • 3: Áp dụng hàm skim() từ gói skimr để tạo bảng thống kê mô tả chi tiết.
    • 4: Trích xuất các cột liên quan đến thống kê số.
    • 5: Định dạng bảng theo thiết lập kbl_default1.
    • 7: Đặt tên cho các bảng và tiêu đề cột trong bảng.
  • Kết quả:
    • Thống kê mô tả cho thấy doanh nghiệp IMP có mức lợi nhuận sau thuế trung bình cao, nhưng dòng tiền từ hoạt động kinh doanh lại biến động mạnh hơn. Điều này phản ánh sự khác biệt giữa lợi nhuận kế toán và dòng tiền thực tế, cần được theo dõi chặt chẽ để đảm bảo tính bền vững trong vận hành và tài trợ cho tăng trưởng.

2.3.7.2 Ma trận tương quan

cor_matrix2 <- bctc %>%
  select(LoiNhuanSauThue, CFO_Tong) %>%
  cor(use = "complete.obs")
cor_matrix2_df <- as.data.frame(as.table(cor_matrix2))
cor_matrix2_df %>%
  kbl_default1() %>%
  kable(
    caption = "Ma trận tương quan giữa Lợi nhuận sau thuế và
    Dòng tiền từ hoạt động kinh doanh",
    col.names = c("Biến 1", "Biến 2", "Hệ số tương quan"), align = "lrr")
Ma trận tương quan giữa Lợi nhuận sau thuế và Dòng tiền từ hoạt động kinh doanh
Biến 1 Biến 2 Hệ số tương quan
LoiNhuanSauThue LoiNhuanSauThue 1,0000000
CFO_Tong LoiNhuanSauThue 0,1689358
LoiNhuanSauThue CFO_Tong 0,1689358
CFO_Tong CFO_Tong 1,0000000
  • Giải thích kỹ thuật:
    • 1: Tạo ma trận tương quan bằng cách chọn hai biến số: LoiNhuanSauThue và CFO_Tong từ bảng bctc.
    • 3: Sử dụng hàm cor() để tính toán hệ số tương quan, bỏ qua các giá trị NA.
    • 4: Chuyển đổi ma trận tương quan thành dạng bảng dữ liệu để dễ dàng trực quan hóa.
    • 5: Định dạng bảng theo thiết lập kbl_default1.
    • 7: Đặt tên cho các bảng và tiêu đề cột trong bảng.

2.3.7.3 Trực quan hóa ma trận tương quan

ggplot(cor_matrix2_df, aes(x = Var1, y = Var2, fill = Freq)) +
  geom_tile(color = "white") + 
  geom_text(aes(label = scales::comma(Freq, big.mark = ".", decimal.mark = ",", accuracy = 0.0001))) +  
  scale_fill_gradient2(low = "#CFCFCF", mid = "#B9D3EE", high = "#8DEEEE", midpoint = 0,
                       limits = c(-1, 1), name = "Hệ số tương quan") +
  labs(title = "Ma trận tương quan giữa LoiNhuanSauThue và CFO Tong",
       x = NULL, y = NULL) +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 0, hjust = 1))

  • Giải thích kỹ thuật:
    • 1: Thiết lập trục x và y dựa trên các biến trong ma trận tương quan, với màu sắc đại diện cho hệ số tương quan.
    • 2: Vẽ các ô hình chữ nhật (tiles) với viền trắng để tạo thành heatmap.
    • 3: Thêm nhãn hệ số tương quan vào mỗi ô, làm tròn đến 4 chữ số thập phân.
    • 4: Tạo dải màu từ xám nhạt (tương quan âm) qua xanh nhạt (không tương quan) đến xanh dương (tương quan dương), với giới hạn từ -1 đến 1.
    • 6: Đặt tiêu đề cho biểu đồ và loại bỏ nhãn trục x, y.
    • 8: Áp dụng giao diện tối giản và tùy chỉnh góc chữ trên trục x.
    • 9: Tùy chỉnh nhãn trục x: giữ nguyên góc (0 độ), căn chỉnh về bên phải để dễ đọc.
  • Kết quả:
    • Ma trận tương quan cho thấy mối liên hệ yếu giữa lợi nhuận sau thuế và dòng tiền từ hoạt động kinh doanh của IMP. Điều này phản ánh rằng lợi nhuận kế toán không phải lúc nào cũng được hỗ trợ bởi dòng tiền thực tế.
    • Do đó, việc đánh giá hiệu quả tài chính cần kết hợp cả hai chỉ tiêu để có cái nhìn toàn diện và chính xác hơn về chất lượng thu nhập và khả năng thanh khoản.

2.3.7.4 Biểu đồ phân tán giữa LoiNhuanSauThue và CFO_Tong với đường hồi quy tuyến tính

bctc %>%
  ggplot(aes(x = LoiNhuanSauThue /1e9 , y = CFO_Tong /1e9)) +
  geom_point(color = "#ff7f0e", size = 3, alpha = 0.7) +
  geom_smooth(method = "lm", color = "#1f77b4", se = TRUE, linewidth = 1.2) +
  scale_x_continuous(labels = format_vn) +  
  scale_y_continuous(labels = format_vn) +  
  labs(
    title = "MỐI QUAN HỆ GIỮA LỢI NHUẬN SAU THUẾ VÀ DÒNG TIỀN TỪ HOẠT ĐỘNG KINH DOANH",
    subtitle = "Đường màu xanh biểu diễn xu hướng tuyến tính giữa hai biến",
    x = "Lợi nhuận sau thuế (Tỷ đồng )",
    y = "Dòng tiền từ hoạt động kinh doanh (CFO Tổng) (Tỷ đồng)") +
  theme_minimal() +
  theme(
    plot.title = element_text(face = "bold", hjust = 0.5, size = 8, color = "#2C3E50"),
    plot.subtitle = element_text(hjust = 0.5, size = 10, color = "gray40"),
    axis.title = element_text(size = 13),
    axis.text = element_text(size = 11))

  • Giải thích kỹ thuật:
    • 1: Thiết lập trục hoành là “LoiNhuanSauThue” chia cho 1e9 để chuyển đổi sang tỷ đồng, và trục tung là “CFO_Tong” chia cho 1e9.
    • 3: Vẽ các điểm dữ liệu với màu cam, kích thước 3 và độ trong suốt 0.7.
    • 4: Thêm đường hồi quy tuyến tính với màu xanh dương, hiển thị khoảng tin cậy và độ dày 1.2.
    • 5-6: Định dạng trục x và y theo chuẩn Việt Nam.
    • 7: Đặt các nhãn cho tiêu đề biểu đồ, phụ đề và trục.
    • 12: Áp dụng giao diện tối giản và tùy chỉnh font chữ cho tiêu đề, phụ đề và trục.
    • 13–17: Tùy chỉnh font và kích thước: Tiêu đề in đậm, căn giữa, màu xanh than.; Phụ đề màu xám, căn giữa.; Nhãn trục và số liệu có kích thước dễ đọc.
  • Kết quả:
    • Mặc dù Lợi nhuận sau thuế và Dòng tiền hoạt động có mối tương quan dương, nhưng mối quan hệ này rất lỏng lẻo và có độ biến thiên cực lớn. Việc LNST cao không đảm bảo CFO sẽ cao (thậm chí có thể âm), cho thấy các yếu tố điều chỉnh (như thay đổi vốn lưu động, khấu hao…) có tác động rất mạnh và không ổn định, làm cho chất lượng lợi nhuận trở nên khó dự đoán.

2.3.8 Mối quan hệ giữa CFO_Tong, CFI_Tong, CFF_Tong

2.3.8.1 Thống kê mô tả của ba biến CFO_Tong, CFI_Tong, CFF_Tong

CFO_CFI_CFF <- bctc %>%
  select(CFO_Tong, CFI_Tong, CFF_Tong) %>%
  skim() %>%
  select(skim_variable, numeric.mean, numeric.sd, numeric.p0, numeric.p100)
CFO_CFI_CFF %>%
  kbl_default1() %>%
  kable(
    caption = "Thống kê mô tả của ba biến CFO Tổng, CFI Tổng, CFF Tổng",
    col.names = c("Biến", "Trung bình", "Độ lệch chuẩn", "Min", "Max"),
    align = "lrrrr")
Thống kê mô tả của ba biến CFO Tổng, CFI Tổng, CFF Tổng
Biến Trung bình Độ lệch chuẩn Min Max
CFO_Tong 133.427.627.649 114.796.209.903 -30.308.428.665 378.603.401.448
CFI_Tong -130.736.762.123 137.798.621.319 -443.246.932.809 69.894.262.492
CFF_Tong -7.647.889.903 158.832.664.745 -271.718.144.243 352.371.798.300
  • Giải thích kỹ thuật:
    • 1: Tạo bảng CFO_CFI_CFF bằng cách chọn ba biến số: CFO_Tong, CFI_Tong và CFF_Tong từ bảng bctc.
    • 3: Áp dụng hàm skim() từ gói skimr để tạo bảng thống kê mô tả chi tiết.
    • 4: Trích xuất các cột liên quan đến thống kê số.
    • 5: Định dạng bảng theo thiết lập kbl_default1.
    • 7: Đặt tên cho các bảng và tiêu đề cột trong bảng.
  • Kết quả:
    • Thống kê mô tả cho thấy IMP có nền tảng dòng tiền hoạt động (CFO) tích cực, nhưng biến động lớn. Doanh nghiệp duy trì chiến lược đầu tư dài hạn (CFI âm liên tục), đồng thời có sự linh hoạt trong huy động và sử dụng vốn tài chính (CFF biến động mạnh). Việc kiểm soát tốt CFO để tài trợ cho CFI là yếu tố then chốt nhằm đảm bảo tính bền vững tài chính và giảm phụ thuộc vào nguồn vốn bên ngoài.

2.3.8.2 Ma trận tương quan

cor_matrix3 <- bctc %>%
  select(CFO_Tong, CFI_Tong, CFF_Tong) %>%
  cor(use = "complete.obs")
cor_matrix3_df <- as.data.frame(as.table(cor_matrix3)) %>%
  rename(Var1 = Var1, Var2 = Var2,  Freq = Freq) %>%  
  mutate(Freq = round(Freq, 4))  
cor_matrix3_df %>%
  kbl_default1() %>%
  kable(
    caption = "Ma trận tương quan giữa CFO Tổng, CFI Tổng, CFF Tổng",
    col.names = c("Biến 1", "Biến 2", "Hệ số tương quan"),
    allign = "lrrr")
Ma trận tương quan giữa CFO Tổng, CFI Tổng, CFF Tổng
Biến 1 Biến 2 Hệ số tương quan
CFO_Tong CFO_Tong 1,0000
CFI_Tong CFO_Tong -0,2491
CFF_Tong CFO_Tong -0,3623
CFO_Tong CFI_Tong -0,2491
CFI_Tong CFI_Tong 1,0000
CFF_Tong CFI_Tong -0,6370
CFO_Tong CFF_Tong -0,3623
CFI_Tong CFF_Tong -0,6370
CFF_Tong CFF_Tong 1,0000
  • Giải thích kỹ thuật:
    • 1: Tạo ma trận tương quan bằng cách chọn ba biến số: CFO_Tong, CFI_Tong và CFF_Tong từ bảng bctc.
    • 3: Sử dụng hàm cor() để tính toán hệ số tương quan, bỏ qua các giá trị NA.
    • 4: Chuyển đổi ma trận tương quan thành dạng bảng dữ liệu để dễ dàng trực quan hóa.
    • 5: Đổi tên các cột trong bảng dữ liệu cho rõ ràng.
    • 6: Làm tròn giá trị hệ số tương quan đến 4 chữ số thập phân.
    • 8: Định dạng bảng theo thiết lập kbl_default1.
    • 9: Đặt tên cho các bảng và tiêu đề cột trong bảng.

2.3.8.3 Trực quan hóa ma trận tương quan

ggplot(cor_matrix3_df, aes(x = Var2, y = Var1, fill = Freq)) +
  geom_tile(color = "white") +
  geom_text(aes(label = scales::comma(Freq, big.mark = ".", decimal.mark = ",", accuracy = 0.0001))) +
  scale_fill_gradient2(low = "blue", mid = "white", high = "red", midpoint = 0,
                       limits = c(-1, 1), name = "Hệ số tương quan") +
  labs(title = "Ma trận tương quan giữa CFO Tong, CFI Tong, CFF Tong",
       x = NULL, y = NULL) +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 0, hjust = 0.5))

  • Giải thích kỹ thuật:
    • 1: Thiết lập trục x và y dựa trên các biến trong ma trận tương quan, với màu sắc đại diện cho hệ số tương quan.
    • 2: Vẽ các ô hình chữ nhật (tiles) với viền trắng để tạo thành heatmap.
    • 3: Thêm nhãn hệ số tương quan vào mỗi ô, làm tròn đến 2 chữ số thập phân.
    • 4: Tạo dải màu từ xanh dương (tương quan âm) qua trắng (không tương quan) đến đỏ (tương quan dương), với giới hạn từ -1 đến 1.
    • 6: Đặt tiêu đề cho biểu đồ và loại bỏ nhãn trục x, y.
    • 8: Áp dụng giao diện tối giản và tùy chỉnh góc chữ trên trục x.
    • 9: Tùy chỉnh nhãn trục x: giữ nguyên góc (0 độ), căn giữa để dễ đọc.
  • Kết quả:
    • Ma trận tương quan cho thấy mối liên hệ chặt chẽ giữa dòng tiền đầu tư và tài trợ, phản ánh chiến lược sử dụng vốn vay hoặc phát hành để mở rộng quy mô. Trong khi đó, dòng tiền từ hoạt động kinh doanh có mối liên hệ yếu hơn với hai dòng tiền còn lại, cho thấy tính độc lập tương đối của vận hành. Việc duy trì CFO ổn định là nền tảng để giảm áp lực tài trợ và đảm bảo tính bền vững tài chính.

2.3.8.4 Biểu đồ phân tán giữa CFO_Tong, CFI_Tong, CFF_Tong với đường hồi quy tuyến tính

df_sum <- bctc %>%
  select(Năm, CFO_Tong, CFI_Tong, CFF_Tong) %>%
  mutate(NetCash = CFO_Tong + CFI_Tong + CFF_Tong)
waterfall_df <- bctc %>%
  summarise(
    CFO_Tong = sum(CFO_Tong, na.rm = TRUE),
    CFI_Tong = sum(CFI_Tong, na.rm = TRUE),
    CFF_Tong = sum(CFF_Tong, na.rm = TRUE)
  ) %>%
  pivot_longer(cols = everything(), names_to = "label", values_to = "value") %>%
  mutate(value = value / 1e9) %>%
  mutate(value_label = sprintf("%.2f", value))
waterfall_df$value <- as.numeric(waterfall_df$value)
net_cash_flow_value <- sum(waterfall_df$value)
total_label_text <- sprintf("%.2f", net_cash_flow_value)
waterfall(waterfall_df,
          calc_total = TRUE, 
          total_axis_text = "Net Cash Flow" ,
          rect_text_labels = waterfall_df$value_label,
          total_rect_text = total_label_text ) +
  scale_y_continuous(labels = format_vn) +
  labs(
    title = "BIỂU DIỄN DÒNG TIỀN TỪ HOẠT ĐỘNG KINH DOANH, ĐẦU TƯ VÀ TÀI CHÍNH",
    x = "Loại Dòng Tiền",
    y = "Giá Trị (Tỷ đồng)",) +
  theme_minimal() +
  theme(
    plot.title = element_text(face = "bold", hjust = 0.5, size = 12, color = "#2C3E50"),
    axis.title = element_text(size = 13),
    axis.text = element_text(size = 11)  )

  • Giải thích kỹ thuật:
    • 1: Tính dòng tiền ròng theo từng năm và lưu vào df_sum.
    • 4-12: Chuyển đổi dữ liệu từ dạng rộng sang dạng dài để phù hợp với biểu đồ thác nước.
    • 11: Chia giá trị dòng tiền cho 1e9 để chuyển đổi sang tỷ đồng.
    • 12: Tạo nhãn giá trị dưới dạng chuỗi với hai chữ số thập phân.
    • 13: Chuyển đổi cột giá trị thành kiểu số.
    • 14: Tính tổng dòng tiền ròng.
    • 16: Tạo biểu đồ thác nước sử dụng hàm waterfall(), hiển thị các thành phần dòng tiền và tổng dòng tiền ròng.
    • 21: Định dạng trục y theo chuẩn Việt Nam.
    • 22: Đặt các nhãn cho tiêu đề biểu đồ và trục.
    • 26: Áp dụng giao diện tối giản và tùy chỉnh font chữ cho tiêu đề và trục.
    • 27-30: Tùy chỉnh font và kích thước: Tiêu đề in đậm, căn giữa, màu xanh than.; Nhãn trục và số liệu có kích thước dễ đọc.
  • Kết quả:
    • Chiến lược của doanh nghiệp rất rõ ràng: Tái đầu tư mạnh mẽ. Gần như toàn bộ lượng tiền mặt dồi dào tạo ra từ hoạt động kinh doanh cốt lõi (CFO) đã được dùng ngay lập tức để chi cho các hoạt động đầu tư (CFI), như mua sắm tài sản cố định, mở rộng nhà xưởng, hoặc đầu tư tài chính. Hoạt động tài chính cũng đang trong giai đoạn trả tiền ra (trả nợ/cổ tức), dẫn đến dòng tiền thuần trong kỳ bị âm.

2.3.9 Mối quan hệ giữa Lợi nhuận sau thuế, CFO_Tong, FCF

2.3.9.1 Ma trận tương quan

cor_matrix4 <- bctc %>%
  select(LoiNhuanSauThue, CFO_Tong, FCF) %>%
  cor(use = "complete.obs")
cor_matrix4_df <- as.data.frame(as.table(cor_matrix4))
cor_matrix4_df %>%
  kbl_default1() %>%
  kable(
    caption = "Ma trận tương quan giữa Lợi nhuận sau thuế, CFO Tổng, và FCF",
    col.names = c("Biến 1", "Biến 2", "Hệ số tương quan"), align = "llr")
Ma trận tương quan giữa Lợi nhuận sau thuế, CFO Tổng, và FCF
Biến 1 Biến 2 Hệ số tương quan
LoiNhuanSauThue LoiNhuanSauThue 1,0000000
CFO_Tong LoiNhuanSauThue 0,1689358
FCF LoiNhuanSauThue 0,3981548
LoiNhuanSauThue CFO_Tong 0,1689358
CFO_Tong CFO_Tong 1,0000000
FCF CFO_Tong 0,8425615
LoiNhuanSauThue FCF 0,3981548
CFO_Tong FCF 0,8425615
FCF FCF 1,0000000
  • Giải thích kỹ thuật:
    • 1: Tạo ma trận tương quan bằng cách chọn ba biến số: LoiNhuanSauThue, CFO_Tong và FCF từ bảng bctc.
    • 3: Sử dụng hàm cor() để tính toán hệ số tương quan, bỏ qua các giá trị NA.
    • 4: Chuyển đổi ma trận tương quan thành dạng bảng dữ liệu để dễ dàng trực quan hóa.
    • 6: Định dạng bảng theo thiết lập kbl_default1.
    • 7: Đặt tên cho các bảng và tiêu đề cột trong bảng.

2.3.9.2 Trực quan hóa ma trận tương quan

ggplot(cor_matrix4_df, aes(x = Var1, y = Var2, fill = Freq)) +
  geom_tile(color = "white") + 
  geom_text(
  aes(label = scales::comma(Freq, big.mark = ".", decimal.mark = ",", accuracy = 0.0001)),
  color = "black",
  size = 5) +
  scale_fill_gradient2(low = "blue", mid = "white", high = "orange", midpoint = 0,
                       limits = c(-1, 1), name = "Hệ số tương quan") +
  labs(title = "Ma trận tương quan giữa LoiNhuanSauThue, CFO Tong, FCF",
       x = NULL, y = NULL) +
  theme_minimal() + 
  theme(axis.text.x = element_text(angle = 0, hjust = 1))

  • Giải thích kỹ thuật:
    • 1: Thiết lập trục x và y dựa trên các biến trong ma trận tương quan, với màu sắc đại diện cho hệ số tương quan.
    • 2: Vẽ các ô hình chữ nhật (tiles) với viền trắng để tạo thành heatmap.
    • 3: Thêm nhãn hệ số tương quan vào mỗi ô, định dạng số theo chuẩn Việt Nam với dấu phẩy phân tách hàng nghìn và làm tròn đến 4 chữ số thập phân.
    • 7: Tạo dải màu từ xanh dương (tương quan âm) qua trắng (không tương quan) đến cam (tương quan dương), với giới hạn từ -1 đến 1.
    • 9: Đặt tiêu đề cho biểu đồ và loại bỏ nhãn trục x, y.
    • 11: Áp dụng giao diện tối giản và tùy chỉnh góc chữ trên trục x.
    • 12: Tùy chỉnh nhãn trục x: giữ nguyên góc (0 độ), căn chỉnh về bên phải để dễ đọc.
  • Kết quả:
    *Ma trận tương quan cho thấy dòng tiền từ hoạt động kinh doanh (CFO) có mối liên hệ chặt chẽ với dòng tiền tự do (FCF), phản ánh vai trò trung tâm của vận hành trong việc tạo ra giá trị thực. Ngược lại, lợi nhuận sau thuế có tương quan yếu với cả CFO và FCF, cho thấy sự lệch pha giữa lợi nhuận kế toán và dòng tiền thực tế.
    • Điều này nhấn mạnh tầm quan trọng của việc đánh giá chất lượng thu nhập và khả năng sinh tiền thay vì chỉ nhìn vào lợi nhuận.

2.3.9.3 Biểu đồ phân tán giữa LoiNhuanSauThue, CFO_Tong, FCF với đường hồi quy tuyến tính

bctc %>%
  mutate(
    LNST_ty = LoiNhuanSauThue / 1e9, 
    CFO_ty  = CFO_Tong / 1e9,         
    FCF_ty  = FCF / 1e9  ) %>%           
  ggplot(aes(x = LNST_ty, y = CFO_ty, color = FCF_ty)) +
  geom_point(size = 3, alpha = 0.7) +
  geom_smooth(method = "lm", color = "#1f77b4", se = TRUE, linewidth = 1.2) +
  scale_x_continuous(labels = label_comma(), n.breaks = 8) +
  scale_y_continuous(labels = label_comma(), n.breaks = 10) +
  scale_color_continuous(labels = label_comma()) +
  labs(
    title = "MỐI QUAN HỆ GIỮA LỢI NHUẬN SAU THUẾ, CFO (tỷ VND), FCF (tỷ VND)",
    subtitle = "Đường màu xanh biểu diễn xu hướng tuyến tính giữa hai biến",
    x = "Lợi nhuận sau thuế (tỷ VND)",
    y = "Dòng tiền từ hoạt động kinh doanh (CFO) (tỷ VND)",
    color = "Dòng tiền tự do (FCF) (tỷ VND)") +
  theme_minimal() +
  theme(
    plot.title = element_text(face = "bold", hjust = 0.5, size = 12, color = "#2C3E50"),
    plot.subtitle = element_text(hjust = 0.5, size = 10, color = "gray40"),
    axis.title = element_text(size = 13),
    axis.text = element_text(size = 11),
    legend.position = "bottom",
    legend.direction = "horizontal" )

  • Giải thích kỹ thuật:
    • 1-5: Tạo các biến mới trong bảng bctc để chuyển đổi LoiNhuanSauThue, CFO_Tong và FCF sang đơn vị tỷ đồng.
    • 6: Thiết lập trục hoành là “LNST_ty” và trục tung là “CFO_ty”, với màu sắc đại diện cho “FCF_ty”.
    • 7: Vẽ các điểm dữ liệu với kích thước 3 và độ trong suốt 0.7.
    • 8: Thêm đường hồi quy tuyến tính với màu xanh dương, hiển thị khoảng tin cậy và độ dày 1.2.
    • 9-11: Định dạng trục x, y và màu sắc theo chuẩn Việt Nam với dấu phẩy phân tách hàng nghìn.
    • 12: Đặt các nhãn cho tiêu đề biểu đồ, phụ đề, trục và chú giải màu.
    • 18: Áp dụng giao diện tối giản và tùy chỉnh font chữ cho tiêu đề, phụ đề, trục và chú giải.
    • 19-25: Tùy chỉnh font và kích thước: Tiêu đề in đậm, căn giữa, màu xanh than.; Phụ đề màu xám, căn giữa.; Nhãn trục và số liệu có kích thước dễ đọc.; Chú giải đặt ở dưới cùng, hướng ngang để tiết kiệm không gian.
  • Kết quả:
    • Biểu đồ phơi bày một điểm rủi ro lớn. Đa số các quan sát (các điểm màu xám đậm và đen) đều có FCF âm, ngay cả khi có lợi nhuận (LNST) dương.
    • Điểm đáng báo động nhất là quan sát có LNST cao nhất (khoảng 300 tỷ) nhưng lại có CFO âm (khoảng -25 tỷ) và (như màu đen đã xác nhận) FCF cũng âm nặng. Đây là dấu hiệu của một “lợi nhuận rỗng”, nơi lợi nhuận kế toán hoàn toàn không đi kèm với tiền mặt, cho thấy vấn đề nghiêm trọng trong quản lý vốn lưu động hoặc bản chất của doanh thu.

2.3.10 Biến động vốn lưu động (ΔNWC) theo năm

2.3.10.1 Thống kê mô tả

NWC_nam <- bctc %>%
  group_by(Năm) %>%
  summarise(Delta_NWC_trungbinh = mean(Delta_NWC, na.rm = TRUE)) %>%
            mutate(Delta_NWC_trungbinh = round(Delta_NWC_trungbinh))
NWC_nam %>%  arrange(Năm) %>%
  kbl_default1() %>%
  kable(
    caption = "Biến động vốn lưu động (ΔNWC) trung bình theo Năm",
    col.names = c("Năm", "ΔNWC trung bình (VND)"), align = "rr")
Biến động vốn lưu động (ΔNWC) trung bình theo Năm
Năm ΔNWC trung bình (VND)
2015 44.718.002.767
2016 26.932.513.131
2017 -72.536.758.771
2018 -7.739.863.812
2019 -96.258.303.417
2020 -86.556.774.723
2021 52.461.839.329
2022 32.954.548.058
2023 -242.067.243.674
2024 -109.765.773.935
  • Giải thích kỹ thuật:
    • 1: Tạo bảng NWC_nam bằng cách nhóm dữ liệu theo năm và tính trung bình của biến Delta_NWC.
    • 3: Làm tròn giá trị trung bình của Delta_NWC.
    • 5: Sắp xếp bảng theo năm để hiển thị theo thứ tự thời gian.
    • 6: Định dạng bảng theo thiết lập kbl_default1.
    • 7: Đặt tên cho các bảng và tiêu đề cột trong bảng.
  • Kết quả:
    • Thống kê mô tả cho thấy IMP có xu hướng chiếm dụng vốn lưu động để hỗ trợ dòng tiền hoạt động, đặc biệt trong các năm 2017, 2019, 2020, 2023 và 2024.
    • Việc kiểm soát ΔNWC đóng vai trò quan trọng trong cân bằng giữa tăng trưởng vận hành và khả năng thanh toán ngắn hạn. Biến động lớn qua các năm phản ánh sự linh hoạt nhưng cũng tiềm ẩn rủi ro nếu không được quản trị chặt chẽ.

2.3.10.2 Xu hướng ΔNWC theo năm

NWC_nam %>%
  ggplot(aes(x = Năm, y = Delta_NWC_trungbinh / 1e9)) +
  geom_line(color = "orange", size = 1.2) +
  geom_point(color = "#CC3300", size = 3) +
  geom_segment(aes(x = Năm, 
                   xend = Năm, 
                   y = Delta_NWC_trungbinh / 1e9, 
        yend = (Delta_NWC_trungbinh + max(NWC_nam$Delta_NWC_trungbinh) * 0.09) / 1e9), 
               color = "#CC3300", linewidth = 0.5) +
  geom_text(aes(
    label = format_vn(Delta_NWC_trungbinh / 1e9, 2),
    y = (Delta_NWC_trungbinh + max(NWC_nam$Delta_NWC_trungbinh) * 0.2 ) / 1e9 ),
    size = 4,
    color = "red",
    fontface = "bold") +
  scale_x_continuous(breaks = unique(NWC_nam$Năm)) +
  labs(
    title = "XU HƯỚNG BIẾN ĐỘNG VỐN LƯU ĐỘNG (ΔNWC) THEO NĂM",
    x = "Năm",
    y = "ΔNWC trung bình (tỷ đồng)"  ) +
  theme_minimal() +
  theme(
    plot.title = element_text(face = "bold", hjust = 0.5, size = 16, color = "#2C3E50"),
    axis.title = element_text(size = 13),
    axis.text = element_text(size = 11) )

  • Giải thích kỹ thuật:
    • 1: Thiết lập trục x là Năm và trục y là Delta_NWC_trungbinh chia cho 1e9 để chuyển đổi sang tỷ đồng.
    • 3: Vẽ đường nối các điểm dữ liệu với màu cam và độ dày 1.2.
    • 4: Thêm các điểm dữ liệu với màu đỏ đậm và kích thước 3.
    • 5: Vẽ các đoạn thẳng từ mỗi điểm dữ liệu lên trên để làm nổi bật giá trị.
    • 10: Thêm nhãn giá trị ΔNWC trung bình trên mỗi điểm, với màu đỏ, kích thước 4 và in đậm.
    • 16: Định dạng trục x để hiển thị tất cả các năm có trong dữ liệu.
    • 17: Đặt các nhãn cho tiêu đề biểu đồ và trục.
    • 21: Áp dụng giao diện tối giản và tùy chỉnh font chữ cho tiêu đề và trục.
    • 22-25: Tùy chỉnh font và kích thước: Tiêu đề in đậm, căn giữa, màu xanh than.; Nhãn trục và số liệu có kích thước dễ đọc.
  • Kết quả:
    • Việc quản lý vốn lưu động là cực kỳ bất ổn. Điểm mấu chốt là năm 2023, khi một lượng tiền mặt khổng lồ (242 tỷ) được “giải phóng” đột ngột từ vốn lưu động (có thể do thanh lý hàng tồn kho, thu hồi nợ cũ, hoặc chiếm dụng vốn nhà cung cấp).
    • Sự kiện này chính là nguyên nhân trực tiếp “thổi phồng” Dòng tiền hoạt động (CFO) trong năm đó, và giải thích tại sao chỉ số Chất lượng thu nhập (QOE) tăng vọt bất thường trong cùng kỳ mà chúng ta đã thấy ở biểu đồ trước.