Em xin bày tỏ lòng cảm ơn chânh thành nhất đến Thầy Trần Mạnh Tường, người đã tận tâm đồng hành, định hướng và giải đáp mọi thắc mắc cho em trong suốt quá trình thực hiện bài tiểu luận này. Sự chỉ dẫn và những góp ý của Thầy là nền tảng quý báu giúp em hoàn thiện công trình này.
Bên cạnh đó, mặc dù em đã rất nỗ lực cố gắng để hoàn thành tốt nhất bài tiểu luận này, nhưng do kiến thức, kinh nghiệm và trải nghiệm còn hạn chế nên chắc chắn sẽ không thể tránh khỏi những sai sót trong quá trình thực hiện báo cáo. Kính mong nhận được những nhận xét, góp ý của các Thầy để em có thể rút kinh nghiệm cho các bài tiểu luận lần sau được tốt hơn.
Em xin cam đoan bài tiểu luận này là thành quả nghiên cứu của riêng mình, được hình thành và hoàn thiện dưới sự chỉ dẫn tận tâm của Thầy Trần Mạnh Tường.
Từng lập luận và phân tích trong bài là kết quả của quá trình tư duy độc lập, có tham khảo các nguồn học liệu và số liệu uy tín. Các kết quả nghiên cứu trong tiểu luận này là trung thực và chưa từng được sử dụng trong bài tiểu luận nào khác, cũng như không sao chép từ bất cứ nguồn nào.
Trong bối cảnh thị trường cạnh tranh ngày càng gay gắt, việc phân tích dữ liệu kinh doanh đóng vai trò then chốt giúp doanh nghiệp thấu hiểu khách hàng, tối ưu hóa hoạt động và đưa ra các quyết định chiến lược đúng đắn. Bài tiểu luận này sẽ thực hiện một quy trình phân tích toàn diện trên bộ dữ liệu giả định về hoạt động bán xe đạp của một chuỗi cửa hàng trong giai đoạn từ 2022 đến 2024. Cấu trúc bài tiểu luận bao gồm 4 phần chính:
1. Giới thiệu bộ dữ liệu: Khám phá cấu trúc và các đặc điểm ban đầu của dữ liệu.
2. Xử lý và làm sạch dữ liệu: Chuẩn bị dữ liệu cho quá trình phân tích, bao gồm việc tạo các biến mới, xử lý các vấn đề tiềm ẩn và mã hóa dữ liệu.
3. Thống kê cơ bản: Thực hiện các phép tính thống kê mô tả để trả lời các câu hỏi kinh doanh cụ thể.
4. Trực quan hóa dữ liệu: Sử dụng các biểu đồ đa dạng để minh họa các kết quả phân tích một cách sinh động và dễ hiểu.
Toàn bộ quá trình phân tích sẽ được thực hiện bằng ngôn ngữ lập trình R, một công cụ mạnh mẽ trong lĩnh vực khoa học dữ liệu.
Bước đầu tiên trong mọi dự án phân tích là làm quen với bộ dữ liệu. Chúng ta cần hiểu rõ về cấu trúc, ý nghĩa của từng cột và các đặc điểm cơ bản của dữ liệu.
Bộ dữ liệu “Bike Sales 100K” được nhóm tác giả thu thập từ nền tảng Kaggle. Đây là bộ dữ liệu mô phỏng hoạt động kinh doanh trong lĩnh vực bán lẻ xe đạp, được thiết kế nhằm phục vụ cho mục đích học tập, nghiên cứu và thực hành phân tích dữ liệu.
Dữ liệu bao gồm khoảng 100.000 quan sát và 11 biến, mỗi quan sát tương ứng với một giao dịch mua xe đạp của khách hàng. Các biến có thể được phân loại thành biến định tính và biến định lượng nhằm phục vụ cho các phân tích thống kê và mô hình hóa.
library(readr)
library(dplyr)
library(tidyr)
library(zoo)
library(lubridate)
library(knitr)
library(kableExtra)
library(ggplot2)
library(scales)
library(ggridges)
library(treemapify)
library(ggalluvial)
library(fmsb)
library(corrplot)
library(ggrepel)
library(e1071)
library(moments)Nhóm 1: Xử lý và Thao tác Dữ liệu
readr: Cung cấp các hàm để đọc và ghi dữ liệu dạng văn bản (như file .csv, .tsv) một cách nhanh chóng và hiệu quả, tối ưu hơn so với các hàm cơ bản của R.
dplyr: Là thư viện nền tảng cho việc thao tác dữ liệu theo triết lý “ngữ pháp của thao tác dữ liệu”. Nó cung cấp một bộ các hàm (động từ) nhất quán và hiệu suất cao như mutate(), select(), filter(), summarise(), và group_by() để thực hiện các tác vụ biến đổi và tóm tắt dữ liệu một cách logic.
tidyr: Chuyên về việc dọn dẹp và tái cấu trúc bố cục dữ liệu. Các hàm chính như pivot_longer() và pivot_wider() giúp chuyển đổi linh hoạt giữa định dạng dữ liệu dạng rộng (wide) và dạng dài (long), một bước tiền xử lý quan trọng cho nhiều loại phân tích và trực quan hóa.
lubridate: Giúp việc xử lý các đối tượng ngày tháng và thời gian (date/time) trở nên đơn giản và nhất quán. Thư viện này cho phép dễ dàng trích xuất các thành phần như năm, tháng, ngày, thứ, hoặc thực hiện các phép tính khoảng thời gian.
zoo: Là một thư viện mạnh mẽ cho việc làm việc với dữ liệu chuỗi thời gian. Trong bài tiểu luận, thư viện này được sử dụng cho hàm rollmean() để tính trung bình động, một kỹ thuật hữu ích để làm mượt và nhận diện xu hướng dài hạn của dữ liệu.
Nhóm 2: Trình bày Báo cáo và Bảng biểu
knitr: Là công cụ cốt lõi để tạo ra các báo cáo động từ R Markdown. Nó cho phép kết hợp mã R, kết quả thực thi (văn bản, bảng biểu, biểu đồ) và văn bản tường thuật vào một tài liệu duy nhất, sau đó biên dịch (knit) thành các định dạng đầu ra như PDF hoặc HTML.
kableExtra: Mở rộng khả năng của hàm knitr::kable(), cung cấp các công cụ để tạo ra các bảng biểu có tính thẩm mỹ cao và chuyên nghiệp. Nó cho phép tùy chỉnh định dạng nâng cao như tạo sọc, nhóm hàng/cột, định dạng có điều kiện và thêm thanh cuộn cho bảng lớn.
Nhóm 3: Trực quan hóa Dữ liệu (Vẽ biểu đồ)
ggplot2: Là thư viện trực quan hóa dữ liệu mạnh mẽ và phổ biến nhất trong R, được xây dựng dựa trên triết lý “Ngữ pháp của Đồ thị”. Nó cho phép người dùng xây dựng các biểu đồ phức tạp từng lớp một cách linh hoạt.
scales: Cung cấp các công cụ để kiểm soát việc ánh xạ dữ liệu sang các thuộc tính thẩm mỹ. Thường được dùng cùng ggplot2 để định dạng lại các nhãn trên trục và chú giải (ví dụ: định dạng tiền tệ $, phần trăm %, hay ngày tháng).
corrplot: Chuyên dùng để trực quan hóa ma trận tương quan. Nó cung cấp nhiều phương pháp hiển thị hệ số tương quan (dưới dạng số, màu sắc, hình tròn,…) giúp dễ dàng nhận diện mối quan hệ giữa các biến số.
treemapify: Vẽ biểu đồ dạng Treemap, giúp hiển thị dữ liệu có cấu trúc phân cấp dưới dạng các hình chữ nhật lồng nhau.
ggridges: Vẽ biểu đồ mật độ dạng “dãy núi”, rất hữu ích để so sánh sự phân bố của dữ liệu giữa nhiều nhóm khác nhau.
ggalluvial: Vẽ biểu đồ Alluvial và Sankey, dùng để minh họa “dòng chảy” của dữ liệu từ nhóm này sang nhóm khác.
fmsb: Dùng để vẽ các loại biểu đồ ít phổ biến hơn như biểu đồ radar (biểu đồ mạng nhện).
ggrepel: Là một phần mở rộng của ggplot2, cung cấp các hàm geom_text_repel() và geom_label_repel() để tự động điều chỉnh vị trí của các nhãn văn bản trên biểu đồ, tránh cho chúng bị chồng chéo lên nhau.
Nhóm 4: Thống kê
Sau khi cài đặt và kích hoạt các gói. Dữ liệu sẽ được đọc vào được lưu dưới tên BK để tiện thao tác trong quá trình xử lý.
head(BK, 5) %>%
kable("latex", booktabs = TRUE, caption = "5 dòng đầu tiên của bộ dữ liệu") %>%
kable_styling(latex_options = c("hold_position", "striped", "scale_down"), position = "center",font_size = 9)Giải thích code:
(1): Lệnh này có nghĩa là “Lấy ra 5 dòng đầu tiên của bộ dữ liệu có tên là BK”.
(2): Dòng này nhận 5 dòng dữ liệu từ bước trên và biến chúng thành một cái bảng cơ bản với tiêu đề là “10 dòng đầu tiên của bộ dữ liệu”.
(3): Dòng cuối cùng này có nhiệm vụ trang trí cho cái bảng đã tạo ở trên cho đẹp hơn bằng cách thêm hiệu ứng sọc, căn lề, và tự động điều chỉnh kích thước cho vừa trang giấy.
## Số biến trong bộ dữ liệu là: 11
## Số quan sát trong bộ dữ liệu là: 100000
Giải thích code:
Dùng cat() để hiển thị một câu thông báo kết hợp cả chữ và số trong báo cáo của mình một cách tự nhiên và sạch sẽ nhất.
ncol(BK): Lệnh này có một nhiệm vụ duy nhất: Đếm xem có bao nhiêu cột trong bảng dữ liệu “BK” và trả về một con số.
nrow(BK): Lệnh này có nhiệm vụ là đếm xem có bao nhiêu hàng (dòng) trong bảng dữ liệu “BK” và trả về một con số.
data_summary_auto <- data.frame(Ten_Cot= names(BK), Loai_Du_lieu = unname(sapply(BK, class)), Giai_thich = c("Mã số bán hàng.", "Ngày bán hàng.","Mã khách hàng.","Tên hoặc loại xe đạp.","Giá bán.","Số lượng bán.","Vị trí của hàng.", "Mã nhân viên bán hàng.","Phương thức thanh toán.","Tuổi của khách hàng.","Giới tính của khách hàng."))
kable(data_summary_auto, caption = "Bảng tóm tắt tên cột và kiểu dữ liệu", col.names = c("Tên biến", "Loại dữ liệu trong R", "Ý nghĩa"),format = "latex",booktabs = TRUE) %>% kable_styling( latex_options = "striped", full_width = F, position = "center")Giải thích code:
(1): Dòng này có nhiệm vụ tạo ra một bảng tóm tắt hoàn toàn mới tên là data_summary_auto để mô tả dữ liệu gốc BK. Bảng này gồm 3 cột:Ten_Cot, Loai_Du_Lieu, Giai_thich.
(2): Lấy bảng trên đặt lại tên các cột tiêu đề (col.names) và thêm tiêu đề chung cho bảng sau đó trang trí lại bảng cho dễ đọc.
Đếm số biến định lượng:
so_bien_dinh_luong <- data_summary_auto %>% # Bắt đầu với bảng 'data_summary_auto'.
filter(Loai_Du_lieu == "numeric") %>% # Dùng filter() để chỉ giữ lại các dòng có Loai_Du_lieu là "numeric".
nrow() # Dùng nrow() để đếm số dòng còn lại.
cat("Số biến định lượng:", so_bien_dinh_luong, "\n")## Số biến định lượng: 6
Các biến định lượng thể hiện các thông số số học, đo lường giá trị và khối lượng giao dịch, bao gồm: Sale_ID, Customer_ID, Salesperson_ID, Price, Quantity, và Customer_Age. Biến Date mặc dù được lưu dưới dạng ngày tháng, cũng có thể được chuyển đổi thành dạng định lượng để phân tích theo thời gian. Những biến định lượng này hỗ trợ các phân tích thống kê mô tả, tính toán doanh thu, lợi nhuận và mô hình dự báo.
so_bien_dinh_tinh <- data_summary_auto %>%
filter(Loai_Du_lieu == "character") %>% # Dùng filter() để chỉ giữ lại các dòng có Loai_Du_lieu là "character".
nrow()
cat("Số biến định tính:", so_bien_dinh_tinh, "\n")## Số biến định tính: 5
Các biến định tính mô tả các đặc điểm phân loại và chất lượng của khách hàng, sản phẩm và giao dịch, bao gồm: Bike_Model, Store_Location, Payment_Method, Customer_Gender, và Date. Những biến này cho phép phân tích phân phối, nhóm khách hàng, đánh giá hiệu suất nhân viên và các đặc trưng sản phẩm.
## Tổng số giá trị bị thiếu (NA): 0
## Tổng số dòng bị trùng lặp hoàn toàn: 0
Giải thích code:
Hàm is.na(): Sẽ quét qua từng ô trong toàn bộ dataframe BK. Nó sẽ trả về một dataframe mới có cùng kích thước, nhưng thay vì chứa dữ liệu gốc, nó sẽ chứa các giá trị TRUE (nếu ô gốc bị thiếu, tức là NA) hoặc FALSE (nếu ô gốc có dữ liệu).
Hàm duplicated(): Sẽ quét qua từng dòng của dataframe BK. Nó so sánh mỗi dòng với tất cả các dòng đã xuất hiện trước nó. Nếu một dòng giống hệt (tất cả các giá trị trong các cột đều giống nhau) với một dòng nào đó ở phía trên, nó sẽ trả về TRUE cho dòng đó. Ngược lại, nó trả về FALSE.
Hàm sum(…): Cho một tập hợp các giá trị TRUE/FALSE, R sẽ tự động coi TRUE = 1 và FALSE = 0.Kết quả cuối cùng chính là tổng số dữ liệu cần tính.
Nhận xét: Dữ liệu rất sạch, không có giá trị bị thiếu (NA) và không có dòng nào bị trùng lặp hoàn toàn.
price_zero_count <- sum(BK$Price == 0, na.rm = TRUE)
quantity_zero_count <- sum(BK$Quantity == 0, na.rm = TRUE)
cat(sprintf("Số lượng giao dịch có giá bán (Price) bằng 0 là: %d\n", price_zero_count))## Số lượng giao dịch có giá bán (Price) bằng 0 là: 0
## Số lượng giao dịch có số lượng (Quantity) bằng 0 là: 0
Giải thích code:
Nhận xét:
Kết quả kiểm tra cho thấy không có bất kỳ giao dịch nào trong bộ dữ liệu có giá bán (Price) hoặc số lượng (Quantity) bằng 0. Đây là một dấu hiệu rất tích cực về chất lượng dữ liệu.
Về mặt logic kinh doanh: Mỗi giao dịch được ghi nhận đều là một giao dịch hợp lệ, có giá trị và có ít nhất một sản phẩm được bán ra.
Về mặt kỹ thuật: Dữ liệu sạch ở điểm này, giúp đảm bảo rằng các phép tính tài chính sau này, đặc biệt là tính toán doanh thu (Revenue = Price * Quantity), sẽ chính xác và không bị sai lệch bởi các giá trị zero bất thường.
## Sale_ID Customer_ID Price Quantity Salesperson_ID
## Min. : 1 Min. :1000 Min. : 200 Min. :1.000 Min. :100.0
## 1st Qu.: 25001 1st Qu.:3249 1st Qu.:1400 1st Qu.:2.000 1st Qu.:324.0
## Median : 50001 Median :5491 Median :2599 Median :3.000 Median :550.0
## Mean : 50001 Mean :5495 Mean :2598 Mean :2.997 Mean :549.9
## 3rd Qu.: 75000 3rd Qu.:7738 3rd Qu.:3796 3rd Qu.:4.000 3rd Qu.:775.0
## Max. :100000 Max. :9999 Max. :5000 Max. :5.000 Max. :999.0
## Customer_Age
## Min. :18.00
## 1st Qu.:31.00
## Median :44.00
## Mean :44.04
## 3rd Qu.:57.00
## Max. :70.00
Giải thích code:
Hàm summary(): Tính toán các thống kê mô tả (như trung bình, trung vị, min, max,…) cho từng cột số đó và hiển thị kết quả.
Hàm select(where(is.numeric)): Lọc và chỉ giữ lại những cột có kiểu dữ liệu là số từ BK.
Nhận xét:
Đối với các biến mang tính chất định danh (ID), các chỉ số như trung bình (Mean) hay trung vị (Median) không mang ý nghĩa về mặt phân tích kinh doanh. Tuy nhiên, giá trị nhỏ nhất (Min) và lớn nhất (Max) lại rất hữu ích. Ví dụ, Sale_ID chạy từ 1 đến 100,000 cho thấy dữ liệu có vẻ đầy đủ. Tương tự, Customer_ID và Salesperson_ID cho chúng ta thấy khoảng mã định danh hợp lệ của khách hàng và nhân viên.
Price (Giá bán): Giá xe dao động từ 200 USD đến 5,000 USD, cho thấy danh mục sản phẩm của cửa hàng khá đa dạng, từ các dòng xe phổ thông đến cao cấp. Giá trị trung bình (Mean = 2598) và trung vị (Median = 2599) rất gần nhau. Đây là một dấu hiệu quan trọng cho thấy sự phân bổ của giá bán là tương đối đối xứng, không bị lệch nhiều bởi các sản phẩm có giá quá cao hoặc quá thấp.
Quantity (Số lượng): Khách hàng mua từ 1 đến 5 sản phẩm trong một lần giao dịch. Khoảng tứ phân vị (giữa 1st Qu. = 2 và 3rd Qu. = 4) cho thấy 50% số giao dịch của cửa hàng có số lượng bán ra từ 2 đến 4 chiếc. Giá trị trung bình cũng xấp xỉ 3, khẳng định đây là quy mô giao dịch phổ biến nhất.
Customer_Age (Tuổi khách hàng): Độ tuổi khách hàng rất rộng, từ 18 đến 70, cho thấy sản phẩm phù hợp với nhiều thế hệ. Đây là thông tin giá trị nhất. Khoảng tứ phân vị cho thấy 50% khách hàng cốt lõi của cửa hàng nằm trong độ tuổi từ 31 đến 57 tuổi. Cả giá trị trung bình và trung vị đều là 44, một lần nữa khẳng định nhóm khách hàng chính là những người ở độ tuổi trung niên.
Kết luận sơ bộ: Qua bước tóm tắt nhanh, có thể thấy dữ liệu định lượng hoàn toàn hợp lý, không có giá trị bất thường. Các biến kinh doanh chính có sự phân bổ khá cân đối và đã cung cấp những gợi ý ban đầu rất quan trọng về dải sản phẩm và nhóm khách hàng mục tiêu của chuỗi cửa hàng.
BK %>% count(Bike_Model, sort = TRUE) %>% kable(caption = "Số lượng giao dịch tại mỗi cửa hàng", col.names = c ("Mẫu xe", "Số lượng giao dịch"))| Mẫu xe | Số lượng giao dịch |
|---|---|
| BMX | 14377 |
| Road Bike | 14363 |
| Cruiser | 14332 |
| Folding Bike | 14329 |
| Hybrid Bike | 14319 |
| Electric Bike | 14169 |
| Mountain Bike | 14111 |
Giải thích code: Dòng code này có nhiệm vụ đếm xem mỗi loại xe đạp (Bike_Model) xuất hiện bao nhiêu lần trong bộ dữ liệu, sau đó sắp xếp kết quả từ cao xuống thấp và cuối cùng là hiển thị kết quả dưới dạng một cái bảng gọn gàng có tiêu đề.
Kết quả: Mẫu xe “BMX” có số lượng giao dịch cao nhất với 14377 lượt, theo sau sát sao là “Road Bike” với 14363 lượt. Trong khi đó, “Mountain Bike” là mẫu xe có số lượng giao dịch thấp nhất, với 14111 lượt. Khoảng cách giữa mẫu xe bán chạy nhất và mẫu xe bán ít nhất không đáng kể. Điều này cho thấy danh mục sản phẩm của cửa hàng rất cân bằng, không có mẫu xe nào chiếm ưu thế vượt trội hay bị bỏ lại phía sau. Tất cả các dòng xe đều có sức hút tương đối đồng đều đối với khách hàng.
Trong mục này, chúng ta sẽ thực hiện các bước quan trọng để làm sạch, chuẩn hóa và làm giàu bộ dữ liệu. Mục tiêu là chuyển đổi dữ liệu thô thành một dạng có cấu trúc, đầy đủ thông tin và sẵn sàng cho các bước phân tích sâu hơn.
Bước đầu tiên là đảm bảo các biến được lưu trữ dưới đúng định dạng mà R có thể hiểu và thao tác.
Biến Date đang ở dạng chữ character. Chúng ta cần chuyển nó sang định dạng Date để có thể thực hiện các phân tích theo thời gian.
## [1] "2022-07-11" "2024-05-03" "2022-09-01" "2022-09-28" "2021-01-05"
## [6] "2021-09-06"
## [1] "Date"
Giải thích code:
(1): Chuyển đổi cột Date từ dạng chữ viết sang định dạng ngày tháng chuẩn.
(2): Hiển thị ra vài giá trị đầu tiên của cột Date sau khi đã chuyển đổi.
(1): Kiểm tra và cho biết kiểu dữ liệu của cột Date bây giờ là gì.
Một bộ dữ liệu thực tế thường chứa các giá trị bị thiếu (NA) hoặc giá trị ngoại lai (outliers). Mặc dù bộ dữ liệu hiện tại của chúng ta khá sạch, chúng ta sẽ minh họa cách xử lý các vấn đề này.
Tình huống giả định: Giả sử có 800 quan sát bị thiếu thông tin về tuổi khách hàng . Giải pháp: Một cách tiếp cận phổ biến là thay thế các giá trị thiếu bằng giá trị trung bình của toàn bộ cột.
BK_dirty <- BK
set.seed(500) # -> set.seed() đảm bảo rằng mỗi lần chạy code, sẽ luôn nhận được CÙNG MỘT KẾT QUẢ "ngẫu nhiên"
na_indices <- sample(1:nrow(BK_dirty), 800)
BK_dirty$Customer_Age[na_indices] <- NA
mean_age <- mean(BK_dirty$Customer_Age, na.rm = TRUE)
BK_dirty$Customer_Age[is.na(BK_dirty$Customer_Age)] <- round(mean_age)
sum(is.na(BK_dirty$Customer_Age))## [1] 0
Giải thích code:
(2): Đảm bảo rằng dù chạy code bao nhiêu lần, 800 vị trí “ngẫu nhiên” này sẽ luôn giống hệt nhau.
(4): Gán giá trị NA (bị thiếu) vào 800 vị trí đã chọn.
(5): Tính tuổi trung bình của cột Customer_Age. Tham số na.rm = TRUE là quan trọng nhất, nó yêu cầu hàm mean phải bỏ qua tất cả các ô bị thiếu (NA) khi tính toán.
(6): Đây là bước xử lý chính. Code này tìm tất cả các ô đang là NA trong cột Customer_Age và thay thế chúng bằng giá trị tuổi trung bình đã tính ở bước trên (đã được làm tròn).
(7): Đếm lại giá trị bị thiếu.
Ghi chú: Cho các phân tích tiếp theo, chúng ta sẽ
tiếp tục sử dụng bộ dữ liệu BK gốc không có giá trị
thiếu.
Tình huống giả định: Giả sử do lỗi nhập liệu, một vài giao dịch có giá trị Price bất thường (ví dụ: bằng 0 hoặc quá cao).
Giải pháp: Thay thế các giá trị ngoại lai này bằng một giá trị hợp lý hơn, ở đây là giá trị trung vị (median) của từng loại xe.
BK_outliers <- BK
BK_outliers$Price[sample(1:nrow(BK_outliers), 10)] <- c(0, 120000, 10, 50000, 20, 70, 40, 80000, 5, 150000)
BK_cleaned <- BK_outliers %>%
group_by(Bike_Model) %>%
mutate(Median_Price = median(Price)) %>%
ungroup() %>%
mutate(Price = ifelse(Price < 100 | Price > 10000, Median_Price, Price)) %>%
select(-Median_Price)
summary(BK_outliers$Price)## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 0 1400 2599 2602 3796 150000
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 200 1400 2598 2598 3796 5000
Giải thích code:
(2): Chọn ngẫu nhiên 10 vị trí trong cột Price và thay thế chúng bằng những con số vô lý (ví dụ: 0, 5, 120000, 150000) để giả lập lỗi nhập liệu.
(5): Với mỗi nhóm xe, tính giá trung vị (median) - là mức giá phổ biến, ít bị ảnh hưởng bởi outlier - và lưu vào một cột tạm tên là Median_Price.
(7): Đây là một hàm điều kiện. Nếu giá là một ngoại lai (thỏa mãn điều kiện), nó sẽ được thay thế bằng giá trị Median_Price (trung vị của loại xe đó) mà chúng ta đã tính ở bước trên và ngược lại.
Ghi chú: Chúng ta sẽ tiếp tục sử dụng bộ dữ liệu
BK gốc cho các phân tích sau.
Đây là bước quan trọng nhất để làm giàu thông tin. Chúng ta sẽ tạo một bản sao BK1 từ BK gốc và thực hiện tất cả các thao tác trên một chuỗi duy nhất.
customer_frequency <- BK %>% group_by(Customer_ID) %>% summarise(Purchase_Count = n(), .groups = 'drop')
BK1 <- BK %>%
left_join(customer_frequency, by = "Customer_ID") %>% mutate(
# 1. Tạo biến doanh thu
Revenue = Price * Quantity,
# 2. Trích xuất thông tin năm, tháng, thứ từ cột Date
Year = year(Date),
Month = month(Date, label = TRUE, abbr = FALSE),
Weekday = wday(Date, label = TRUE, abbr = FALSE),
# 3. Tạo biến mùa (Season)
Season = case_when(
month(Date) %in% c(3, 4, 5) ~ "Xuân",
month(Date) %in% c(6, 7, 8) ~ "Hạ",
month(Date) %in% c(9, 10, 11) ~ "Thu",
TRUE ~ "Đông"),
# 4. Phân tổ theo nhóm tuổi
Age_Group = case_when(
Customer_Age <= 30 ~ "Trẻ",
Customer_Age <= 55 ~ "Trung niên",
TRUE ~ "Lớn tuổi"),
# 5. Phân tổ theo mức giá
Price_Level = case_when(
Price <= 2600 ~ "Phổ thông",
Price < 4000 ~ "Cao cấp",
TRUE ~ "Hạng sang"),
# 6. Tạo biến hạng khách hàng thân thiết (dựa vào Purchase_Count đã join ở trên)
Loyalty_Status = case_when(
Purchase_Count >= 15 ~ "VIP",
Purchase_Count >= 10 ~ "Thân thiết",
TRUE ~ "Khách hàng mới"),
# 7. Mã hóa dữ liệu (Encoding) giới tính
Gender_Encoded = case_when(
Customer_Gender == "Male" ~ 0,
Customer_Gender == "Female" ~ 1))Sau khi thực hiện tất cả các bước trên, bộ dữ liệu của chúng ta đã được mở rộng và làm giàu đáng kể.
head_data <- head(BK1, 5)
num_cols <- ncol(head_data)
mid_point <- ceiling(num_cols / 2)
head_data[, 1:mid_point] %>% kable(caption = "Dữ liệu sau xử lý (Phần 1).", booktabs = TRUE) %>% kable_styling(latex_options = c("striped", "hold_position"), font_size = 6)| Sale_ID | Date | Customer_ID | Bike_Model | Price | Quantity | Store_Location | Salesperson_ID | Payment_Method | Customer_Age | Customer_Gender |
|---|---|---|---|---|---|---|---|---|---|---|
| 1 | 2022-07-11 | 9390 | Cruiser | 318.32 | 1 | Philadelphia | 589 | Apple Pay | 70 | Female |
| 2 | 2024-05-03 | 3374 | Hybrid Bike | 3093.47 | 4 | Chicago | 390 | Apple Pay | 37 | Male |
| 3 | 2022-09-01 | 2689 | Folding Bike | 4247.99 | 3 | San Antonio | 338 | PayPal | 59 | Female |
| 4 | 2022-09-28 | 3797 | Mountain Bike | 1722.01 | 3 | San Antonio | 352 | Apple Pay | 19 | Male |
| 5 | 2021-01-05 | 1633 | BMX | 3941.44 | 3 | Philadelphia | 580 | PayPal | 67 | Female |
head_data[, (mid_point + 1):num_cols] %>% kable(caption = "Dữ liệu sau xử lý (Phần 2).", booktabs = TRUE) %>% kable_styling(latex_options = c("striped", "hold_position"), font_size = 7)| Purchase_Count | Revenue | Year | Month | Weekday | Season | Age_Group | Price_Level | Loyalty_Status | Gender_Encoded |
|---|---|---|---|---|---|---|---|---|---|
| 15 | 318.32 | 2022 | July | Monday | Hạ | Lớn tuổi | Phổ thông | VIP | 1 |
| 11 | 12373.88 | 2024 | May | Friday | Xuân | Trung niên | Cao cấp | Thân thiết | 0 |
| 7 | 12743.97 | 2022 | September | Thursday | Thu | Lớn tuổi | Hạng sang | Khách hàng mới | 1 |
| 17 | 5166.03 | 2022 | September | Wednesday | Thu | Trẻ | Phổ thông | VIP | 0 |
| 18 | 11824.32 | 2021 | January | Tuesday | Đông | Lớn tuổi | Cao cấp | VIP | 1 |
Giải thích code:
(3): Chia đôi số cột và làm tròn lên để đảm bảo không mất cột nào nếu tổng số cột là số lẻ. Kết quả được lưu vào mid_point.
(4): Lấy nửa đầu dữ liệu đó và định dạng nó thành một bảng đẹp có tiêu đề là “Dữ liệu sau xử lý (Phần 1)”.
(1): Lấy nửa sau dữ liệu đó và định dạng nó thành một bảng đẹp thứ hai có tiêu đề là “Dữ liệu sau xử lý (Phần 2)”.
Kết quả: Bộ dữ liệu BK1 giờ đây đã có thêm nhiều cột mới (Revenue, Year, Month, Weekday, Season, Age_Group, Price_Level, Loyalty_Status, Gender_Encoded), sẵn sàng cho các phân tích sâu sắc ở các chương tiếp theo.
Sau khi đã xử lý và làm giàu dữ liệu, mục này sẽ tập trung vào việc thực hiện các phép tính thống kê cơ bản để trả lời các câu hỏi kinh doanh cụ thể, từ đó rút ra những insight ban đầu về hoạt động của doanh nghiệp.
numeric_vars <- BK %>%
select(where(is.numeric)) %>% select(-ends_with("_ID"))
descriptive_table_adv <- numeric_vars %>%
summarise(across(everything(), list(
Mean = ~mean(., na.rm = TRUE),
Median = ~median(., na.rm = TRUE),
SD = ~sd(., na.rm = TRUE),
Var = ~var(., na.rm = TRUE),
Min = ~min(., na.rm = TRUE),
Max = ~max(., na.rm = TRUE),
Skewness = ~skewness(., na.rm = TRUE),
Kurtosis = ~kurtosis(., na.rm = TRUE)),
.names = "{.col}__{.fn}")) %>%
tidyr::pivot_longer(cols = everything(), names_to = c("Variable", "Statistic"), names_sep = "__" ) %>%
tidyr::pivot_wider( names_from = Statistic, values_from = value ) %>%
mutate(across(where(is.numeric), ~round(., 2)))
kable(descriptive_table_adv, caption = "Thống kê mô tả chi tiết các biến định lượng", digits = 2, booktabs = TRUE, format = "latex") %>%
kable_styling(latex_options = c("striped", "hold_position"), full_width = FALSE, position = "center")Giải thích code:
(2): Giữ lại những cột là số và loại bỏ các cột định danh (ID) vì chúng không có ý nghĩa thống kê.
(4): Áp dụng một loạt các hàm thống kê (mean, median, sd,…) cho tất cả các cột.
(16): Lệnh này thực hiện một thao tác biến đổi cấu trúc quan trọng. Hãy tưởng tượng nó tháo dỡ cái bảng rộng ban đầu và xếp lại thành một danh sách dài, mỗi dòng là một cặp (Tên biến, Chỉ số thống kê, Giá trị).
(20): Xoay bảng từ dạng dài về lại dạng rộng. Kết quả là một bảng mà mỗi hàng là một biến số (Price, Quantity,…) và mỗi cột là một chỉ số thống kê (Mean, Median,…).
Nhận xét:
1. Biến Price (Giá cả):
Mean (2598.18) & Median (2598.57): Giá trị trung bình và trung vị gần như bằng nhau. Điều này là một dấu hiệu rất tốt, cho thấy sự phân phối của giá cả rất đối xứng. Không có nhiều sản phẩm giá quá cao hoặc quá thấp kéo lệch giá trị trung bình.
SD (1384.94) & Var (1918067.48): Độ lệch chuẩn (SD) khá lớn. Điều này cho thấy giá cả của các sản phẩm có sự biến động và chênh lệch cao. Dữ liệu không tập trung quanh một mức giá duy nhất mà trải rộng ra.
Min (200.01) & Max (4999.81): Khoảng giá của sản phẩm là từ khoảng 200 đến 5000. Điều này xác nhận lại độ phân tán cao của dữ liệu giá.
Skewness (0): Độ xiên bằng 0. Đây là sự khẳng định về mặt toán học rằng phân phối giá cả là hoàn toàn đối xứng. Đồ thị phân phối có hình dạng giống quả chuông cân đối.
Kurtosis (1.8): Độ nhọn là 1.8. Giá trị này nhỏ hơn 3 (độ nhọn của phân phối chuẩn). Điều này có nghĩa là phân phối ít nhọn hơn (bẹt hơn) so với phân phối chuẩn. Nó có ít các giá trị ngoại lai ở hai phía đuôi.
2. Biến Quantity (Số lượng):
Mean (3.00) & Median (3.00): Trung bình và trung vị bằng nhau tuyệt đối. Số lượng sản phẩm được mua trung bình là 3.
SD (1.41) & Var (2.00): Độ lệch chuẩn nhỏ, cho thấy số lượng mua ít biến động. Hầu hết các giao dịch đều có số lượng xoay quanh mức 3.
Min (1.00) & Max (5.00): Số lượng mua ít nhất là 1 và nhiều nhất là 5. Đây là một khoảng giá trị rất hẹp, củng cố cho việc độ biến động thấp.
Skewness (0): Tương tự như giá, phân phối của số lượng cũng hoàn toàn đối xứng.
Kurtosis (1.7): Phân phối cũng bẹt hơn phân phối chuẩn, cho thấy các giá trị tập trung đều hơn và ít giá trị ngoại lai.
3. Biến Customer_Age (Tuổi khách hàng):
Mean (44.04) & Median (44.00): Độ tuổi trung bình và trung vị của khách hàng là khoảng 44 tuổi. Hai giá trị này rất gần nhau, cho thấy phân phối tuổi cũng rất cân đối.
SD (15.31): Độ lệch chuẩn khá lớn, cho thấy độ tuổi của các khách hàng rất đa dạng, không tập trung ở một nhóm tuổi cụ thể nào.
Min (18.00) & Max (70.00): Nhóm khách hàng trải dài từ 18 đến 70 tuổi.
Skewness (0): Phân phối tuổi hoàn toàn đối xứng. Lượng khách hàng trẻ tuổi và lớn tuổi được phân bổ đều quanh độ tuổi trung bình.
Kurtosis (1.8): Phân phối tuổi cũng bẹt hơn phân phối chuẩn.
Kết luận chung: Price là biến có độ biến động lớn nhất, tiếp theo là Customer_Age, và cuối cùng Quantity là biến ổn định và ít thay đổi nhất.Tất cả các biến đều có phân phối “bẹt hơn” so với phân phối chuẩn, nghĩa là dữ liệu ít tập trung vào đỉnh và có ít giá trị cực đoan ở hai bên. Cả ba biến đều có độ xiên (Skewness) bằng 0 nó cho thấy dữ liệu cực kỳ cân bằng và đối xứng.
qual_data <- BK %>% select(where(is.character) | where(is.factor))
for (col_name in names(qual_data))
{freq_table <- qual_data %>% count(!!sym(col_name), sort = TRUE) %>% mutate(Ty_le = n / sum(n) * 100) %>% mutate(Ty_le = round(Ty_le, 2)) %>% rename(Gia_tri = 1, Tan_so = n, "Ty_le (\\%)" = Ty_le)
table_for_pdf <- kable(freq_table, caption = paste("Bảng tần suất cho biến:", gsub("_", "\\\\_", col_name)), booktabs = TRUE) %>%
kable_styling(latex_options = c("striped", "hold_position"), full_width = FALSE, position = "center")
print(table_for_pdf)
cat("\n\n")}| Gia_tri | Tan_so | Ty_le (%) |
|---|---|---|
| BMX | 14377 | 14.38 |
| Road Bike | 14363 | 14.36 |
| Cruiser | 14332 | 14.33 |
| Folding Bike | 14329 | 14.33 |
| Hybrid Bike | 14319 | 14.32 |
| Electric Bike | 14169 | 14.17 |
| Mountain Bike | 14111 | 14.11 |
| Gia_tri | Tan_so | Ty_le (%) |
|---|---|---|
| New York | 14515 | 14.52 |
| Phoenix | 14385 | 14.38 |
| Philadelphia | 14330 | 14.33 |
| San Antonio | 14300 | 14.30 |
| Chicago | 14207 | 14.21 |
| Houston | 14149 | 14.15 |
| Los Angeles | 14114 | 14.11 |
| Gia_tri | Tan_so | Ty_le (%) |
|---|---|---|
| Apple Pay | 16751 | 16.75 |
| Debit Card | 16738 | 16.74 |
| Cash | 16692 | 16.69 |
| Credit Card | 16653 | 16.65 |
| Google Pay | 16613 | 16.61 |
| PayPal | 16553 | 16.55 |
| Gia_tri | Tan_so | Ty_le (%) |
|---|---|---|
| Female | 50227 | 50.23 |
| Male | 49773 | 49.77 |
Giải thích code:
(2): Tạo ra một vòng lặp. Với mỗi tên cột (col_name) có trong bảng qual_data, nó sẽ thực hiện toàn bộ khối lệnh nằm bên trong cặp dấu {…}.
(3): Dòng này có nhiệm vụ tính toán bảng tần suất cho biến hiện tại đang được xét trong vòng lặp.
Nhận xét:
1. Bảng tần suất cho biến Bike_Model (Loại xe đạp):
Nội dung: Bảng này liệt kê 7 loại xe đạp khác nhau (BMX, Road Bike, v.v.) và số lượng của mỗi loại.
Ý nghĩa quan trọng: Tần suất và tỷ lệ phần trăm của tất cả các loại xe đạp là gần như bằng nhau (tất cả đều xoay quanh 14.1% - 14.4%). Điều này cho thấy dữ liệu được phân bổ rất đều cho mỗi loại xe. Không có loại xe nào chiếm ưu thế vượt trội.
2. Bảng tần suất cho biến Store_Location (Vị trí cửa hàng):
Nội dung: Liệt kê số lượng bản ghi (có thể là giao dịch) tại 7 thành phố khác nhau.
Ý nghĩa quan trọng: Tương tự như loại xe, số lượng giao dịch tại mỗi thành phố cũng cực kỳ cân bằng (đều trong khoảng 14.1% - 14.5%). Mặc dù New York có nhiều hơn một chút, sự khác biệt là không đáng kể. Điều này cho thấy hoạt động kinh doanh hoặc việc thu thập dữ liệu diễn ra đồng đều trên tất cả các địa điểm.
3. Bảng tần suất cho biến Payment_Method (Phương thức thanh toán):
Nội dung: Thống kê 6 phương thức thanh toán khác nhau mà khách hàng đã sử dụng.
Ý nghĩa quan trọng: Một lần nữa, chúng ta lại thấy một sự cân bằng gần như hoàn hảo. Tất cả các phương thức thanh toán đều được sử dụng với tần suất rất giống nhau (khoảng 16.6% - 16.7%). Điều này có nghĩa là không có phương thức thanh toán nào được ưa chuộng hơn hẳn các phương thức còn lại trong tập dữ liệu này.
4. Bảng tần suất cho biến Customer_Gender (Giới tính khách hàng):
Nội dung: Thống kê số lượng khách hàng nam và nữ.
Ý nghĩa quan trọng: Tỷ lệ giữa khách hàng nữ (Female) và nam (Male) gần như là 50/50 (50.23% so với 49.77%). Đây là một sự cân bằng giới tính hoàn hảo.
total_revenue <- sum(BK1$Revenue)
cat("Tổng doanh thu:", dollar(total_revenue, prefix = "", suffix = " USD"))## Tổng doanh thu: 778,434,235 USD
Giải thích code: sum(BK1$Revenue): Tính tổng tất cả các giá trị trong cột Revenue của bộ dữ liệu BK1.
avg_revenue_per_sale <- mean(BK1$Revenue)
cat("Doanh thu trung bình mỗi giao dịch:", dollar(avg_revenue_per_sale, prefix = "", suffix = " USD"))## Doanh thu trung bình mỗi giao dịch: 7,784.34 USD
Giải thích code: mean(BK1$Revenue): Tính doanh thu trung bình cho mỗi giao dịch.
## Tổng số giao dịch: 100,000
Giải thích code: nrow(BK1): Đếm tổng số hàng trong bộ dữ liệu BK1.
avg_quantity_per_sale <- mean(BK1$Quantity)
cat("Số lượng xe trung bình mỗi giao dịch:", round(avg_quantity_per_sale, 2))## Số lượng xe trung bình mỗi giao dịch: 3
Giải thích code: mean(BK1$Quantity): Tính Số lượng sản phẩm trung bình mỗi giao dịch.
Nhận xét: Các chỉ số tổng quan cho thấy quy mô kinh doanh, giá trị trung bình mỗi đơn hàng và thói quen mua sắm (số lượng) của khách hàng.
revenue_by_price_level <- BK1 %>%
group_by(Price_Level) %>%
summarise(Total_Revenue = sum(Revenue)) %>%
arrange(desc(Total_Revenue))
kable(revenue_by_price_level, caption = "Tổng doanh thu theo mức giá")| Price_Level | Total_Revenue |
|---|---|
| Cao cấp | 290411858 |
| Hạng sang | 277944860 |
| Phổ thông | 210077517 |
Nhận xét: Doanh thu cốt lõi của công ty đến từ các sản phẩm ở mức giá Cao cấp và Hạng sang (chiếm phần lớn nhất trong tổng doanh thu).
quantity_by_price_level <- BK1 %>%
group_by(Price_Level) %>%
summarise(Total_Quantity = sum(Quantity)) %>%
arrange(desc(Total_Quantity))
kable(quantity_by_price_level, caption = "Tổng số lượng bán theo mức giá")| Price_Level | Total_Quantity |
|---|---|
| Phổ thông | 150097 |
| Cao cấp | 87869 |
| Hạng sang | 61745 |
Nhận xét:
Phân khúc “Phổ thông” chiếm ưu thế vượt trội về số lượng bán: Với 150.097 sản phẩm được bán ra, số lượng bán của phân khúc Phổ thông gấp khoảng 1.7 lần so với phân khúc Cao cấp và gấp khoảng 2.4 lần so với phân khúc Hạng sang.
Xu hướng giảm dần rõ rệt theo mức giá: Có một mối quan hệ nghịch đảo rõ ràng: càng lên phân khúc giá cao hơn (“Cao cấp”, “Hạng sang”), tổng số lượng sản phẩm bán ra càng giảm.
transactions_by_price_level <- BK1 %>%
count(Price_Level, name = "Transaction_Count", sort = TRUE)
kable(transactions_by_price_level, caption = "Số lượng giao dịch theo mức giá")| Price_Level | Transaction_Count |
|---|---|
| Phổ thông | 50030 |
| Cao cấp | 29341 |
| Hạng sang | 20629 |
Nhận xét:
Đây là tổng số lần các giao dịch (đơn hàng) đã xảy
ra trong mỗi Price_Level. Một giao dịch có thể bao gồm việc
mua nhiều sản phẩm.
Phân khúc “Phổ thông” dẫn đầu về số lượt mua hàng: Với 50.030 giao dịch, các sản phẩm ở mức giá “Phổ thông” thu hút số lượng lượt mua lớn nhất.
Xu hướng giảm dần theo mức giá: Tương tự như tổng số lượng bán, số lượng giao dịch cũng có xu hướng giảm khi mức giá sản phẩm tăng lên. Điều này là dấu hiệu cho thấy phân khúc giá cao hơn có tệp khách hàng nhỏ hơn hoặc tần suất mua sắm ít thường xuyên hơn.
transactions_by_price_level_and_store <- BK1 %>%
group_by(Store_Location, Price_Level) %>%
count(name = "Transaction_Count") %>%
arrange(Store_Location, desc(Transaction_Count))
kable(transactions_by_price_level_and_store, caption = "Số lượng giao dịch theo mức giá tại mỗi cửa hàng")| Store_Location | Price_Level | Transaction_Count |
|---|---|---|
| Chicago | Phổ thông | 7082 |
| Chicago | Cao cấp | 4213 |
| Chicago | Hạng sang | 2912 |
| Houston | Phổ thông | 7126 |
| Houston | Cao cấp | 4093 |
| Houston | Hạng sang | 2930 |
| Los Angeles | Phổ thông | 7075 |
| Los Angeles | Cao cấp | 4157 |
| Los Angeles | Hạng sang | 2882 |
| New York | Phổ thông | 7254 |
| New York | Cao cấp | 4277 |
| New York | Hạng sang | 2984 |
| Philadelphia | Phổ thông | 7105 |
| Philadelphia | Cao cấp | 4189 |
| Philadelphia | Hạng sang | 3036 |
| Phoenix | Phổ thông | 7179 |
| Phoenix | Cao cấp | 4232 |
| Phoenix | Hạng sang | 2974 |
| San Antonio | Phổ thông | 7209 |
| San Antonio | Cao cấp | 4180 |
| San Antonio | Hạng sang | 2911 |
Nhận xét:
Mô hình Phân bổ Giao dịch Nhất quán: Thứ tự số lượng giao dịch theo mức giá là Phổ thông > Cao cấp > Hạng sang được lặp lại chính xác tại tất cả 7 thành phố. Điều này chỉ ra rằng thói quen tiêu dùng và cơ cấu nhu cầu sản phẩm là ổn định trên khắp các thị trường địa lý này.
Phổ thông là động lực khối lượng: Phân khúc Phổ thông luôn là động lực chính tạo ra khối lượng giao dịch (Volume) tại mọi cửa hàng, với số lượng vượt trội so với hai phân khúc còn lại.
Hiệu suất cửa hàng đồng đều: Sự chênh lệch về tổng số lượng giao dịch giữa các thành phố cho cùng một mức giá là rất nhỏ. Cửa hàng New York có số lượng giao dịch cao nhất ở cả ba phân khúc, khẳng định vị thế dẫn đầu về hiệu suất giao dịch tổng thể.
revenue_by_store <- BK1 %>%
group_by(Store_Location) %>%
summarise(Total_Revenue = sum(Revenue)) %>%
arrange(desc(Total_Revenue))
kable(revenue_by_store, caption = "Doanh thu theo địa điểm cửa hàng")| Store_Location | Total_Revenue |
|---|---|
| New York | 113592474 |
| Phoenix | 111860420 |
| Philadelphia | 111765829 |
| Houston | 110427042 |
| Chicago | 110388139 |
| Los Angeles | 110340851 |
| San Antonio | 110059479 |
transactions_by_store <- BK1 %>%
count(Store_Location, name = "Transaction_Count", sort = TRUE)
kable(transactions_by_store, caption = "Số lượng giao dịch theo cửa hàng")| Store_Location | Transaction_Count |
|---|---|
| New York | 14515 |
| Phoenix | 14385 |
| Philadelphia | 14330 |
| San Antonio | 14300 |
| Chicago | 14207 |
| Houston | 14149 |
| Los Angeles | 14114 |
avg_revenue_by_store <- BK1 %>%
group_by(Store_Location) %>%
summarise(Average_Revenue_Per_Sale = mean(Revenue)) %>%
arrange(desc(Average_Revenue_Per_Sale))
kable(avg_revenue_by_store, caption = "Giá trị giao dịch trung bình tại mỗi cửa hàng")| Store_Location | Average_Revenue_Per_Sale |
|---|---|
| New York | 7825.868 |
| Los Angeles | 7817.830 |
| Houston | 7804.583 |
| Philadelphia | 7799.430 |
| Phoenix | 7776.185 |
| Chicago | 7769.982 |
| San Antonio | 7696.467 |
top_salesperson <- BK1 %>%
group_by(Salesperson_ID) %>%
summarise(Total_Revenue = sum(Revenue)) %>%
arrange(desc(Total_Revenue)) %>%
top_n(5, Total_Revenue)
kable(top_salesperson, caption = "Top 5 nhân viên có doanh thu cao nhất")| Salesperson_ID | Total_Revenue |
|---|---|
| 794 | 1196096 |
| 500 | 1137238 |
| 605 | 1125912 |
| 655 | 1123964 |
| 914 | 1123278 |
Nhận xét: Các thống kê này giúp đánh giá hiệu quả hoạt động của từng chi nhánh và hiệu suất làm việc của cá nhân, từ đó có chính sách khen thưởng hoặc cải thiện phù hợp.
mean_customer_age <- mean(BK1$Customer_Age)
cat("Độ tuổi trung bình của khách hàng:", round(mean_customer_age, 1), "tuổi\n")## Độ tuổi trung bình của khách hàng: 44 tuổi
revenue_by_age_group <- BK1 %>%
group_by(Age_Group) %>%
summarise(
Total_Revenue = sum(Revenue),
Average_Purchase_Value = mean(Revenue)
) %>%
arrange(desc(Total_Revenue))
kable(revenue_by_age_group, caption = "Doanh thu theo nhóm tuổi")| Age_Group | Total_Revenue | Average_Purchase_Value |
|---|---|---|
| Trung niên | 367911187 | 7820.244 |
| Lớn tuổi | 220315371 | 7741.501 |
| Trẻ | 190207676 | 7765.163 |
Nhận xét:
Mức độ đóng góp doanh thu giảm dần từ Trung niên > Thanh niên > Cao tuổi.
Nhóm Trung niên (36-55) là khách hàng cốt lõi tạo ra phần lớn doanh thu, trong khi thói quen chi tiêu trung bình trên mỗi lần mua hàng là tương đối nhất quán giữa các nhóm tuổi.
transactions_by_age_group <- BK1 %>%
count(Age_Group, name = "Transaction_Count", sort = TRUE)
kable(transactions_by_age_group, caption = "Số lượng giao dịch theo nhóm tuổi")| Age_Group | Transaction_Count |
|---|---|
| Trung niên | 47046 |
| Lớn tuổi | 28459 |
| Trẻ | 24495 |
model_preference_by_age_group <- BK1 %>%
group_by(Age_Group, Bike_Model) %>%
summarise(Total_Quantity = sum(Quantity), .groups = 'drop') %>%
group_by(Age_Group) %>%
top_n(1, Total_Quantity) %>%
arrange(Age_Group)
kable(model_preference_by_age_group, caption = "Mẫu xe được ưa chuộng nhất theo nhóm tuổi")| Age_Group | Bike_Model | Total_Quantity |
|---|---|---|
| Lớn tuổi | Cruiser | 12446 |
| Trung niên | Folding Bike | 20635 |
| Trẻ | Cruiser | 10758 |
Nhận xét:
Phân khúc Trung niên có nhu cầu khác biệt: Nhóm Middle-Aged (36-55) có sở thích khác biệt rõ rệt, ưa chuộng Folding Bike (Xe đạp gấp) với số lượng bán cao nhất (16,601 chiếc). Điều này có thể phản ánh nhu cầu về sự tiện lợi, khả năng mang theo và tiết kiệm không gian cho nhóm tuổi thường xuyên di chuyển hoặc đi làm.
Mẫu Cruiser được ưa chuộng bởi nhóm trẻ và cao tuổi.
Nhóm Young mua Cruiser với số lượng cao hơn đáng kể (14,941 chiếc) so với nhóm Senior (12,446 chiếc).
price_level_preference_by_age <- BK1 %>%
group_by(Age_Group, Price_Level) %>%
summarise(Total_Quantity = sum(Quantity), .groups = 'drop') %>%
group_by(Age_Group) %>%
top_n(1, Total_Quantity) %>%
arrange(Age_Group)
kable(price_level_preference_by_age, caption = "Mức giá ưa chuộng theo nhóm tuổi")| Age_Group | Price_Level | Total_Quantity |
|---|---|---|
| Lớn tuổi | Phổ thông | 42452 |
| Trung niên | Phổ thông | 70529 |
| Trẻ | Phổ thông | 37116 |
Nhận xét: Khi xét về mức giá, phân khúc Phổ thông là lựa chọn số một phổ quát, với nhóm Trung niên dẫn đầu về khối lượng mua.
revenue_by_gender <- BK1 %>%
group_by(Customer_Gender) %>%
summarise(
Total_Revenue = sum(Revenue),
Average_Purchase_Value = mean(Revenue)
) %>%
arrange(desc(Total_Revenue))
kable(revenue_by_gender, caption = "Doanh thu theo giới tính")| Customer_Gender | Total_Revenue | Average_Purchase_Value |
|---|---|---|
| Female | 392314349 | 7810.826 |
| Male | 386119886 | 7757.617 |
Nhận xét: Mức đóng góp doanh thu và thói quen chi tiêu trung bình trên mỗi lần mua hàng của hai giới là gần như ngang nhau.
transactions_by_gender <- BK1 %>%
count(Customer_Gender, name = "Transaction_Count", sort = TRUE)
kable(transactions_by_gender, caption = "Số lượng giao dịch theo giới tính")| Customer_Gender | Transaction_Count |
|---|---|
| Female | 50227 |
| Male | 49773 |
Nhận xét: Phân tích này giúp chúng ta hiểu rõ hơn về tần suất giao dịch của từng nhóm giới tính. Giới tính nào có số lượng giao dịch nhiều hơn có thể là nhóm khách hàng năng động hơn.
model_preference_by_gender <- BK1 %>%
group_by(Customer_Gender, Bike_Model) %>%
summarise(Total_Quantity = sum(Quantity), .groups = 'drop') %>%
group_by(Customer_Gender) %>%
top_n(1, Total_Quantity) %>%
arrange(Customer_Gender)
kable(model_preference_by_gender, caption = "Mẫu xe được ưa chuộng nhất theo giới tính")| Customer_Gender | Bike_Model | Total_Quantity |
|---|---|---|
| Female | BMX | 21870 |
| Male | Road Bike | 21490 |
Nhận xét: Bảng này sẽ chỉ ra những mẫu xe đạp nào được ưa chuộng nhất bởi nam giới và nữ giới. Thông tin này rất hữu ích cho việc quản lý tồn kho, marketing và phát triển sản phẩm.
price_level_preference_by_gender <- BK1 %>%
group_by(Customer_Gender, Price_Level) %>%
summarise(Total_Quantity = sum(Quantity), .groups = 'drop') %>%
group_by(Customer_Gender) %>%
top_n(1, Total_Quantity) %>%
arrange(Customer_Gender)
kable(price_level_preference_by_gender, caption = "Mức giá ưa chuộng theo giới tính")| Customer_Gender | Price_Level | Total_Quantity |
|---|---|---|
| Female | Phổ thông | 75240 |
| Male | Phổ thông | 74857 |
Nhận xét: Phổ thông là phân khúc chiến lược về mặt khối lượng. Hơn nữa, sự đồng đều này cho thấy chiến lược quản lý tồn kho và tiếp thị có thể được thiết kế để phục vụ cân bằng nhu cầu mua hàng số lượng lớn của cả khách hàng Nam và Nữ trong phân khúc giá thấp hơn.
loyalty_distribution <- BK1 %>%
distinct(Customer_ID, .keep_all = TRUE) %>%
count(Loyalty_Status) %>%
mutate(Percentage = n / sum(n) * 100)
kable(loyalty_distribution, caption = "Phân bổ khách hàng theo hạng thân thiết")| Loyalty_Status | n | Percentage |
|---|---|---|
| Khách hàng mới | 2994 | 33.26667 |
| Thân thiết | 4628 | 51.42222 |
| VIP | 1378 | 15.31111 |
Giải thích code:
Nhận xét:
Khách hàng Platinum là phân khúc lớn nhất, chiếm 66.73% (6,006 người).
Khách hàng Gold đứng thứ hai, chiếm 31.69% (2,852 người).
Tổng cộng, hai hạng này chiếm khoảng 98.42% tổng số khách hàng thân thiết.
Các hạng Silver và Bronze chiếm tỷ lệ rất nhỏ: Silver chỉ chiếm 1.54% (139 người), và Bronze gần như không đáng kể (0.03%, 3 người).
Ý nghĩa Chiến lược: Chương trình khách hàng thân thiết đang hoạt động hiệu quả trong việc thúc đẩy khách hàng đạt và duy trì các cấp độ cao, tạo ra một cơ sở khách hàng VIP rất mạnh mẽ và tập trung.
total_unique_customers <- n_distinct(BK1$Customer_ID)
returning_customers <- customer_frequency %>% filter(Purchase_Count > 1) %>% nrow()
cat("Tỷ lệ khách hàng quay lại:", percent(returning_customers / total_unique_customers,
accuracy = 0.1))## Tỷ lệ khách hàng quay lại: 100.0%
revenue_by_loyalty <- BK1 %>%
group_by(Loyalty_Status) %>%
summarise(Total_Revenue = sum(Revenue)) %>%
arrange(desc(Total_Revenue))
kable(revenue_by_loyalty, caption = "Tổng doanh thu theo hạng khách hàng")| Loyalty_Status | Total_Revenue |
|---|---|
| Thân thiết | 425059285 |
| VIP | 177937892 |
| Khách hàng mới | 175437058 |
Nhận xét: Khách hàng Platinum là nguồn doanh thu cốt lõi, tạo ra 603 triệu đơn vị tiền tệ. Doanh thu của hạng Platinum lớn hơn gấp hơn 3.5 lần so với hạng Gold (171 triệu). Hai hạng thấp nhất (Silver và Bronze) đóng góp tổng cộng rất ít vào doanh thu (chỉ khoảng 400.000 đơn vị). Sự khác biệt này củng cố rằng công ty nên tập trung tài nguyên và các chương trình duy trì vào các hạng Platinum và Gold, vì họ là nguồn giá trị tài chính duy nhất có ý nghĩa.
avg_revenue_by_loyalty <- BK1 %>%
group_by(Loyalty_Status) %>%
summarise(Average_Revenue_Per_Sale = mean(Revenue)) %>%
arrange(desc(Average_Revenue_Per_Sale))
kable(avg_revenue_by_loyalty, caption = "Giá trị đơn hàng trung bình theo hạng khách hàng")| Loyalty_Status | Average_Revenue_Per_Sale |
|---|---|
| VIP | 7805.321 |
| Khách hàng mới | 7783.020 |
| Thân thiết | 7776.139 |
Nhận xét: Đánh giá hiệu quả của chương trình khách hàng thân thiết. Khách hàng hạng VIP chi tiêu nhiều hơn và phần lớn doanh thu đến từ nhóm khách hàng này.
revenue_by_year <- BK1 %>%
group_by(Year) %>%
summarise(Total_Revenue = sum(Revenue))
kable(revenue_by_year, caption = "Doanh thu theo năm", col.names = c ("Năm", "Tổng doanh thu"))| Năm | Tổng doanh thu |
|---|---|
| 2020 | 164224491 |
| 2021 | 163535245 |
| 2022 | 165560749 |
| 2023 | 164497574 |
| 2024 | 120616177 |
Giải thích code:
(2): Nhóm tất cả các hàng trong data frame BK1 lại với nhau dựa trên giá trị của cột Year (Năm).
(3): Tính tổng của cột Revenue (Doanh thu) cho mỗi nhóm (tức là cho mỗi năm) và tạo một cột mới tên là Total_Revenue để lưu kết quả này.
Nhận xét:
revenue_by_season <- BK1 %>%
group_by(Season) %>%
summarise(Total_Revenue = sum(Revenue)) %>%
arrange(desc(Total_Revenue))
kable(revenue_by_season, caption = "Doanh thu theo mùa", col.names = c("Mùa", "Tổng doanh thu"))| Mùa | Tổng doanh thu |
|---|---|
| Hạ | 207337508 |
| Xuân | 207312828 |
| Đông | 188748226 |
| Thu | 175035674 |
Giải thích code:
(2): Nhóm tất cả các hàng trong data frame BK1 lại với nhau dựa trên giá trị của cột Season (Mùa).
(3): Tính tổng của cột Revenue (Doanh thu) cho mỗi nhóm (tức là cho mỗi mùa) và tạo một cột mới tên là Total_Revenue để lưu kết quả này.
Nhận xét:
Mùa cao điểm: Mùa Hè và Mùa Xuân là hai mùa kinh doanh sôi động nhất, mang lại doanh thu cao và gần như tương đương nhau. Đây là giai đoạn “vàng” của công ty.
Mùa thấp điểm: Ngược lại, Mùa Thu là mùa có doanh thu thấp nhất, cho thấy nhu cầu mua sắm giảm đáng kể trong giai đoạn này, theo sau là Mùa Đông.
revenue_by_weekday <- BK1 %>%
group_by(Weekday) %>%
summarise(Total_Revenue = sum(Revenue)) %>% arrange(desc(Total_Revenue))
kable(revenue_by_weekday, caption = "Doanh thu theo ngày trong tuần", col.names = c("Ngày", "Tổng doanh thu"))| Ngày | Tổng doanh thu |
|---|---|
| Thursday | 113678630 |
| Friday | 111803200 |
| Wednesday | 111762837 |
| Saturday | 111409344 |
| Monday | 110441224 |
| Sunday | 110304080 |
| Tuesday | 109034920 |
Giải thích code:
(2): Nhóm tất cả các hàng trong data frame BK1 lại với nhau dựa trên giá trị của cột Weekday (Ngày trong tuần).
(3): Tính tổng của cột Revenue (Doanh thu) cho mỗi nhóm (tức là cho mỗi ngày trong tuần) và sắp xếp các hàng theo thứ tự giảm dần của cột Total_Revenue, tức là ngày có doanh thu cao nhất sẽ được xếp lên đầu.
Nhận xét: Hành vi mua sắm của khách hàng không bị phụ thuộc nhiều vào các ngày nghỉ. Điều này cho thấy khách hàng có thể là những người có nhu cầu ổn định, mua sắm thường xuyên trong suốt cả tuần. Sự ổn định này giúp việc lên kế hoạch nhân sự và quản lý hàng tồn kho trở nên dễ dàng và hiệu quả hơn.
price_level_popularity <- BK1 %>%
count(Price_Level, sort = TRUE) %>%
mutate(Percentage = round(n / sum(n) * 100, 2))
kable(price_level_popularity, caption = "Mức độ phổ biến của các phân khúc giá", col.names = c("Phân khúc giá", "Số lượng giao dịch", "Tỷ lệ"))| Phân khúc giá | Số lượng giao dịch | Tỷ lệ |
|---|---|---|
| Phổ thông | 50030 | 50.03 |
| Cao cấp | 29341 | 29.34 |
| Hạng sang | 20629 | 20.63 |
Giải thích code:
(2): Đếm số lần xuất hiện của mỗi giá trị duy nhất trong cột Price_Level (Phân khúc giá).
(3): Lấy số lượng giao dịch của từng phân khúc chia cho tổng số lượng rồi nhân 100 để ra tỷ lệ phần trăm và tạo một cột mới có tên là Percentage.
Nhận xét: Dòng sản phẩm “Phổ thông” với 50.03% tổng số lượng giao dịch, phục vụ cho số đông và là nguồn doanh số chính. Các phân khúc “Cao cấp” và “Hạng sang” tuy chiếm tỷ trọng nhỏ hơn nhưng vẫn đóng vai trò quan trọng trong việc đa dạng hóa danh mục, nâng cao hình ảnh thương hiệu và phục vụ các nhóm khách hàng có khả năng chi trả cao hơn.
model_preference_by_location <- BK1 %>%
group_by(Store_Location, Bike_Model) %>%
summarise(Total_Quantity = sum(Quantity), .groups = 'drop') %>%
group_by(Store_Location) %>% top_n(1, Total_Quantity)
kable(model_preference_by_location, caption = "Mẫu xe được ưa chuộng nhất theo địa điểm", col.names = c("Địa điểm","Mẫu xe", "Tổng số lượng bán"))| Địa điểm | Mẫu xe | Tổng số lượng bán |
|---|---|---|
| Chicago | Hybrid Bike | 6318 |
| Houston | Road Bike | 6243 |
| Los Angeles | Folding Bike | 6251 |
| New York | Hybrid Bike | 6422 |
| Philadelphia | BMX | 6253 |
| Phoenix | Cruiser | 6442 |
| San Antonio | Folding Bike | 6248 |
Giải thích code:
(2): Nhóm data frame BK1 theo hai cột là Store_Location (Địa điểm cửa hàng) và Bike_Model (Mẫu xe).
(3): Đối với mỗi nhóm (tức là mỗi mẫu xe tại mỗi địa điểm), nó sẽ tính tổng của cột Quantity (Số lượng).
(4): Với mỗi nhóm Store_Location, hàm này sẽ lọc và chỉ giữ lại 1 hàng (n=1) có giá trị cao nhất trong cột Total_Quantity. Kết quả là nó sẽ tìm ra mẫu xe (Bike_Model) bán chạy nhất cho từng địa điểm cửa hàng.
Nhận xét: Phân tích cho thấy thị hiếu của khách hàng có sự khác biệt rõ rệt theo từng địa điểm. Mỗi thành phố lại có một mẫu xe bán chạy nhất riêng, ví dụ như Cruiser tại Phoenix hay BMX tại Philadelphia.
correlation_re_price <- cor(BK1$Revenue, BK1$Price)
print(paste("Hệ số tương quan giữa doanh thu và giá xe:", round(correlation_re_price, 4)))## [1] "Hệ số tương quan giữa doanh thu và giá xe: 0.7055"
Giải thích code: Hàm cor() trong R để tính toán hệ số tương quan Pearson giữa hai biến.
Nhận xét: Kết quả 0.7055, một con số dương và khá gần với 1. Điều này cho thấy có một mối quan hệ tương quan dương mạnh giữa giá xe (Price) và doanh thu (Revenue) của một giao dịch. Nói một cách đơn giản: Khi giá của chiếc xe trong một giao dịch tăng lên, thì doanh thu của giao dịch đó cũng có xu hướng tăng theo. Mối quan hệ này là hợp lý và đúng với mong đợi trong kinh doanh.
Trong mục này, chúng ta sẽ sử dụng thư viện ggplot2 và các phần mở rộng của nó để trực quan hóa các insight đã tìm thấy, giúp truyền tải thông tin một cách sinh động và dễ hiểu.
revenue_by_model <- BK1 %>%
group_by(Bike_Model) %>%
summarise(Total_Revenue = sum(Revenue))
ggplot(revenue_by_model,
aes(x = reorder(Bike_Model, -Total_Revenue),
y = Total_Revenue,
fill = Bike_Model)) +
geom_col(show.legend = FALSE) + # Vẽ cột, ẩn chú giải màu
geom_text(aes(label = dollar(Total_Revenue, accuracy = 1)),
angle = 90, # Xoay nhãn dọc
vjust = 0.5, # Căn giữa theo chiều cao cột
hjust = 1.1, # Đưa nhãn vào trong thân cột
color = "white", # Màu chữ trắng nổi bật
size = 3.2,
fontface = "bold") +
labs(title = "Tổng doanh thu theo từng mẫu xe",
x = "Mẫu xe",
y = "Tổng doanh thu") +
scale_y_continuous(labels = dollar_format()) +
theme_minimal(base_size = 13) +
theme(axis.text.x = element_text(angle = 45, hjust = 1))Giải thích code:
Nhận xét: Biểu đồ cho thấy mẫu xe Hybrid Bike có tổng doanh thu cao nhất. Khoảng cách doanh thu giữa các mẫu xe khá nhỏ, kết quả giúp nhà quản lý xác định đâu là sản phẩm chủ lực.
revenue_by_store <- BK1 %>%
group_by(Store_Location) %>% summarise(Total_Revenue = sum(Revenue))
ggplot(revenue_by_store, aes(x = "", y = Total_Revenue, fill = Store_Location)) +
geom_bar(stat = "identity", width = 0.8) +
coord_polar("y", start = 0) +
geom_text(aes(label = scales::percent(Total_Revenue / sum(Total_Revenue), accuracy = 1)), position = position_stack(vjust = 0.5), color = "white", size = 5) +
labs(title = "Tỷ lệ phân bổ doanh thu theo địa điểm cửa hàng", fill = "Địa điểm", x = NULL, y = NULL) +
theme_void() +
theme(legend.title = element_text(face = "bold"))Giải thích code:
(2): Gom nhóm tất cả các giao dịch theo từng cửa hàng riêng biệt, với mỗi cửa hàng, tính tổng doanh thu mà nó tạo ra.
(3): Khởi tạo biểu đồ với x = “” để vẽ tất cả các cột chồng lên nhau tại một vị trí duy nhất và chiều cao của các đoạn trong cột chồng được quyết định bởi doanh thu.
(4): Vẽ một biểu đồ cột chồng duy nhất.
(5): Nó biến đổi hệ tọa độ của biểu đồ cột chồng thành hệ tọa độ cực, uốn cong thanh cột đó thành một hình tròn, tạo ra biểu đồ tròn mà chúng ta thấy.
Nhận xét: Cửa hàng ở New York hoạt động tốt nhất và chỉ là cao hơn các địa điểm khác 1%. Điều này cho thấy hiệu suất kinh doanh của các chi nhánh đang ở mức tương đối cân bằng.
revenue_by_store1 <- BK1 %>%
group_by(Store_Location) %>%
summarise(Total_Revenue = sum(Revenue), .groups = "drop")
# Vẽ biểu đồ
ggplot(revenue_by_store1,
aes(x = reorder(Store_Location, -Total_Revenue),
y = Total_Revenue,
fill = Store_Location)) +
geom_col(show.legend = FALSE) +
geom_text(aes(label = paste0("$", format(Total_Revenue, big.mark = ",", scientific = FALSE))),
angle = 90, # 🔹 Xoay chữ dọc
vjust = 0.5, # 🔹 Căn giữa theo chiều cao
hjust = 1.1, # 🔹 Đẩy chữ vào trong cột
color = "white",
size = 3.2,
fontface = "bold") +
labs(title = "Doanh thu theo địa điểm cửa hàng (Barplot)",
subtitle = "Các địa điểm có doanh thu gần tương đương nhau",
x = "Địa điểm cửa hàng",
y = "Tổng doanh thu") +
scale_y_continuous(labels = dollar_format(prefix = "$", big.mark = ",")) +
theme_minimal(base_size = 13) +
theme(axis.text.x = element_text(angle = 45, hjust = 1))Giải thích code:
Nhận xét Dễ dàng nhìn thấy cửa hàng ở New York đóng góp cao nhất. Kết quả cho thấy toàn bộ hệ thống cửa hàng đang hoạt động một cách ổn định và cân bằng, không có chi nhánh nào tỏ ra vượt trội hay yếu kém một cách đáng kể so với phần còn lại.
ggplot(revenue_by_store, aes(area = Total_Revenue, fill = Store_Location, label = Store_Location)) +
geom_treemap() +
geom_treemap_text(colour = "white", place = "centre", size = 14, grow = TRUE) +
labs(title = "Tỷ trọng doanh thu theo địa điểm cửa hàng (Treemap)",
fill = "Địa điểm cửa hàng") +
theme_minimal()Giải thích code:
Nhận xét Diện tích mỗi ô = mức doanh thu → dùng để kiểm tra store nào chiếm tỷ trọng lớn.
revenue_by_model_store <- BK1 %>%
group_by(Bike_Model, Store_Location) %>%
summarise(Total_Revenue = sum(Revenue), .groups = "drop")
ggplot(revenue_by_model_store,
aes(x = reorder(Bike_Model, -Total_Revenue),
y = Total_Revenue,
fill = Store_Location)) +
geom_bar(stat = "identity", position = "dodge") +
labs(title = "So sánh doanh thu của từng mẫu xe theo địa điểm cửa hàng",
subtitle = "Giúp xác định mẫu xe nào là sản phẩm chủ lực tại từng địa điểm",
x = "Mẫu xe",
y = "Tổng doanh thu",
fill = "Địa điểm cửa hàng") +
scale_y_continuous(labels = scales::dollar) +
theme_minimal() +
theme(axis.text.x = element_text(angle = 45, hjust = 1))Giải thích code:
Nhận xét:
So sánh hiệu suất sản phẩm: Có thể dễ dàng thấy mẫu xe nào đang tạo ra doanh thu cao nhất hoặc thấp nhất tại một địa điểm cụ thể. Ví dụ, tại New York, “Road Bike” và “Hybrid Bike” có vẻ là những mẫu xe có doanh thu cao nhất.
So sánh hiệu suất cửa hàng: Đối với một mẫu xe nhất định, ta có thể so sánh xem cửa hàng ở thành phố nào bán chạy nhất. Ví dụ, với “Mountain Bike”, cửa hàng ở Los Angeles (cột màu xanh lá) có doanh thu cao hơn hẳn so với các cửa hàng khác.
Xác định sản phẩm chủ lực: Đúng như phụ đề của biểu đồ, mục đích chính là “Giúp xác định mẫu xe nào là sản phẩm chủ lực tại từng địa điểm”. Dựa vào chiều cao các cột, nhà quản lý có thể biết được ở Chicago nên tập trung vào sản phẩm nào, ở Houston nên tập trung vào sản phẩm nào, từ đó đưa ra các chiến lược kinh doanh và marketing phù hợp cho từng khu vực.
revenue_over_time <- BK1 %>%
mutate(YearMonth = floor_date(Date, "month")) %>%
group_by(YearMonth) %>%
summarise(Monthly_Revenue = sum(Revenue), .groups = "drop")
ggplot(revenue_over_time, aes(x = YearMonth, y = Monthly_Revenue)) +
geom_line(color = "steelblue", size = 1) +
geom_point(color = "darkred", size = 2, shape = 21, fill = "yellow") +
labs(title = "Xu hướng doanh thu hàng tháng",
subtitle = "Phân tích doanh thu từ 2022 đến 2024",
x = "Thời Gian", y = "Doanh thu tháng") +
scale_y_continuous(labels = scales::dollar) +
scale_x_date(date_breaks = "3 months", date_labels = "%b %Y") +
theme_light() +
theme(axis.text.x = element_text(angle = 45, hjust = 1))Giải thích code:
Ý nghĩa: Doanh thu hàng tháng không ổn định mà dao động khá mạnh, thường nằm trong khoảng từ 13 triệu đến 14.5 triệu đô la. Có những đỉnh và đáy rõ rệt qua các tháng. Thường có sự sụt giảm doanh thu vào khoảng tháng 4 và một sự tăng trưởng vào giữa hoặc cuối năm. Điều này có thể liên quan đến các yếu tố mùa vụ trong kinh doanh.
revenue_over_time <- BK1 %>%
mutate(YearMonth = floor_date(Date, "month")) %>%
group_by(YearMonth) %>%
summarise(Monthly_Revenue = sum(Revenue), .groups = "drop") %>%
mutate(MA3 = rollmean(Monthly_Revenue, k = 3, fill = NA, align = "right"))
ggplot(revenue_over_time, aes(x = YearMonth)) +
geom_line(aes(y = Monthly_Revenue), color = "steelblue", size = 1) +
geom_point(aes(y = Monthly_Revenue), color = "darkred", size = 2, shape = 21, fill = "yellow") +
# Đường trung bình động 3 tháng
geom_line(aes(y = MA3), color = "orange", size = 1.2, linetype = "solid") +
labs(title = "Xu hướng doanh thu hàng tháng",subtitle = "Thêm đường trung bình động 3 tháng (MA3) để làm mượt xu hướng",
x = "Thời gian",
y = "Doanh thu tháng") +
scale_y_continuous(labels = scales::dollar) +
scale_x_date(date_breaks = "3 months", date_labels = "%b %Y") +
theme_light() +
theme(axis.text.x = element_text(angle = 45, hjust = 1))Giải thích code:
Ý nghĩa:
Đường MA3 cho thấy trong suốt giai đoạn từ 2020 đến giữa 2024, xu hướng doanh thu dài hạn là tương đối ổn định, dao động quanh mốc 13.5 đến 14 triệu đô la. Biểu đồ này cung cấp một cái nhìn sâu sắc và đáng tin cậy hơn về hiệu suất kinh doanh. Nó không chỉ cho thấy những gì đã xảy ra hàng tháng mà còn giúp lọc ra các biến động ngắn hạn để tập trung vào xu hướng cốt lõi, giúp các nhà quản lý đưa ra quyết định chiến lược tốt hơn.
Biểu đồ hộp cho thấy “Electric Bolt” không chỉ có giá trung bình cao nhất mà còn có khoảng giá rất rộng. Ngược lại, “Kids Explorer” có giá thấp và ít biến động.
ggplot(BK1, aes(x = reorder(Bike_Model, Price), y = Price, fill = Bike_Model)) +
geom_boxplot(show.legend = FALSE, outlier.colour = "red", outlier.shape = 8) +
labs(title = "Phân bổ giá bán của các mẫu xe",
subtitle = "So sánh sự biến động giá và các giá trị ngoại lai",
x = "Mẫu xe", y = "Giá bán") +
scale_y_continuous(labels = scales::dollar) +
stat_summary(fun = mean, geom = "point", shape = 23, size = 4, fill = "white") + # Thêm điểm trung bình
theme_bw() +
theme(axis.text.x = element_text(angle = 45, hjust = 1))Giải thích code:
Ý nghĩa: Biểu đồ hộp cho thấy các mẫu xe có giá trung bình và khoảng giá khá bằng nhau.
ggplot(BK1, aes(x = Price)) +
geom_density(fill = "orange", alpha = 0.6, color = "black", size = 1) +
labs(title = "Phân bố giá xe trong dữ liệu bán hàng",
x = "Giá bán (Price)",
y = "Mật độ") +
theme_minimal()Giải thích code:
Ý nghĩa: Biểu đồ mật độ cho thấy cách giá xe được phân bố trong dữ liệu bán hàng. Đường mật độ tương đối phẳng ở phần trung tâm (khoảng 1.000–4.000), cho thấy phần lớn xe có giá nằm trong khoảng này và phân bố khá đồng đều, không tập trung mạnh vào một mức giá cụ thể. Ở hai đầu phân bố (giá rất thấp hoặc rất cao) ta thấy mật độ giảm xuống, nghĩa là số lượng xe giá quá rẻ hoặc quá đắt là ít.
BK1Density <- BK1
BK1Density %>%
ggplot(aes(x = Price)) +
geom_density(fill = "blue", alpha = 0.7) +
facet_wrap(~ Bike_Model) +
labs(
title = "Phân bố giá theo từng mẫu xe",
x = "Giá bán (Price)",
y = "Mật độ"
) +
theme_minimal()Giải thích code:
Ý nghĩa: Giá bán của các mẫu xe nhìn chung phân bố khá đồng đều trong khoảng 500 – 5000 USD, không có sự chênh lệch quá lớn giữa các dòng xe. Tuy nhiên, một số dòng như BMX và Cruiser có xu hướng tập trung giá ở mức thấp hơn, trong khi Electric Bike và Hybrid Bike có phân bố giá rộng hơn, phản ánh các dòng xe này có thể có nhiều phiên bản hoặc phân khúc khác nhau. Điều này cho thấy thị trường sản phẩm có cấu trúc giá đa dạng nhưng vẫn cân đối giữa các nhóm xe.
ggplot(BK1, aes(x = Customer_Age)) +
geom_histogram(binwidth = 5, fill = "skyblue", color = "black", alpha = 0.7) +
geom_vline(aes(xintercept = mean(Customer_Age)), color = "red", linetype = "dashed", size = 1) +annotate("text", x = mean(BK$Customer_Age) + 10, y = 7000,
label = paste("Tuổi TB:", round(mean(BK$Customer_Age), 1)), color = "red") +
geom_density(aes(y = after_stat(count) * 5), color = "darkgreen", size = 1) + # Thêm đường mật độ
labs(title = "Phân bổ độ tuổi của khách hàng",
x = "Tuổi", y = "Số lượng khách hàng") +
theme_classic()Giải thích code:
Ý nghĩa: Điểm đặc biệt và nổi bật nhất của biểu đồ này không phải là sự tập trung vào một nhóm tuổi nào, mà là sự phân bổ gần như bằng nhau của khách hàng trên tất cả các nhóm tuổi từ thanh niên đến khi về hưu. Các cột trong khoảng từ 25 đến 65 tuổi có chiều cao gần như không chênh lệch.
revenue_age_gender <- BK1 %>%
group_by(Age_Group, Customer_Gender) %>%
summarise(Total_Revenue = sum(Revenue), .groups = 'drop')
ggplot(revenue_age_gender, aes(x = Age_Group, y = Total_Revenue, fill = Customer_Gender)) +
geom_bar(stat = "identity", position = "stack") +
facet_wrap(~Customer_Gender) + # Chia thành các biểu đồ nhỏ theo giới tính
labs(title = "Doanh thu theo nhóm Tuổi và Giới Tính",
x = "Nhóm Tuổi", y = "Tổng Doanh Thu", fill = "Giới Tính") +
scale_y_continuous(labels = scales::dollar) +
scale_fill_brewer(palette = "Set2") +
theme_minimal()Giải thích code:
Ý nghĩa: Nhóm tuổi “Middle-Aged” (36-55) đóng góp nhiều doanh thu nhất. Trong hầu hết các nhóm tuổi, tỷ lệ doanh thu từ nam và nữ khá tương đồng.
ggplot(BK1 %>% sample_n(5000), aes(x = Customer_Age, y = Revenue)) + # Lấy mẫu 5000 điểm để vẽ cho nhẹ
geom_point(alpha = 0.3, aes(color = Store_Location)) +
geom_smooth(method = "lm", color = "red", se = FALSE, linetype = "dashed") +
labs(title = "Mối quan hệ giữa tuổi khách hàng và giá trị đơn hàng",
subtitle = "Mỗi điểm là một giao dịch",
x = "Tuổi khách hàng", y = "Doanh thu mỗi giao dịch", color = "Cửa hàng") +
scale_y_continuous(labels = scales::dollar) +
theme_gray()Giải thích code:
Ý nghĩa: Các điểm dữ liệu phân bố khá ngẫu nhiên và đường xu hướng (màu đỏ) gần như nằm ngang, khẳng định lại kết quả phân tích tương quan ở Chương 3: không có mối liên hệ rõ ràng giữa tuổi và giá trị đơn hàng.
ggplot(loyalty_distribution, aes(x = Percentage, y = reorder(Loyalty_Status, Percentage), fill = Loyalty_Status)) +
geom_col() + # Sử dụng geom_col để vẽ biểu đồ thanh
geom_text(aes(label = paste0(round(Percentage, 1), "%")),
hjust = -0.2, # Điều chỉnh vị trí nhãn để nằm bên phải thanh
size = 4, color = "black") +
labs(title = "Phân bổ khách hàng theo hạng thân thiết",
x = "Tỷ lệ (%)",
y = "Hạng thân thiết") +
scale_x_continuous(limits = c(0, max(loyalty_distribution$Percentage) * 1.2)) + # Mở rộng giới hạn trục X
theme_minimal() +
guides(fill = "none") # Bỏ chú giải màu vì màu được lặp lại ở trục YGiải thích code:
Nhận xét:
Hạng Platinum Chiếm Đa Số Tuyệt Đối (66.7%): Hạng Platinum chiếm tỷ lệ lớn nhất, với 66.7% tổng số khách hàng. Điều này cho thấy phần lớn cơ sở khách hàng nằm ở phân khúc cao cấp nhất hoặc có giá trị nhất (tùy thuộc vào tiêu chí xếp hạng).
Hạng Gold Đáng Kể (31.7%): Hạng Gold chiếm tỷ lệ lớn thứ hai, với 31.7%. Kết hợp với Platinum, hai hạng này chiếm hơn 98% cơ sở khách hàng.
Các Hạng Thấp Chiếm Tỷ Lệ Rất Nhỏ (Dưới 2%):
Hạng Silver chỉ chiếm 1.5%.
Hạng Bronze không có khách hàng nào (0%).
revenue_by_loyalty <- BK1 %>%
group_by(Loyalty_Status) %>%
summarise(Total_Revenue = sum(Revenue)) %>%
ungroup()
ggplot(revenue_by_loyalty, aes(x = Loyalty_Status, y = Total_Revenue, fill = Loyalty_Status)) +
geom_col() +
labs(title = "Doanh thu theo Hạng Khách hàng",
x = "Hạng khách hàng",
y = "Tổng doanh thu") +
theme_minimal() +
theme(legend.position = "none")Giải thích code:
Nhận xét:: Biểu đồ là bằng chứng cho thấy doanh nghiệp đang hoạt động hiệu quả với khách hàng hiện tại có giá trị cao, nhưng cần phải cân nhắc chiến lược để giảm thiểu rủi ro tập trung và mở rộng cơ sở doanh thu từ các phân khúc cấp thấp hơn.
ggplot(avg_revenue_by_loyalty, aes(x = reorder(Loyalty_Status, Average_Revenue_Per_Sale), y = Average_Revenue_Per_Sale, fill = Loyalty_Status)) +
geom_col() +
geom_text(aes(label = dollar(Average_Revenue_Per_Sale, prefix = "", suffix = " USD")), vjust = 0.5, size = 4) +
labs(title = "Giá trị đơn hàng trung bình theo hạng khách hàng",
x = "Hạng thân thiết",
y = "Giá trị đơn hàng trung bình") +
scale_y_continuous(labels = scales::dollar_format(prefix = "", suffix = " USD")) +
theme_minimal() +
guides(fill = "none")Giải thích code:
Nhận xét: Giá trị đơn hàng trung bình của hạng khách hàng Bronze là cao nhất vượt trội hơn hẳn so vớ i Gold, Platinum, Silver có giá trị khá ngang nhau.
revenue_heatmap_data <- BK1 %>%
group_by(Weekday, Month) %>%
summarise(Total_Revenue = sum(Revenue), .groups = 'drop')
ggplot(revenue_heatmap_data, aes(x = Weekday, y = Month, fill = Total_Revenue)) +
geom_tile(color = "white", linewidth = 0.5) +
geom_text(aes(label = number(Total_Revenue / 1000, accuracy = 0.01)),
size = 2.8, color = "black", fontface = "bold") +
scale_fill_gradient(
low = "#FFF5CC", high = "#7A0000",
labels = label_dollar(scale = 1, suffix = "")
) +
labs(
title = "Heatmap doanh thu theo ngày và tháng",
subtitle = "Doanh thu được tính bằng nghìn đô la Mỹ (K)",
x = "Ngày trong tuần",
y = "Tháng",
fill = "Doanh thu"
) +
theme_minimal(base_size = 11) +
theme(
plot.title = element_text(face = "bold", size = 13, hjust = 0.5),
plot.subtitle = element_text(size = 10, hjust = 0.5),
axis.text.x = element_text(angle = 45, hjust = 1),
axis.text.y = element_text(size = 9),
legend.title = element_text(face = "bold"),
legend.text = element_text(size = 8),
panel.grid = element_blank(),
plot.margin = margin(10, 10, 10, 10)
)Giải thích code:
Ý nghĩa: Các ô màu càng đậm (đỏ) thể hiện doanh thu càng cao. Biểu đồ này giúp nhanh chóng xác định rằng cuối tuần và các tháng mùa hè thường là thời điểm kinh doanh tốt nhất.
quantity_quarter_store <- BK1 %>%
mutate(Quarter = paste0("Q", quarter(Date), "-", year(Date))) %>%
# Tạo biến Quý (VD: Q1-2023)
group_by(Quarter, Store_Location) %>%
summarise(Total_Quantity = sum(Quantity), .groups = "drop") %>%
arrange(Quarter)
ggplot(quantity_quarter_store, aes(x = Quarter, y = Total_Quantity, color = Store_Location, group = Store_Location)) +
geom_line(size = 1.2) +
geom_point(size = 2) +
labs(title = "Xu hướng số lượng xe bán theo Quý và Địa điểm cửa Hàng",
x = "Quý",
y = "Tổng số lượng xe",
color = "Địa điểm cửa hàng") +
theme_minimal() +
theme(axis.text.x = element_text(angle = 45, hjust = 1))Giải thích code:
Ý nghĩa: Số lượng xe bán ra dao động theo từng quý, thể hiện sự biến động nhu cầu của thị trường theo thời gian. Các cửa hàng như New York, Chicago và Los Angeles thường có mức bán ra cao và ổn định hơn so với các cửa hàng còn lại. Một số quý xuất hiện mức giảm mạnh (điểm đáy), cho thấy khả năng chịu ảnh hưởng từ yếu tố mùa vụ hoặc điều kiện kinh tế. Xu hướng tổng thể cho thấy không có chênh lệch quá lớn giữa các cửa hàng, nhưng một số cửa hàng duy trì hiệu suất tốt hơn liên tục, phản ánh ưu thế vị trí, thị hiếu hoặc chiến lược bán hàng hiệu quả.
top5_salesperson_trans <- BK1 %>%
count(Salesperson_ID, name = "Transaction_Count", sort = TRUE) %>%
slice_head(n = 5)
ggplot(top5_salesperson_trans, aes(x = reorder(Salesperson_ID, Transaction_Count), y = Transaction_Count, fill = Salesperson_ID)) +
geom_bar(stat = "identity", show.legend = FALSE, width = 0.7) +
coord_flip() +
geom_text(aes(label = Transaction_Count), hjust = -0.2) +
labs(title = "Top 5 nhân viên theo số lượng giao dịch",
subtitle = "Dựa trên tổng số hóa đơn đã xử lý",
x = "Nhân viên", y = "Số lượng giao dịch") +
theme_minimal()Giải thích code:
Ý nghĩa: Biểu đồ xác định nhân viên có mã số 422 là người xử lý nhiều giao dịch nhất (143). Tuy nhiên, điều quan trọng hơn là hiệu suất của top 5 nhân viên này cực kỳ đồng đều và sít sao, chỉ chênh lệch nhau tối đa 3 giao dịch.
bubble_data <- BK1 %>%
group_by(Bike_Model) %>%
summarise(Total_Revenue = sum(Revenue),Total_Quantity = sum(Quantity),
Avg_Price = mean(Price), .groups = 'drop')
ggplot(bubble_data, aes(x = Total_Quantity, y = Avg_Price, size = Total_Revenue, color = Bike_Model)) +
geom_point(alpha = 0.7) +
scale_size(range = c(3, 15), name = "Tổng doanh thu (USD)") +
scale_color_viridis_d(guide = "none") +
geom_text(aes(label = Bike_Model), size = 3.5, vjust = -2.5, fontface = "bold") +
labs(title = "Phân tích tổng quan các mẫu xe",
subtitle = "Doanh thu (kích thước), Số lượng (trục x), Giá TB (trục y)",
x = "Tổng số lượng bán ra", y = "Giá bán trung bình (USD)") +
scale_y_continuous(labels = scales::dollar) +
theme_minimal()Giải thích code:
Nhận xét “Electric Bolt” nằm ở góc trên bên phải với bong bóng lớn nhất: giá trung bình cao, số lượng bán nhiều, doanh thu vượt trội.
ggplot(revenue_by_model, aes(
area = Total_Revenue,
fill = Bike_Model, label = Bike_Model)) +
geom_treemap() +
geom_treemap_text(colour = "white", place = "centre", size = 14, grow = TRUE) +
labs(title = "Tỷ trọng doanh thu theo mẫu xe", fill = "Mẫu xe")Giải thích code:
(1): Khởi tạo biểu đồ sử dụng bảng dữ liệu đã tóm tắt tổng doanh thu theo từng mẫu xe.
(2): Diện tích của mỗi hình chữ nhật trên biểu đồ sẽ tỷ lệ thuận với tổng doanh thu của mẫu xe đó. Doanh thu càng cao, hình chữ nhật càng lớn.
(4): Có nhiệm vụ vẽ các hình chữ nhật của biểu đồ Treemap dựa trên các ánh xạ đã thiết lập ở trên.
Nhận xét: Không có bất kỳ mẫu xe nào có diện tích vượt trội, cho thấy không có một sản phẩm nào đang thống trị hay gánh doanh thu cho toàn bộ công ty.
Giảm thiểu rủi ro: Công ty không bị phụ thuộc vào sự thành công của bất kỳ một sản phẩm riêng lẻ nào. Nếu một mẫu xe gặp vấn đề, các mẫu xe khác vẫn có thể duy trì hoạt động kinh doanh ổn định.
Tiếp cận thị trường rộng: Sự thành công đồng đều cho thấy công ty đã đáp ứng tốt nhu cầu của nhiều phân khúc khách hàng khác nhau.
sankey_data <- BK1 %>%
group_by(Customer_Gender, Bike_Model) %>%
summarise(Count = n(), .groups = 'drop')
ggplot(as.data.frame(sankey_data),
aes(y = Count, axis1 = Customer_Gender, axis2 = Bike_Model)) +
geom_alluvium(aes(fill = Bike_Model), width = 1/12, alpha = 0.7) +
geom_stratum(width = 1/6, fill = "grey", color = "white") +
geom_label(stat = "stratum", aes(label = after_stat(stratum))) +
scale_x_discrete(limits = c("Giới tính", "Mẫu xe"), expand = c(.05, .05)) +
labs(title = "Luồng lựa chọn mẫu xe theo giới tính",subtitle = "Độ rộng của dải màu thể hiện số lượng khách hàng") +
theme_void() +
theme(legend.position = "bottom")Giải thích code:
Ý nghĩa: Điểm đáng chú ý nhất từ biểu đồ này là không có sự khác biệt rõ rệt trong sở thích chọn xe giữa khách hàng nam và nữ. Nhìn chung, tất cả các mẫu xe đều được cả hai giới lựa chọn với tỷ lệ rất cân bằng. Điều này cho thấy các mẫu xe có sức hấp dẫn rộng rãi và không cần các chiến lược marketing nhắm riêng vào từng giới tính.
Công ty Cổ phần Hoàng Anh Gia Lai (HAG) là một trong những doanh nghiệp có lịch sử hoạt động đầy biến động và thu hút sự quan tâm lớn trên thị trường chứng khoán Việt Nam. Giai đoạn 2015-2024 đánh dấu một thập kỷ mang tính bước ngoặt, khi HAG thực hiện quá trình tái cấu trúc sâu sắc, chuyển đổi mô hình kinh doanh từ bất động sản và cao su sang tập trung vào nông nghiệp công nghệ cao, với các sản phẩm chủ lực là cây ăn trái (chuối) và chăn nuôi (heo).
Quá trình chuyển đổi này đặt ra một câu hỏi nghiên cứu cốt lõi: Liệu chiến lược tái cấu trúc và tập trung vào nông nghiệp có thực sự mang lại hiệu quả tài chính bền vững cho HAG hay không?
Để trả lời câu hỏi này, chương này sẽ thực hiện một phân tích định lượng chi tiết dựa trên dữ liệu tài chính của công ty. Trọng tâm của phân tích sẽ là Báo cáo Kết quả Hoạt động Kinh doanh (KQKD), vì đây là báo cáo phản ánh rõ nét nhất khả năng tạo ra doanh thu, quản lý chi phí và hiệu quả sinh lời từ hoạt động cốt lõi của doanh nghiệp. Thông qua việc phân tích sâu các chỉ tiêu trên Báo cáo KQKD trong suốt 10 năm, nghiên cứu này sẽ làm rõ các xu hướng, xác định các động lực tăng trưởng và đánh giá những thách thức mà HAG đã và đang đối mặt.
Để thực hiện phân tích, chúng tôi sử dụng bộ dữ liệu tài chính của HAG được tổng hợp cho giai đoạn 10 năm, từ 2015 đến 2024.
Nguồn gốc và Phạm vi: Dữ liệu được thu thập từ các báo cáo tài chính chính thức của CTCP Hoàng Anh Gia Lai, bao gồm số liệu hàng năm trong khoảng thời gian đã nêu.
Cấu trúc dữ liệu gốc: Dữ liệu ban đầu được tổ chức dưới dạng bảng (dạng rộng - wide format). Trong đó, mỗi hàng đại diện cho một năm tài chính và mỗi cột là một chỉ tiêu tài chính cụ thể. Tệp dữ liệu gốc (BCTC) là một bộ dữ liệu lớn, tích hợp các chỉ tiêu từ ba báo cáo tài chính chính:
Bảng Cân đối Kế toán
Báo cáo Kết quả Hoạt động Kinh doanh
Báo cáo Lưu chuyển Tiền tệ
Trọng tâm phân tích: Để đáp ứng mục tiêu nghiên cứu đã đề ra trong phần giới thiệu, bài phân tích này sẽ thực hiện thao tác rút trích dữ liệu (data extraction). Cụ thể, chúng tôi sẽ tạo ra một tập con (subset) chỉ chứa các biến thuộc Báo cáo Kết quả Kinh doanh để tiến hành phân tích chuyên sâu.
library(dplyr)
library(readxl)
BCTC <- read_excel("D:/Thúy Hiền/Ngôn ngữ lập trình trong phân tích dữ liệu/BCTC.xlsx")read_excel(…) Là hàm chính từ gói readxl, dùng để đọc dữ liệu từ tệp Excel.
Giải thích code:
Trong bối cảnh HAG trải qua quá trình tái cấu trúc và chuyển đổi mô hình kinh doanh (từ bất động sản/cao su sang nông nghiệp), việc đánh giá hiệu suất hoạt động cốt lõi là tối quan trọng. Bảng KQKD là báo cáo duy nhất cung cấp bức tranh chi tiết về các nguồn thu nhập và chi phí phát sinh, cho phép nhà nghiên cứu:
Phân tích Chiều sâu Lợi nhuận: Theo dõi lợi nhuận từ các hoạt động chính (nông nghiệp) qua các tầng (Lợi nhuận gộp, Lợi nhuận từ hoạt động kinh doanh).
Đánh giá Sức khỏe Tài chính (Cơ bản): Đánh giá khả năng tạo ra dòng tiền từ hoạt động kinh doanh (trước khi xem xét các hoạt động đầu tư/tài chính).
Xác định Xu hướng (Trend Analysis): So sánh các yếu tố doanh thu và chi phí qua các năm để làm rõ tính ổn định và động lực tăng trưởng mới của HAG.
ten_bien_kqkd <- c(
"x1_doanh_thu_ban_hang_va_cung_cap_dich_vu",
"x2_cac_khoan_giam_tru_doanh_thu",
"x3_doanh_thu_thuan_ve_ban_hang_va_cung_cap_dich_vu",
"x4_gia_von_hang_ban_va_dich_vu_cung_cap",
"x5_loi_nhuan_gop_ve_ban_hang_va_cung_cap_dich_vu",
"x6_doanh_thu_hoat_dong_tai_chinh",
"x7_chi_phi_tai_chinh",
"trong_do_chi_phi_lai_vay",
"x8_phan_lai_trong_cong_ty_lien_ket",
"x9_chi_phi_ban_hang",
"x10_chi_phi_quan_ly_doanh_nghiep",
"x11_lo_thuan_tu_hoat_dong_kinh_doanh",
"x12_thu_nhap_khac",
"x13_chi_phi_khac",
"x14_lo_khac",
"x15_lo_ke_toan_truoc_thue",
"x16_chi_phi_thue_tndn_hien_hanh",
"x17_chi_phi_thu_nhap_thue_tndn_hoan_lai",
"x18_lo_sau_thue_tndn",
"x19_lo_loi_nhuan_sau_thue_cua_cong_ty_me",
"x20_lo_sau_thue_cua_co_dong_khong_kiem_soat",
"x21_lo_lai_co_ban_tren_co_phieu_vnd",
"x22_lo_lai_suy_giam_tren_co_phieu_vnd"
)
# 2. Tạo tập con chỉ gồm bảng kết quả kinh doanh
KQKD <- BCTC %>%
select(
nam, # Giữ lại cột Năm
any_of(ten_bien_kqkd) # Chọn các cột KQKD tồn tại
)## Số biến trong bộ dữ liệu là: 24
## Số quan sát trong bộ dữ liệu là: 10
## Tổng số giá trị bị thiếu (NA): 5
Tính giá trị trung vị (median) vào các giá trị bị thiếu.
KQKD_sach_na <- KQKD %>%
mutate(across(where(is.numeric),
~ ifelse(is.na(.), median(., na.rm = TRUE), .)))## Tổng số giá trị bị thiếu (NA): 0
Nhận xét: Sau khi xử lý giá trị bị thiếu ta kiểm tra lại và thấy dữ liệu đã sạch.
KQKD_sach_na <- KQKD_sach_na %>%
rename(
doanh_thu = x1_doanh_thu_ban_hang_va_cung_cap_dich_vu,
giam_tru_dt = x2_cac_khoan_giam_tru_doanh_thu,
doanh_thu_thuan = x3_doanh_thu_thuan_ve_ban_hang_va_cung_cap_dich_vu,
gia_von = x4_gia_von_hang_ban_va_dich_vu_cung_cap,
loi_nhuan_gop = x5_loi_nhuan_gop_ve_ban_hang_va_cung_cap_dich_vu,
doanh_thu_tc = x6_doanh_thu_hoat_dong_tai_chinh,
chi_phi_tc = x7_chi_phi_tai_chinh,
chi_phi_lai_vay = trong_do_chi_phi_lai_vay,
lai_trong_lien_ket = x8_phan_lai_trong_cong_ty_lien_ket,
chi_phi_bh = x9_chi_phi_ban_hang,
chi_phi_ql = x10_chi_phi_quan_ly_doanh_nghiep,
loi_nhuan_hd = x11_lo_thuan_tu_hoat_dong_kinh_doanh,
thu_nhap_khac = x12_thu_nhap_khac,
chi_phi_khac = x13_chi_phi_khac,
lo_khac = x14_lo_khac,
lo_truoc_thue = x15_lo_ke_toan_truoc_thue,
cp_thue_hien_hanh = x16_chi_phi_thue_tndn_hien_hanh,
cp_thue_hoan_lai = x17_chi_phi_thu_nhap_thue_tndn_hoan_lai,
lo_sau_thue = x18_lo_sau_thue_tndn,
lnst_cong_ty_me = x19_lo_loi_nhuan_sau_thue_cua_cong_ty_me,
lo_cd_khong_kiem_soat = x20_lo_sau_thue_cua_co_dong_khong_kiem_soat,
lai_co_ban = x21_lo_lai_co_ban_tren_co_phieu_vnd, # EPS: Earnings Per Share
lai_suy_giam = x22_lo_lai_suy_giam_tren_co_phieu_vnd
)
names(KQKD_sach_na)## [1] "nam" "doanh_thu" "giam_tru_dt"
## [4] "doanh_thu_thuan" "gia_von" "loi_nhuan_gop"
## [7] "doanh_thu_tc" "chi_phi_tc" "chi_phi_lai_vay"
## [10] "lai_trong_lien_ket" "chi_phi_bh" "chi_phi_ql"
## [13] "loi_nhuan_hd" "thu_nhap_khac" "chi_phi_khac"
## [16] "lo_khac" "lo_truoc_thue" "cp_thue_hien_hanh"
## [19] "cp_thue_hoan_lai" "lo_sau_thue" "lnst_cong_ty_me"
## [22] "lo_cd_khong_kiem_soat" "lai_co_ban" "lai_suy_giam"
Nhận xét: Vì tên các chỉ tiêu trong báo cáo tài chính quá dài nên đã đặt tên lại cho các chỉ tiêu để dễ dàng phân tích.
KQKD1 <- KQKD_sach_na%>%
mutate(
Giai_doan = ifelse(
nam <= 2019,
"Tái cấu trúc", # Đổi tên nhóm 2015-2019
"Phục hồi" # Đổi tên nhóm 2020-2024
)
)Nhận xét:
Từ năm 2015-2019 là giai đoạn tái cấu trúc.
Từ năm 2020-2024 là giai đoạn phục hồi.
KQKD1 <- KQKD1 %>%
mutate(Tinh_trang_KD = ifelse(lnst_cong_ty_me > 0, "Có lãi", "Lỗ"))
KQKD1 %>%
select(nam, lnst_cong_ty_me, Tinh_trang_KD)Tình trạng kinh doanh của HAG, được đo lường bằng Lợi nhuận sau thuế của công ty mẹ, phản ánh quá trình tái cấu trúc đầy khó khăn nhưng cuối cùng đã thành công của doanh nghiệp.Từ 2015-2020 có 2 năm Lỗ đó là năm 2016 và 2020 còn lại đều Có lãi. Giai đoạn 2015–2020 thể hiện sự bất ổn sâu sắc.
Công ty liên tục xen kẽ giữa trạng thái thua lỗ lớn (đặc biệt năm 2016 và 2020 với mức lỗ vượt 1,1 nghìn tỷ VND) và có lãi rất khiêm tốn. Tình trạng này khẳng định rằng mặc dù hoạt động cốt lõi có lúc tạo ra lợi nhuận gộp, nhưng toàn bộ kết quả đã bị Chi phí lãi vay khổng lồ hấp thụ, khiến lợi nhuận ròng bị xói mòn và xác nhận rủi ro tài chính cao là rào cản chính.
Tuy nhiên, từ năm 2021, HAG đã đạt được bước ngoặt quan trọng, chuyển sang trạng thái có lãi liên tục và tăng trưởng bền vững. LNST tăng trưởng mạnh mẽ, đạt đỉnh gần 1,7 nghìn tỷ VND vào năm 2023, cho thấy thành công trong việc tái cơ cấu nợ và chuyển đổi mô hình kinh doanh. Sự giảm áp lực lãi vay đã giải phóng lợi nhuận gộp từ mảng nông nghiệp cốt lõi, củng cố sức khỏe tài chính và chứng minh hiệu quả sinh lời bền vững hơn của công ty trong những năm gần đây.
thong_ke_mo_ta <- function(data, var_name){
x <- data[[var_name]]
result <- data.frame(
Bien = var_name,
Min = min(x, na.rm = TRUE),
Q1 = quantile(x, 0.25, na.rm = TRUE),
Median = median(x, na.rm = TRUE),
Mean = mean(x, na.rm = TRUE),
Q3 = quantile(x, 0.75, na.rm = TRUE),
Max = max(x, na.rm = TRUE),
Sd = sd(x, na.rm = TRUE),
Var = var(x, na.rm = TRUE),
Skewness = skewness(x, na.rm = TRUE),
Kurtosis = kurtosis(x, na.rm = TRUE)
)
return(result)
}
cac_bien <- setdiff(names(KQKD_sach_na), "nam")
bang_thong_ke_day_du <- do.call(
rbind,
lapply(cac_bien, function(v) thong_ke_mo_ta(KQKD_sach_na, v))
)
# Bảng 1
bang_thong_ke_day_du %>%
select(Bien, Min, Q1, Median, Mean, Q3, Max) %>%
kbl(
caption = "Bảng thống kê mô tả (Phần 1)",
row.names = FALSE,
digits = 2,
align = "lcccccc"
) %>%
kable_styling(
latex_options = c("striped", "bordered", "hold_position"), # Tùy chọn cho PDF
full_width = FALSE,
position = "center"
)| Bien | Min | Q1 | Median | Mean | Q3 | Max |
|---|---|---|---|---|---|---|
| doanh_thu | 2091833174 | 3.602780e+09 | 5197982826 | 4778701701.4 | 6162994499 | 6492569736 |
| giam_tru_dt | -111481812 | -7.794384e+07 | -33280844 | -43640756.6 | -14086485 | -35528 |
| doanh_thu_thuan | 2075444024 | 3.592791e+09 | 5249491144 | 4760738870.9 | 6135097400 | 6442397199 |
| gia_von | -5430638742 | -4.282861e+09 | -3360410005 | -3505832053.9 | -2981560516 | -1590448139 |
| loi_nhuan_gop | 205730343 | 6.325128e+08 | 1233428249 | 1254906817.0 | 1823704991 | 2374705174 |
| doanh_thu_tc | 280428437 | 6.358340e+08 | 1000786308 | 1062120059.4 | 1375440301 | 2137143442 |
| chi_phi_tc | -1963934151 | -1.692079e+09 | -1483654365 | -1279198249.1 | -1118636680 | 215432853 |
| chi_phi_lai_vay | -1585315746 | -1.465539e+09 | -1166140953 | -1050382818.4 | -837852275 | 270599417 |
| lai_trong_lien_ket | -18433513 | -4.839361e+06 | 3434652 | 6752914.9 | 10974578 | 64840488 |
| chi_phi_bh | -396487002 | -2.954072e+08 | -222192512 | -231297493.0 | -150230945 | -111239060 |
| chi_phi_ql | -1851240106 | -6.988116e+08 | -425967966 | -386887348.5 | -157646627 | 1349894514 |
| loi_nhuan_hd | -2022124320 | -2.346216e+08 | 885249748 | 423709570.3 | 1178959094 | 1690412815 |
| thu_nhap_khac | 21546363 | 3.762621e+07 | 88909719 | 121902298.0 | 179523568 | 281127775 |
| chi_phi_khac | -1380140330 | -8.527156e+08 | -521404910 | -612196782.4 | -262012765 | -116111269 |
| lo_khac | -1337563204 | -7.785596e+08 | -364405886 | -490294484.4 | -215137882 | 102463888 |
| lo_truoc_thue | -2351460262 | -1.087367e+09 | 238921066 | -66584914.1 | 968669652 | 1792876703 |
| cp_thue_hien_hanh | -153548976 | -2.117953e+07 | -3545265 | -23375871.8 | -2439711 | -885768 |
| cp_thue_hoan_lai | -86187524 | -3.727448e+07 | -4342050 | 27897825.1 | 83741605 | 259098512 |
| lo_sau_thue | -2383339850 | -1.125529e+09 | 249606713 | -62062960.8 | 945681602 | 1781685785 |
| lnst_cong_ty_me | -1255661344 | 8.156770e+07 | 209773938 | 252282346.6 | 885660614 | 1663970953 |
| lo_cd_khong_kiem_soat | -2025322017 | -3.024177e+08 | -39747303 | -314345307.4 | 86685488 | 302019303 |
| lai_co_ban | -1439 | 9.175000e+01 | 226 | 248.7 | 901 | 1794 |
| lai_suy_giam | -1439 | 9.175000e+01 | 226 | 248.7 | 901 | 1794 |
# Bảng 2
bang_thong_ke_day_du %>%
select(Bien, Sd, Var, Skewness, Kurtosis) %>%
kbl(
caption = "Bảng thống kê mô tả (Phần 2)",
row.names = FALSE,
digits = 2,
align = "lcccc" # Căn lề
) %>%
kable_styling(
latex_options = c("striped", "bordered", "hold_position"), # Tùy chọn cho PDF
full_width = FALSE,
position = "center"
)| Bien | Sd | Var | Skewness | Kurtosis |
|---|---|---|---|---|
| doanh_thu | 1697429237.0 | 2.881266e+18 | -0.63 | 1.88 |
| giam_tru_dt | 39814324.8 | 1.585180e+15 | -0.53 | 1.85 |
| doanh_thu_thuan | 1707942693.2 | 2.917068e+18 | -0.66 | 1.90 |
| gia_von | 1269370382.3 | 1.611301e+18 | -0.03 | 2.04 |
| loi_nhuan_gop | 779254932.9 | 6.072383e+17 | -0.04 | 1.71 |
| doanh_thu_tc | 576082822.3 | 3.318714e+17 | 0.45 | 2.29 |
| chi_phi_tc | 646305167.5 | 4.177104e+17 | 1.28 | 3.85 |
| chi_phi_lai_vay | 559300683.9 | 3.128173e+17 | 1.29 | 4.14 |
| lai_trong_lien_ket | 22714342.4 | 5.159414e+14 | 1.72 | 5.53 |
| chi_phi_bh | 98499222.1 | 9.702097e+15 | -0.38 | 1.84 |
| chi_phi_ql | 825992043.9 | 6.822629e+17 | 0.42 | 3.70 |
| loi_nhuan_hd | 1127420460.2 | 1.271077e+18 | -1.03 | 3.12 |
| thu_nhap_khac | 97949046.0 | 9.594016e+15 | 0.57 | 1.83 |
| chi_phi_khac | 435485086.5 | 1.896473e+17 | -0.58 | 2.07 |
| lo_khac | 453758629.0 | 2.058969e+17 | -0.64 | 2.30 |
| lo_truoc_thue | 1385063737.3 | 1.918402e+18 | -0.47 | 1.92 |
| cp_thue_hien_hanh | 46943148.9 | 2.203659e+15 | -2.43 | 7.33 |
| cp_thue_hoan_lai | 102409828.1 | 1.048777e+16 | 1.14 | 3.55 |
| lo_sau_thue | 1384531230.6 | 1.916927e+18 | -0.49 | 1.95 |
| lnst_cong_ty_me | 922629589.0 | 8.512454e+17 | -0.32 | 2.35 |
| lo_cd_khong_kiem_soat | 719170399.5 | 5.172061e+17 | -1.59 | 4.27 |
| lai_co_ban | 1026.3 | 1.053289e+06 | -0.42 | 2.41 |
| lai_suy_giam | 1026.3 | 1.053289e+06 | -0.42 | 2.41 |
KQKD1 <- KQKD1 %>%
mutate(bien_loi_nhuan_gop = (loi_nhuan_gop / doanh_thu_thuan) * 100
)
KQKD1 %>%
select(nam, doanh_thu_thuan, loi_nhuan_gop, bien_loi_nhuan_gop) %>%
kable(
caption = "Bảng tóm tắt kết quả kinh doanh qua các năm",
col.names = c("Năm", "Doanh thu thuần", "Lợi nhuận gộp", "Biên lợi nhuận gộp (%)"),
digits = 2) | Năm | Doanh thu thuần | Lợi nhuận gộp | Biên lợi nhuận gộp (%) |
|---|---|---|---|
| 2015 | 6252446533 | 1854425962 | 29.66 |
| 2016 | 6439779268 | 1009140526 | 15.67 |
| 2017 | 4841225074 | 1731542077 | 35.77 |
| 2018 | 5388200400 | 2374705174 | 44.07 |
| 2019 | 2075444024 | 227784373 | 10.98 |
| 2020 | 3176645956 | 205730343 | 6.48 |
| 2021 | 2097418366 | 506970227 | 24.17 |
| 2022 | 5110781887 | 1173401018 | 22.96 |
| 2023 | 6442397199 | 1293455480 | 20.08 |
| 2024 | 5783050002 | 2171912990 | 37.56 |
Nhận xét: Sự biến động của Biên lợi nhuận gộp HAG là một thước đo rõ ràng về sự chuyển đổi từ một công ty đa ngành sang một công ty nông nghiệp tập trung. Mức thấp trong giai đoạn 2019-2020 cho thấy khó khăn ban đầu khi chuyển đổi mô hình và chịu áp lực chi phí. Tuy nhiên, việc GPM duy trì ổn định trên 20% từ năm 2021 và đạt mức cao 37.56% vào năm 2024 xác nhận rằng hiệu suất hoạt động cốt lõi của HAG đã được cải thiện đáng kể và công ty đang quản lý tốt hơn mối quan hệ giữa Giá vốn và Doanh thu.
KQKD1 <- KQKD1 %>%
mutate(
ty_le_lai_vay_tren_cp_tai_chinh = (chi_phi_lai_vay/ chi_phi_tc) * 100
)
KQKD1 %>%
select(nam, chi_phi_tc, chi_phi_lai_vay, ty_le_lai_vay_tren_cp_tai_chinh) %>%
kable(
caption = "Bảng tỷ lệ lãi vay",
col.names = c("Năm", "Chi phí tài chính", "Chi phí lãi vay", "Tỷ lệ lãi vay trên chi phí tài chính"),
digits = 2)| Năm | Chi phí tài chính | Chi phí lãi vay | Tỷ lệ lãi vay trên chi phí tài chính |
|---|---|---|---|
| 2015 | -1203667607 | -1078711240 | 89.62 |
| 2016 | -1674519826 | -1579381993 | 94.32 |
| 2017 | -1697932438 | -1585315746 | 93.37 |
| 2018 | -1721684164 | -1532928450 | 89.04 |
| 2019 | -1963934151 | -1263369664 | 64.33 |
| 2020 | -1318161483 | -1253570666 | 95.10 |
| 2021 | -1090293038 | -971878185 | 89.14 |
| 2022 | -1649147246 | -793176972 | 48.10 |
| 2023 | 215432853 | 270599417 | 125.61 |
| 2024 | -688075391 | -716094685 | 104.07 |
Kết luận: Rủi ro tài chính của HAG đang có dấu hiệu giảm nhờ tái cơ cấu nợ. Tuy nhiên, do quy mô nợ cũ còn lớn, Chi phí lãi vay vẫn là một yếu tố cần theo dõi sát, bởi nó tiếp tục là rào cản chính hạn chế khả năng sinh lời bền vững và ổn định của công ty.
KQKD1 <- KQKD1 %>%
mutate(
Tang_truong_DT = (doanh_thu_thuan-lag(doanh_thu_thuan))/lag(doanh_thu_thuan) * 100
)
KQKD1 %>%
select(nam, doanh_thu_thuan, Tang_truong_DT) %>%
kable(
caption = "Bảng tốc độ tăng trưởng doanh thu theo năm",
col.names = c("Năm", "Doanh thu thuần", "Tăng trưởng doanh thu"),
digits = 2)| Năm | Doanh thu thuần | Tăng trưởng doanh thu |
|---|---|---|
| 2015 | 6252446533 | NA |
| 2016 | 6439779268 | 3.00 |
| 2017 | 4841225074 | -24.82 |
| 2018 | 5388200400 | 11.30 |
| 2019 | 2075444024 | -61.48 |
| 2020 | 3176645956 | 53.06 |
| 2021 | 2097418366 | -33.97 |
| 2022 | 5110781887 | 143.67 |
| 2023 | 6442397199 | 26.06 |
| 2024 | 5783050002 | -10.23 |
Kết luận: Kết quả phân tích tốc độ tăng trưởng doanh thu của HAG cho thấy xu hướng biến động mạnh. Giai đoạn 2016–2021, doanh thu ghi nhận sự suy giảm đáng kể, đặc biệt năm 2017 và 2019, khi doanh thu giảm lần lượt khoảng 25% và hơn 61%. Nguyên nhân chủ yếu đến từ việc HAG thu hẹp mảng bất động sản và cao su, đồng thời chịu áp lực lớn từ gánh nặng nợ vay và xử lý tài sản dở dang. Năm 2020 đánh dấu sự phục hồi, với doanh thu tăng trên 53%, nhờ mảng nông nghiệp – đặc biệt là sản xuất và xuất khẩu chuối – bắt đầu tạo dòng tiền ổn định. Tuy nhiên, ngay năm sau, doanh thu lại giảm do ảnh hưởng từ đại dịch COVID-19 gây gián đoạn logistics và tiêu thụ nông sản.
KQKD1 <- KQKD1 %>%
mutate(
Tang_truong_gia_von=(gia_von-lag(gia_von))/lag(gia_von) * 100
)
KQKD1 %>%
select(nam, gia_von, Tang_truong_gia_von) %>%
kable(
caption = "Bảng tốc dộ tăng trưởng giá vốn hàng bán",
col.names = c("Năm", "Giá vốn", "Tăng trưởng giá bán"),
digits = 2)| Năm | Giá vốn | Tăng trưởng giá bán |
|---|---|---|
| 2015 | -4398020571 | NA |
| 2016 | -5430638742 | 23.48 |
| 2017 | -3109682997 | -42.74 |
| 2018 | -3013495226 | -3.09 |
| 2019 | -1847659651 | -38.69 |
| 2020 | -2970915613 | 60.79 |
| 2021 | -1590448139 | -46.47 |
| 2022 | -3937380869 | 147.56 |
| 2023 | -5148941719 | 30.77 |
| 2024 | -3611137012 | -29.87 |
Nhận xét: Giai đoạn 2017-2019 chứng kiến sự sụt giảm sâu liên tiếp của Giá vốn hàng bán, đặc biệt là mức giảm -42.74% năm 2017 và -38.69% năm 2019. Điều này cho thấy HAG đã trải qua một quá trình thu hẹp quy mô hoạt động hoặc tái cấu trúc mạnh mẽ để cắt giảm chi phí đầu vào. Tuy nhiên, năm 2022 ghi nhận mức tăng trưởng Giá vốn hàng bán kỷ lục 147.56%, mức cao nhất trong toàn bộ giai đoạn, tiếp nối là mức tăng đáng kể 30.77% vào năm 2023. Sự tăng trưởng đột biến này có thể là hệ quả trực tiếp của việc mở rộng hoạt động sản xuất kinh doanh trọng điểm trở lại, đặc biệt là trong các mảng nông nghiệp và chăn nuôi, hoặc do áp lực tăng giá nguyên vật liệu trên thị trường. Sự biến động dữ dội này – chuyển từ giảm sâu sang tăng kỷ lục rồi lại giảm mạnh -29.87% vào năm 2024 – nhấn mạnh tính bất ổn cao trong quản lý chi phí và quy mô sản xuất của HAG, đồng thời cho thấy công ty luôn trong trạng thái tái cơ cấu và điều chỉnh chiến lược kinh doanh theo từng giai đoạn.
KQKD_06 <- KQKD1 %>%
mutate(val = abs(Doanh_thu_TC_rong),
ty = val / sum(val, na.rm=TRUE))
ggplot(KQKD_06, aes(area = ty, fill = Doanh_thu_TC_rong > 0, label = paste(nam, "\n", round(ty*100,1),"%"))) +
geom_treemap() +
geom_treemap_text(place = "centre", grow = TRUE, reflow = TRUE) +
scale_fill_manual(values=c("TRUE"="green","FALSE"="tomato"), guide=FALSE) +
labs(title="Tỷ trọng đóng góp của doanh thu theo năm") +
theme_void()Giải thích code:
Nhận xét: Đóng góp của “Tài chính ròng” rất tập trung vào chỉ một vài năm đột biến (đặc biệt là 2022) và biến động cao theo thời gian.
vars_corr <- KQKD1 %>%
select(doanh_thu_thuan, loi_nhuan_gop, Loi_nhuan_HDKD, lnst_cong_ty_me,
Doanh_thu_TC_rong) %>%
na.omit()
corr_mat <- cor(vars_corr)
corrplot::corrplot(
corr_mat,
method = "color",
addCoef.col = "black",
tl.col = "black",
tl.srt = 45,
number.cex = 0.7, # Giảm cỡ chữ của các con số trong ma trận
tl.cex = 0.8, # Giảm cỡ chữ của tên các biến
number.digits = 2,
title = "\nMa trận tương quan các chỉ tiêu chính",
mar = c(0, 0, 1, 0) # Tăng lề trên và lề phải
)Giải thích code:
(10),(11) tl.col = “black”, tl.srt = 45: Đặt màu nhãn trục là đen và xoay nhãn 45 độ để tránh chồng lấn.
Nhận xét: Ma trận tương quan cho thấy hoạt động kinh doanh cốt lõi của công ty vận hành một cách hợp lý, thể hiện qua mối tương quan thuận mạnh mẽ giữa doanh_thu_thuan và loi_nhuan_gop (0.72), và giữa loi_nhuan_gop và Loi_nhuan_HDKD (0.62). Tuy nhiên, có một điểm bất thường và đáng lo ngại nhất là mối tương quan nghịch (-0.21) giữa Loi_nhuan_HDKD và LNST_cong_ty_me (Lợi nhuận sau thuế của công ty mẹ). Điều này cho thấy lợi nhuận kiếm được từ hoạt động kinh doanh chính không chuyển hóa thành lợi nhuận cuối cùng cho cổ đông, mà thậm chí còn đi ngược chiều. Ngoài ra, Doanh_thu_TC_rong (Doanh thu tài chính ròng) gần như không liên quan hoặc hơi ngược chiều với hoạt động kinh doanh chính (-0.19), cho thấy sự tách biệt của hai mảng này.
data_long <- KQKD1 %>%
select(nam, doanh_thu_thuan, lnst_cong_ty_me, loi_nhuan_gop, Loi_nhuan_HDKD) %>%
pivot_longer(
cols = -nam,
names_to = "Chi_tieu",
values_to = "Gia_tri"
) %>%
na.omit()
ggplot(data_long, aes(x = factor(nam), y = Gia_tri, group = Chi_tieu, color = Chi_tieu)) +
geom_line(linewidth = 1.2) + # Tăng độ dày đường kẻ
geom_point(size = 3) + # Thêm điểm đánh dấu cho mỗi năm
scale_y_continuous(
labels = label_number(
scale = 1e-9,
suffix = " Tỷ"
)
) +
scale_color_brewer(palette = "Set1") + # Sử dụng bảng màu dễ phân biệt
labs(
title = "Diễn biến Doanh thu và Lợi nhuận qua các năm",
subtitle = "Sự phân kỳ giữa Lợi nhuận HĐKD và Lợi nhuận Sau thuế",
x = "Năm",
y = "Giá trị (Tỷ VND)",
color = "Chỉ tiêu" # Đặt tên cho phần chú giải
) +
theme_minimal() +
theme(
plot.title = element_text(hjust = 0.5, face = "bold", size = 16),
plot.subtitle = element_text(hjust = 0.5, size = 12),
legend.position = "top",
axis.text.x = element_text(angle = 45, hjust = 1) # Xoay nhãn trục X nếu cần
)Giải thích code:
Nhận xét: Biểu đồ cho thấy Doanh thu thuần (đỏ) luôn ở mức cao (từ 2-6.5 tỷ tỷ), nhưng 3 đường lợi nhuận còn lại bị “ép” xuống mức rất thấp, cho thấy biên lợi nhuận mỏng. Đúng như tiêu đề phụ, có sự phân kỳ rõ rệt: Lợi nhuận HĐKD (tím) và Lợi nhuận gộp (xanh lá) luôn duy trì trên mức 0 (trừ 2022), nhưng Lợi nhuậnSau thuế (xanh dương) lại liên tục rơi xuống mức âm sâu (đặc biệt là 2016 và 2020), cho thấy gánh nặng chi phí (như tài chính) là rất lớn.
ggplot(KQKD1, aes(x = doanh_thu_thuan)) +
geom_histogram(
bins = 5, # Đặt số bin (thùng) để trực quan hóa phân phối
fill = "#1f78b4", # Màu xanh đại diện cho doanh thu
color = "white" # Viền trắng giữa các cột
) +
labs(
title = "Phân phối Doanh thu thuần của HAG (2015-2024)",
x = "Doanh thu thuần (VND)",
y = "Số năm (Tần suất)"
) +
scale_x_continuous(
labels = label_number(
big.mark = ".",
decimal.mark = ",",
scale = 1e-9, # Chuyển đơn vị sang Tỷ VND (chia cho 10^9)
suffix = " Tỷ"
)
) +
theme_minimal() +
theme(
plot.title = element_text(hjust = 0.5, face = "bold"),
axis.title.x = element_text(face = "bold"),
axis.title.y = element_text(face = "bold")
)Giải thích code:
(16),(17) Chia giá trị trục x cho \(10^9\) (1e-9) và thêm hậu tố ” Tỷ”, chuyển đơn vị hiển thị sang Tỷ VND.
(14),(15) 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 phân cách thập phân.
(20),(21) Áp dụng chủ đề tối giản và căn giữa tiêu đề chính (hjust = 0.5, face = “bold”), làm đậm nhãn trục.
Nhận xét:
Biểu đồ cho thấy sự phân cực mạnh mẽ trong kết quả kinh doanh của HAG. Công ty dường như hoạt động ở hai “trạng thái” riêng biệt trong giai đoạn này:
Trạng thái tốt: 7 năm kinh doanh với doanh thu rất cao (tập trung ở 5-6 Tỷ).
Trạng thái xấu: 3 năm kinh doanh với doanh thu thấp (tập trung ở 2-3 Tỷ).
Sự thiếu vắng hoàn toàn các giá trị ở mức trung bình (quanh 4 Tỷ) cho thấy HAG không có sự tăng trưởng hay suy giảm ổn định, mà là sự biến động đột ngột giữa hai trạng thái kinh doanh rất khác nhau.
plot_box_gpm <- ggplot(KQKD1, aes(y = Ty_suat_LNG)) +
geom_boxplot(fill = "lightblue", color = "darkblue", alpha = 0.7) +
geom_point(aes(x = 0, color = factor(nam)), size = 3, position = position_jitter(width = 0.1)) + # Jitter plot để thấy từng điểm năm
labs(
title = "Phân bổ và biến động của tỷ suất lợi nhuận gộp (GPM)",
subtitle = "Giá trị Trung vị, Tứ phân vị và các điểm ngoại lai",
x = "",
y = "Tỷ suất Lợi nhuận gộp (%)",
color = "Năm"
) +
scale_y_continuous(labels = function(x) paste0(x, "%")) +
theme_minimal() +
theme(plot.title = element_text(hjust = 0.5, face = "bold"),
axis.text.x = element_blank(), # Bỏ trục x vì đây là phân tích một chiều
axis.ticks.x = element_blank(),
legend.position = "right")
print(plot_box_gpm)Giải thích code:
Nhận xét: Tỷ suất lợi nhuận gộp của công ty rất không ổn định qua các năm. Các điểm dữ liệu phân tán rộng, trải dài từ mức rất thấp (khoảng 7% vào năm 2020) đến mức rất cao (hơn 40% vào năm 2018). Mức lợi nhuận gộp trung vị (đường kẻ đậm ở giữa hộp) là khoảng 23%. Điều này có nghĩa là một nửa số năm công ty đạt được GPM cao hơn mức này và một nửa còn lại thì thấp hơn. Năm 2018 có tỷ suất lợi nhuận gộp cao vượt trội so với các năm khác. Các năm 2019 và 2020 có tỷ suất lợi nhuận gộp rất thấp, nằm ở đáy của phân phối.
# Chuẩn bị dữ liệu: Tạo Ty_suat_LNG và Ty_suat_LNST, sau đó pivot
data_density_compare <- KQKD1 %>%
mutate(
Ty_suat_LNST = (lnst_cong_ty_me / doanh_thu_thuan) * 100
# Giả định Ty_suat_LNG đã tồn tại
) %>%
select(nam, Ty_suat_LNG, Ty_suat_LNST) %>%
pivot_longer(cols = starts_with("Ty_suat"), names_to = "Ty_suat", values_to = "Gia_tri") %>%
filter(!is.na(Gia_tri))
plot_density_overlay <- ggplot(data_density_compare, aes(x = Gia_tri, fill = Ty_suat)) +
geom_density(alpha = 0.5, adjust = 1.5) +
geom_vline(aes(xintercept = median(Gia_tri)), linetype = "dashed", color = "gray50") +
labs(
title = "So sánh phân bổ tỷ suất lợi nhuận gộp (GPM) và ròng (NPM)",
x = "Tỷ lệ (%)",
y = "Mật độ",
fill = "Loại Tỷ suất"
) +
scale_fill_manual(values = c("Ty_suat_LNG" = "#00BFC4", "Ty_suat_LNST" = "#F8766D"),
labels = c("Ty_suat_LNG" = "GPM (Lợi nhuận gộp)", "Ty_suat_LNST" = "NPM (Lợi nhuận ròng)")) +
theme_minimal() +
theme(plot.title = element_text(hjust = 0.5, face = "bold"),
legend.position = "top")
print(plot_density_overlay)
Giải thích code:
Nhận xét:
Phân bổ của Lợi nhuận gộp hoàn toàn nằm ở phía bên phải của mốc 0%, cho thấy công ty luôn tạo ra lợi nhuận từ hoạt động bán hàng cơ bản.
Phân bổ của Lợi nhuận ròng bị lệch hẳn sang bên trái so với Lợi nhuận gộp. Đáng chú ý là một phần đáng kể của đường cong này nằm trong vùng âm, cho thấy công ty thường xuyên bị lỗ ròng sau khi trừ đi các chi phí hoạt động, lãi vay và thuế.
Biểu đồ cho thấy một vấn đề nghiêm trọng về quản lý chi phí. Mặc dù công ty kinh doanh có lãi ở cấp độ gộp, nhưng các chi phí phát sinh sau đó quá lớn, dẫn đến kết quả lợi nhuận cuối cùng rất kém hiệu quả và không ổn định..
plot1 <- ggplot(KQKD1, aes(x = nam, y = doanh_thu_thuan)) +
geom_line(color = "blue", size = 1) +
geom_point(color = "darkblue", size = 3) +
geom_label_repel(aes(label = comma(doanh_thu_thuan, accuracy = 1)),
, color = "darkblue", size = 3.5,
box.padding = 0.5, point.padding = 0.5,
max.overlaps = Inf) +
labs(title = "1. Xu hướng Doanh thu thuần của HAG (2015-2024)",
x = "Năm", y = "Doanh thu thuần",
) +
theme_minimal() +
scale_y_continuous(labels = comma) +
scale_x_continuous(breaks = unique(KQKD1$nam)) +
theme(plot.title = element_text(hjust = 0.5, face = "bold"))
print(plot1)Giải thích code:
Nhận xét: Hoạt động kinh doanh của HAG mang tính chu kỳ rõ rệt, không có sự ổn định. Công ty có khả năng tạo ra doanh thu rất lớn nhưng cũng phải đối mặt với nguy cơ sụt giảm sâu. Giai đoạn 2021-2023 cho thấy khả năng phục hồi mạnh mẽ của công ty.
plot2 <- ggplot(KQKD1, aes(x = nam, y = lnst_cong_ty_me)) +
geom_col(aes(fill = Tinh_trang_KD), width = 0.7) +
geom_hline(yintercept = 0, linetype = "dashed", color = "red", size = 1) +
geom_text(aes(label = comma(lnst_cong_ty_me / 1e9, accuracy = 0.01),
vjust = ifelse(lnst_cong_ty_me >= 0, -0.5, 1.5)),
color = "black", size = 3.5) +
labs(title = "2. Biến động Lợi nhuận sau thuế của HAG (2015-2024)",
x = "Năm", y = "Lợi nhuận sau thuế",
fill = "Tình trạng") +
theme_light() +
scale_fill_manual(values = c("Có lãi" = "seagreen", "Lỗ" = "tomato")) +
scale_y_continuous(labels = comma) +
scale_x_continuous(breaks = KQKD1$nam) +
theme(plot.title = element_text(hjust = 0.5, face = "bold"))
print(plot2)Giải thích code:
Nhận xét: HAG có lịch sử kinh doanh rất sóng gió với 2 lần lỗ nặng (2016, 2020). Tuy nhiên, giai đoạn 4 năm gần nhất (2021-2024) cho thấy một sự thay đổi cơ bản trong hoạt động kinh doanh, mang lại lợi nhuận cao và ổn định hơn nhiều so với trước đây.
KQKD_sach_na_lng <- KQKD_sach_na %>%
select(nam, doanh_thu_thuan, loi_nhuan_gop) %>%
pivot_longer(-nam, names_to = "Chi_tieu", values_to = "Gia_tri")
plot3 <- ggplot(KQKD_sach_na_lng, aes(x = nam, y = Gia_tri, color = Chi_tieu)) +
geom_line(size = 1.2) +
geom_point(size = 3) +
geom_label_repel(aes(label = comma(Gia_tri / 1e9, accuracy = 0.01)), size = 3.5, max.overlaps = Inf) +
labs(title = "3. So sánh Doanh thu thuần và Lợi nhuận gộp",
x = "Năm", y = "Giá trị", color = "Chỉ tiêu") +
theme_bw() +
scale_color_manual(values = c("doanh_thu_thuan" = "navy", "loi_nhuan_gop" = "orange"),
labels = c("doanh_thu_thuan" = "Doanh thu thuần", "Loi_nhuan_gop" = "Lợi nhuận gộp")) +
scale_y_continuous(labels = comma) +
scale_x_continuous(breaks = KQKD_sach_na$nam) +
theme(plot.title = element_text(hjust = 0.5, face = "bold"),
legend.position = "bottom")
print(plot3)Giải thích code:
(5),(6) Tạo hai đường (line) và các điểm (point) riêng biệt thể hiện xu hướng của mỗi chỉ tiêu.
Nhận xét: Cả hai chỉ tiêu đều cho thấy sự biến động lớn, không ổn định qua các năm. Đặc biệt, giai đoạn 2018 - 2022 chứng kiến những sự sụt giảm và phục hồi mạnh mẽ. Doanh thu thuần đạt mức cao nhất vào khoảng năm 2015, 2017-2018 và giai đoạn 2022-2023. Giai đoạn giảm sâu nhất là vào năm 2019 và 2021. Lợi nhuận gộp cũng có xu hướng tăng giảm tương ứng với doanh thu. Tuy nhiên, có những năm tỷ suất lợi nhuận gộp (khoảng cách giữa hai đường) bị thu hẹp đáng kể, ví dụ như năm 2019 và 2021, cho thấy chi phí giá vốn hàng bán tăng cao so với doanh thu.
KQKD_sach_na_cp <- KQKD1 %>%
select(nam, chi_phi_tc, chi_phi_bh, chi_phi_ql) %>%
pivot_longer(-nam, names_to = "Loai_chi_phi", values_to = "So_tien") %>%
mutate(
So_tien = abs(So_tien), # Lấy giá trị tuyệt đối của Chi phí
Loai_chi_phi = case_when(
Loai_chi_phi == "chi_phi_tc" ~ "CP Tài chính",
Loai_chi_phi == "chi_phi_bh" ~ "CP Bán hàng",
Loai_chi_phi == "chi_phi_ql" ~ "CP Quản lý DN")
)
plot4 <- ggplot(KQKD_sach_na_cp, aes(x = nam, y = So_tien, fill = Loai_chi_phi)) +
geom_area(alpha = 0.9) +
labs(
title = "Cơ cấu chi phí hoạt động qua các năm (Giá trị tuyệt đối)",
x = "Năm",
y = "Chi phí (Tỷ đồng)",
fill = "Loại chi phí"
) +
theme_classic() +
scale_fill_brewer(palette = "Set1") + # Màu sắc tương phản hơn
scale_y_continuous(labels = scales::comma) +
scale_x_continuous(
breaks = seq(min(KQKD_sach_na_cp$nam), max(KQKD_sach_na_cp$nam), by = 2)
) +
facet_wrap(~Loai_chi_phi, scales = "free_y", nrow = 1) +
theme(
plot.title = element_text(hjust = 0.5, face = "bold"),
legend.position = "bottom",
axis.text.x = element_text(angle = 45, hjust = 1)
)
print(plot4)
Giải thích code:
scale_y_continuous(labels = scales::comma): Định dạng thêm dấu phẩy hàng nghìn trên trục Y.
facet_wrap(~Loai_chi_phi, scales = “free_y”, nrow = 1): Chia biểu đồ thành nhiều biểu đồ con (facets), mỗi biểu đồ con ứng với một giá trị trong cột Loai_chi_phi; nrow = 1: Sắp xếp tất cả các biểu đồ con này trên một hàng duy nhất.
Nhận xét:
Chi phí Tài chính và Chi phí Quản lý DN là hai gánh nặng chi phí chính, lớn hơn nhiều so với Chi phí Bán hàng.
Công ty giảm bớt các chi phí có quy mô lớn là Chi phí Tài chính và Chi phí Quản lý DN kể từ các đỉnh điểm của chúng vào 2018-2019. Ngược lại, công ty đang gia tăng đầu tư vào Chi phí Bán hàng, và chi phí này đang trong xu hướng tăng rõ rệt, đạt đỉnh vào năm 2023.
KQKD_ty_le_cp <- KQKD1 %>%
mutate(
# Sử dụng giá trị tuyệt đối cho Chi phí Tài chính
Ty_le_TC = abs(chi_phi_tc) / doanh_thu_thuan * 100,
Ty_le_BH = abs(chi_phi_bh) / doanh_thu_thuan * 100,
Ty_le_QL = abs(chi_phi_ql) / doanh_thu_thuan * 100
) %>%
select(nam, Ty_le_TC, Ty_le_BH, Ty_le_QL) %>%
pivot_longer(-nam, names_to = "Loai_chi_phi", values_to = "Ty_le")
plot_ty_le_cp <- ggplot(KQKD_ty_le_cp, aes(x = nam, y = Ty_le, color = Loai_chi_phi)) +
geom_line(size = 1.2) +
geom_point(size = 3) +
labs(title = "Tỷ lệ các loại chi phí hoạt động trên oanh thu thuần",
subtitle = "Hiệu quả kiểm soát chi phí so với quy mô doanh thu",
x = "Năm", y = "Tỷ lệ Chi phí / Doanh thu (%)", color = "Loại Chi phí") +
scale_y_continuous(labels = function(x) paste0(x, "%")) +
scale_x_continuous(breaks = unique(KQKD_ty_le_cp$nam)) +
scale_color_manual(values = c("Ty_le_TC" = "firebrick",
"Ty_le_BH" = "darkorange",
"Ty_le_QL" = "darkgreen"),
labels = c("Ty_le_TC" = "Tài chính",
"Ty_le_BH" = "Bán hàng",
"Ty_le_QL" = "Quản lý DN")) +
theme_bw() +
theme(plot.title = element_text(hjust = 0.5, face = "bold"),
legend.position = "top")
print(plot_ty_le_cp)Giải thích code:
(12),(13) Tạo các đường và điểm riêng biệt cho từng loại chi phí.
Nhận xét: Biểu đồ này giúp so sánh sự thay đổi của hiệu quả kiểm soát chi phí (Chi phí trên 1 đồng Doanh thu) giữa ba loại chi phí qua các năm.
plot_LNCP <- ggplot(KQKD1, aes(x = nam)) +
geom_col(aes(y = abs(chi_phi_tc), fill = "Chi phí Tài chính"), width = 0.6, alpha = 0.8) +
geom_line(aes(y = loi_nhuan_gop, color = "Lợi nhuận gộp"), size = 1.2) +
geom_point(aes(y = loi_nhuan_gop, color = "Lợi nhuận gộp"), size = 3) +
geom_hline(yintercept = 0) +
labs(title = "Lợi nhuận gộp có đủ gánh Chi phí tài chính",
subtitle = "Sự đảo chiều từ năm 2021 khi lợi nhuận gộp vượt xa Chi phí tài chính",
x = "Năm", y = "Số tiền (tỷ VND)") +
scale_y_continuous(labels = scales::comma) +
scale_x_continuous(breaks = KQKD_sach_na$nam) +
scale_fill_manual(name = "", values = c("Chi phí Tài chính" = "firebrick")) +
scale_color_manual(name = "", values = c("Lợi nhuận gộp" = "steelblue")) +
theme(legend.position = "top")
print(plot_LNCP)Giải thích code:
(3),(4) Vẽ Biểu đồ Đường cho Lợi nhuận gộp.
Nhận xét: Từ năm 2023 trở đi, công ty đã thoát khỏi gánh nặng chi phí tài chính vốn đã đè nặng lên họ trong nhiều năm. Lợi nhuận gộp giờ đây không chỉ đủ “gánh” chi phí tài chính mà còn vượt xa, tạo ra một khoảng đệm an toàn rất lớn. Điều này cho thấy sức khỏe tài chính đã trở nên lành mạnh hơn rất nhiều.
plot4LNG <- ggplot(KQKD1, aes(x = nam, y = Ty_suat_LNG)) +
geom_line(color = "darkgreen", linewidth = 1.2) +
geom_point(aes(color = Giai_doan), size = 4) +
ggrepel::geom_label_repel(aes(label = paste0(round(Ty_suat_LNG, 1), "%")), size = 4) +
scale_x_continuous(breaks = KQKD1$nam) +
scale_y_continuous(labels = function(x) paste0(x, "%")) +
labs(title = "Tỷ suất Lợi nhuận gộp (2015-2024)",
subtitle = "Hiệu quả hoạt động cốt lõi cải thiện rõ rệt trong giai đoạn Phục hồi",
x = "Năm", y = "Tỷ suất Lợi nhuận gộp (%)", color = "Giai đoạn")
print(plot4LNG)Giải thích code:
Nhận xét: Sau giai đoạn khủng hoảng chạm đáy (2019-2020) do gánh nặng chi phí tài chính khổng lồ và hiệu quả kinh doanh cốt lõi sụp đổ, công ty đã đảo chiều ngoạn mục. Giai đoạn 2023-2024 chứng kiến gánh nặng nợ được giải quyết triệt để, trong khi hiệu quả kinh doanh cốt lõi (biên lợi nhuận gộp) phục hồi mạnh mẽ, tăng vọt lên 37.6%. Doanh nghiệp đã vượt qua giai đoạn khó khăn nhất và tái lập một nền tảng tài chính lành mạnh để tăng trưởng.
plotLNST <- ggplot(KQKD1, aes(x = nam, y = Ty_suat_LNST)) +
geom_line(color = "darkgreen", linewidth = 1.2) +
geom_point(aes(color = Giai_doan), size = 4) +
ggrepel::geom_label_repel(aes(label = paste0(round(Ty_suat_LNST, 1), "%")), size = 4) +
scale_x_continuous(breaks = KQKD1$nam) +
scale_y_continuous(labels = function(x) paste0(x, "%")) +
labs(title = "Tỷ suất Lợi nhuận sau thuế (2015-2024)",
subtitle = "Hiệu quả hoạt động cốt lõi cải thiện rõ rệt trong giai đoạn phục hồi",
x = "Năm", y = "Tỷ suất Lợi nhuận sau thuế (%)", color = "Giai đoạn")
print(plotLNST)Nhận xét: Sau giai đoạn 2015-2019 bất ổn, công ty đã chạm đáy khủng hoảng vào năm 2020 với tỷ suất lợi nhuận ròng (NPM) âm 39.5%. Ngay lập tức, công ty đã phục hồi “thần tốc” theo hình chữ V, và đạt mức hiệu quả sinh lời cao nhất lịch sử vào 2022-2023 (đỉnh 25.8%).
data_ty_suat <- KQKD1 %>%
select(nam, Ty_suat_LNG, Ty_suat_LNST) %>%
pivot_longer(-nam, names_to = "Ty_suat", values_to = "Gia_tri")
plot_ty_suat <- ggplot(data_ty_suat, aes(x = nam, y = Gia_tri, color = Ty_suat)) +
geom_line(size = 1.2) +
geom_point(size = 3) +
geom_hline(yintercept = 0, linetype = "dashed", color = "black") +
labs(title = "So sánh tỷ suất Lợi nhuân gộp và Lợi nhuận sau thuế",
subtitle = "Khoảng cách giữa hai đường thể hiện mức độ ảnh hưởng của
chi phí hoạt động và tài chính",
x = "Năm", y = "Tỷ lệ (%)", color = "Loại tỷ suất") +
scale_y_continuous(labels = scales::percent_format(scale = 1)) +
scale_x_continuous(breaks = KQKD1$nam) +
scale_color_manual(values = c("Ty_suat_LNG" = "seagreen", "Ty_suat_LNST" = "firebrick"),
labels = c("Ty_suat_LNG" = "Tỷ suất Lợi nhuận gộp", "Ty_suat_LNST" = "Tỷ suất Lợi nhuận sau thuế")) +
theme(legend.position = "bottom")
print (plot_ty_suat)Giải thích code:
(5),(6) Tạo hai đường và các điểm riêng biệt cho GPM và NPM.geom_hline(yintercept = 0, …): Thêm đường tham chiếu ngang tại y=0 (hòa vốn).
Nhận xét: Giai đoạn 2015-2020, khoảng cách lớn giữa Tỷ suất lợi nhuận gộp (GPM-luôn dương) và Tỷ suất lợi nhuận ròng (NPM-liên tục âm) cho thấy chi phí hoạt động và tài chính đã “ăn mòn” toàn bộ thành quả kinh doanh. Từ 2021 khoảng cách thu hẹp đột ngột, NPM dương trở lại và thậm chí giao cắt GPM (2022-2023). Điều này khẳng định công ty đã kiểm soát chi phí thành công và hiệu quả sinh lời tổng thể được cải thiện vượt bậc.
XH_loi_nhuan <- ggplot(KQKD1, aes(x = nam)) +
geom_line(aes(y = Loi_nhuan_HDKD, color = "Lợi nhuận HĐKD"), size = 1.2) +
geom_line(aes(y = lnst_cong_ty_me, color = "Lợi nhuận Sau thuế"), size = 1.2, linetype = "dashed") +
geom_point(aes(y = Loi_nhuan_HDKD), color = "seagreen", size = 3) +
geom_point(aes(y = lnst_cong_ty_me), color = "firebrick", size = 3) +
geom_hline(yintercept = 0, linetype = "dotted") +
scale_y_continuous(labels = scales::comma) +
scale_x_continuous(breaks = KQKD1$nam) +
scale_color_manual(name = "Chỉ tiêu", values = c("Lợi nhuận HĐKD" = "seagreen", "Lợi nhuận sau thuế" = "firebrick")) +
theme(legend.position = "top") +
labs(title = "Lợi nhuận HĐKD so với Lợi nhuận sau thuế",
subtitle = "Khoảng cách giữa hai đường thể hiện gánh nặng của chi phí tài chính",
x = "Năm", y = "Giá trị (tỷ VND)")
print(XH_loi_nhuan)
Giải thích code:
Nhận xét: Giai đoạn 2015-2022: Lợi nhuận HĐKD (đường xanh) luôn dương, nhưng Lợi nhuận Sau thuế (đường đỏ) lại liên tục âm nặng, đặc biệt vào 2016 và 2020. Khoảng cách khổng lồ giữa hai đường xác nhận gánh nặng Chi phí Tài chính đã “ăn mòn” toàn bộ thành quả kinh doanh. Bước ngoặt xảy ra vào 2023-2024: khoảng cách này thu hẹp đột ngột, giúp Lợi nhuận Sau thuế lần đầu tiên có lãi dương trở lại và tăng trưởng, khẳng định công ty đã kiểm soát được chi phí tài chính hiệu quả.
KQKD_lnst <- KQKD1 %>%
select(nam, Toc_do_tang_truong_LNST) %>%
filter(!is.na(Toc_do_tang_truong_LNST), !is.infinite(Toc_do_tang_truong_LNST))
plot_lnst_growth <- ggplot(KQKD_lnst, aes(x = nam, y = Toc_do_tang_truong_LNST, fill = Toc_do_tang_truong_LNST > 0)) +
geom_col(width = 0.7) +
geom_hline(yintercept = 0, linetype = "dashed", color = "black") +
geom_text(aes(label = paste0(round(Toc_do_tang_truong_LNST, 0), "%"),
vjust = ifelse(Toc_do_tang_truong_LNST >= 0, -0.5, 1.5)),
size = 3.5) +
labs(title = "Tốc độ tăng trưởng Lợi nhuận sau thuế (YoY)",
x = "Năm", y = "Tỷ lệ tăng trưởng (%)", fill = "Tăng trưởng dương") +
scale_y_continuous(labels = function(x) paste0(x, "%")) +
scale_x_continuous(breaks = unique(KQKD_lnst$nam)) +
scale_fill_manual(values = c("TRUE" = "darkgreen", "FALSE" = "tomato")) +
theme_minimal() +
theme(plot.title = element_text(hjust = 0.5, face = "bold"),
legend.position = "none")
print(plot_lnst_growth)Giải thích code:
Nhận xét: Giai đoạn 2016-2021 liên tục chứng kiến sụt giảm thảm hại, chạm đáy khủng hoảng với mức tăng trưởng âm 680% vào năm 2020. Ngay sau đó, công ty đã có cú “đại nhảy vọt” ngoạn mục, tăng trưởng bùng nổ 456% vào năm 2022, trước khi chững lại vào 2023-2024.
KQKD_lng <- KQKD1 %>%
select(nam, Toc_do_tang_truong_LNG) %>%
filter(!is.na(Toc_do_tang_truong_LNG), !is.infinite(Toc_do_tang_truong_LNG))
plot_lng_growth <- ggplot(KQKD_lng, aes(x = nam, y = Toc_do_tang_truong_LNG, fill = Toc_do_tang_truong_LNG > 0)) +
geom_col(width = 0.7) +
geom_hline(yintercept = 0, linetype = "dashed", color = "black") +
geom_text(aes(label = paste0(round(Toc_do_tang_truong_LNG, 0), "%"),
vjust = ifelse(Toc_do_tang_truong_LNG >= 0, -0.5, 1.5)),
size = 3.5) +
labs(title = "Tốc độ tăng trưởng Lợi nhuận gộp",
x = "Năm", y = "Tỷ lệ tăng trưởng (%)", fill = "Tăng trưởng Dương") +
scale_y_continuous(labels = function(x) paste0(x, "%")) +
scale_x_continuous(breaks = unique(KQKD_lnst$nam)) +
scale_fill_manual(values = c("TRUE" = "darkgreen", "FALSE" = "tomato")) +
theme_minimal() +
theme(plot.title = element_text(hjust = 0.5, face = "bold"),
legend.position = "none")
print(plot_lng_growth)Nhận xét: Giai đoạn 2016-2020 chứng kiến 3 năm tăng trưởng âm, đặc biệt là cú sụt giảm sâu -90% vào năm 2019. Doanh nghiệp đã bùng nổ ngoạn mục trong hai năm 2021 và 2022. Tốc độ tăng trưởng chững lại rõ rệt trong năm 2023 khi chỉ còn tăng 10%. Đến năm 2024, đà tăng trưởng đã phục hồi mạnh mẽ trở lại, đạt mức 68%.
KQKD_cp_growth <- KQKD1 %>%
mutate(
# Lấy giá trị tuyệt đối của chi phí trước khi tính tăng trưởng
cp_bh_abs = abs(chi_phi_bh),
cp_ql_abs = abs(chi_phi_ql),
# Tính tốc độ tăng trưởng chi phí Bán hàng và QLDN
CP_BH_Growth = (cp_bh_abs / lag(cp_bh_abs) - 1) * 100,
CP_QL_Growth = (cp_ql_abs / lag(cp_ql_abs) - 1) * 100
) %>%
select(nam, CP_BH_Growth, CP_QL_Growth) %>%
pivot_longer(-nam, names_to = "Loai_Chi_phi", values_to = "Ty_le_tang_truong") %>%
filter(!is.na(Ty_le_tang_truong), !is.infinite(Ty_le_tang_truong))
plot_cp_growth <- ggplot(KQKD_cp_growth, aes(x = nam, y = Ty_le_tang_truong, color = Loai_Chi_phi)) +
geom_line(size = 1.2) +
geom_point(size = 3) +
geom_hline(yintercept = 0, linetype = "dashed", color = "gray50") +
labs(title = "Tốc độ Tăng trưởng chi phí hoạt động (Bán hàng và QLDN)",
subtitle = "So sánh với tốc độ tăng trưởng doanh thu để
đánh giá hiệu quả kiểm soát chi phí",
x = "Năm", y = "Tỷ lệ tăng trưởng (%)", color = "Loại chi phí") +
scale_color_manual(values = c("CP_BH_Growth" = "orange", "CP_QL_Growth" = "purple"),
labels = c("CP_BH_Growth" = "Chi phí Bán hàng", "CP_QL_Growth" = "Chi phí QLDN")) +
scale_x_continuous(breaks = unique(KQKD_cp_growth$nam)) +
scale_y_continuous(labels = function(x) paste0(x, "%")) +
theme_bw() +
theme(plot.title = element_text(hjust = 0.5, face = "bold"),
legend.position = "top")
print(plot_cp_growth)Giải thích code:
(14),(15) Tạo hai đường và điểm riêng biệt thể hiện xu hướng tăng trưởng của mỗi loại chi phí.
Nhận xét: Đặc biệt năm 2022 có chi phí QLDN “bùng nổ”, với tốc độ tăng trưởng phi mã (lên đến hơn 500% vào 2022). Ngay sau đó, chi phí này được “ghìm” lại và tăng trưởng âm vào 2021 và 2023. Ngược lại, Chi phí Bán hàng (cam) tăng trưởng ổn định và dễ đoán hơn nhiều.
KQKD_ty_le <- KQKD1 %>%
select(nam, Toc_do_tang_truong_DT, Toc_do_tang_truong_LNG) %>%
pivot_longer(-nam, names_to = "Chi_tieu", values_to = "Ty_le_tang_truong")
plot_growth_line <- ggplot(KQKD_ty_le, aes(x = nam, y = Ty_le_tang_truong, color = Chi_tieu)) +
geom_line(aes(group = Chi_tieu), size = 1.2) +
geom_point(size = 3) +
geom_hline(yintercept = 0, linetype = "dashed", color = "red") +
labs(
title = "Tốc độ tăng trưởng Doanh thu và Lợi nhuận gộp",
x = "Năm", y = "Tỷ lệ tăng trưởng (%)", color = "Chỉ tiêu"
) +
scale_y_continuous(labels = function(x) paste0(x, "%")) +
scale_x_continuous(breaks = unique(KQKD_ty_le$nam)) +
scale_color_manual(
values = c("Toc_do_tang_truong_DT" = "darkblue", "Toc_do_tang_truong_LNG" = "orange"),
labels = c("Toc_do_tang_truong_DT" = "Tốc độ tăng trưởng DT Thuần", "Toc_do_tang_truong_LNG" = "Tốc độ tăng trưởng LN Gộp")
) +
theme_minimal() +
theme(
plot.title = element_text(hjust = 0.5, face = "bold"),
legend.position = "top" # Di chuyển chú giải lên trên
)
print(plot_growth_line)Giải thích code:
Nhận xét: Tốc độ tăng trưởng của doanh nghiệp mang tính chu kỳ và bất ổn định rất cao. Cả doanh thu (xanh) và lợi nhuận gộp (cam) đều trải qua những cú sụt giảm sâu (như 2019, 2023) xen kẽ với các giai đoạn phục hồi bùng nổ (2017, 2021-2022). Đáng chú ý, tốc độ tăng trưởng lợi nhuận gộp luôn biến động với biên độ lớn hơn doanh thu. Điều này hàm ý rằng biên lợi nhuận gộp của công ty không ổn định, bị bào mòn rất mạnh khi doanh thu giảm và cũng được khuếch đại nhanh khi doanh thu phục hồi.
Bài tiểu luận đã trình bày một cách toàn diện về năng lực và tính ứng dụng của ngôn ngữ lập trình R trong lĩnh vực phân tích dữ liệu kinh doanh thông qua hai nghiên cứu tình huống điển hình.
1. Tổng kết các kết quả chính:
Đối với bài toán bán lẻ xe đạp (Chương 1), phân tích đã chỉ ra rằng công ty sở hữu một mô hình kinh doanh cực kỳ ổn định và cân bằng. Doanh thu và hiệu suất hoạt động được phân bổ đều trên các dòng sản phẩm, địa điểm kinh doanh và nhóm khách hàng. Điều này cho thấy một nền tảng vững chắc nhưng cũng mở ra cơ hội để tạo ra các chiến lược đột phá nhằm tối ưu hóa cho từng phân khúc cụ thể.
Đối với bài toán tài chính của CTCP Hoàng Anh Gia Lai (Chương 2), phân tích báo cáo kết quả kinh doanh giai đoạn 2015-2024 đã vẽ nên một bức tranh sống động về quá trình “hồi sinh” của doanh nghiệp. Kết quả khẳng định rằng chiến lược tái cấu trúc đã thành công rực rỡ, đặc biệt là trong việc kiểm soát và cắt giảm gánh nặng chi phí tài chính, từ đó giúp HAG chuyển từ thua lỗ sang giai đoạn tăng trưởng lợi nhuận bền vững.
2. Khẳng định vai trò của Ngôn ngữ R:
Qua hai ví dụ trên, có thể thấy R không chỉ là một công cụ tính toán mà còn là một hệ sinh thái mạnh mẽ cho phép thực hiện toàn bộ quy trình khoa học dữ liệu. Từ việc làm sạch, biến đổi dữ liệu (dplyr, tidyr) đến việc phân tích thống kê và trực quan hóa các insight phức tạp một cách sinh động (ggplot2), R đã chứng tỏ là một công cụ không thể thiếu cho các nhà phân tích kinh doanh hiện đại.
3. Hạn chế của bài tiểu luận:
Mặc dù đã đạt được các mục tiêu đề ra, bài tiểu luận vẫn còn một số hạn chế. Thứ nhất, bộ dữ liệu bán lẻ xe đạp là dữ liệu giả định, có thể không phản ánh hết sự phức tạp của thị trường thực tế. Thứ hai, phân tích tài chính HAG chỉ tập trung vào báo cáo kết quả kinh doanh và chưa kết hợp các yếu tố vĩ mô hay các báo cáo tài chính khác để có cái nhìn đa chiều hơn. Cuối cùng, các phân tích chủ yếu dừng lại ở mức độ mô tả mà chưa đi sâu vào các mô hình dự báo hay phân tích nhân quả.
4. Hướng phát triển trong tương lai:
Dựa trên nền tảng của bài tiểu luận này, các nghiên cứu tiếp theo có thể được phát triển theo hướng:
Xây dựng mô hình học máy để dự báo doanh số bán xe đạp hoặc phân khúc khách hàng tiềm năng.
Thực hiện một phân tích tài chính toàn diện hơn cho HAG, kết hợp mô hình định giá cổ phiếu hoặc phân tích các chỉ số từ bảng cân đối kế toán và báo cáo lưu chuyển tiền tệ.
Tóm lại, bài tiểu luận đã hoàn thành mục tiêu ứng dụng R để khai phá những câu chuyện kinh doanh ẩn sau các con số, qua đó khẳng định giá trị to lớn của phân tích dữ liệu trong việc ra quyết định chiến lược