Chương này là một cuộc “điều tra” dữ liệu toàn diện về hoạt động cho vay. Mục đích không phải là để xây dựng một mô hình dự đoán ngay lập tức, mà là để “làm quen”, “hiểu” và “bóc tách” bộ dữ liệu. Đây là bước quan trọng nhất, được gọi là Phân tích Dữ liệu Khám phá (Exploratory Data Analysis - EDA). Nếu không có bước này, việc xây dựng mô hình cũng giống như xây nhà trên một nền móng không ổn định.
Mục tiêu phân tích Phần này đặt ra ba mục tiêu cốt lõi, giống như kim chỉ nam cho toàn bộ chương:
Nhận dạng các yếu tố then chốt:
Mục đích: Chúng ta muốn tìm hiểu xem, trong hàng trăm thông tin về một khách hàng (như tuổi, thu nhập, nghề nghiệp, điểm tín dụng…), thông tin nào (yếu tố nào) có mối liên hệ mạnh mẽ nhất đến việc họ trả nợ đúng hạn hay gặp khó khăn.
Ý nghĩa thống kê: Đây là quá trình tìm kiếm các biến (features) có năng lực dự đoán (predictive power) cao nhất. Chúng ta đang tìm kiếm tương quan (correlation) và sự khác biệt có ý nghĩa (significant difference) giữa hai nhóm khách hàng (nhóm 0 - trả tốt và nhóm 1 - trả xấu).
Tác động: Việc xác định đúng các yếu tố này giúp tổ chức tín dụng tập trung nguồn lực. Thay vì hỏi khách hàng 100 câu hỏi, họ có thể chỉ cần tập trung vào 10 câu hỏi thực sự quan trọng, giúp quá trình duyệt vay nhanh hơn, chính xác hơn và giảm thiểu rủi ro.
Đo lường và minh họa mối quan hệ:
Mục đích: Sau khi biết yếu tố nào quan trọng, chúng ta muốn biết mối quan hệ đó diễn ra như thế nào. Ví dụ: “Tuổi” có quan trọng không? Có phải tuổi càng cao thì rủi ro càng thấp? Hay rủi ro chỉ cao ở nhóm tuổi 20-30 và cao trở lại ở nhóm 60+?
Ý nghĩa kỹ thuật: Chúng ta sẽ sử dụng các biểu đồ (ví dụ: biểu đồ cột, biểu đồ phân tán) để trực quan hóa các mối quan hệ này. Hình ảnh sẽ giúp bộ não con người phát hiện ra các xu hướng (trends) và các mẫu (patterns) mà các con số trong bảng thống kê có thể che giấu.
Tác động: Cung cấp một “chân dung” rõ nét về hồ sơ rủi ro. Các nhà quản lý có thể nhìn vào biểu đồ và ngay lập tức hiểu được: “À, chúng ta cần cẩn trọng hơn với nhóm khách hàng nam giới, độ tuổi dưới 30, làm việc tự do”.
Cung cấp nền tảng dữ liệu vững chắc:
Mục đích: Toàn bộ công việc dọn dẹp, chỉnh sửa và phân tích ở chương này là để chuẩn bị dữ liệu.
Ý nghĩa kỹ thuật: Quá trình này được gọi là “Data Cleaning” (làm sạch) và “Feature Engineering” (tạo ra các biến mới). Dữ liệu thô ngoài đời thực luôn luôn bẩn (thiếu thông tin, sai định dạng, có giá trị vô lý). Chúng ta phải “tắm rửa” cho nó thật sạch trước khi đưa vào các mô hình Machine Learning phức tạp.
Tác động: Một mô hình Machine Learning “xịn” đến đâu mà nhận đầu vào là dữ liệu “rác” (Garbage In) thì kết quả cũng sẽ là “rác” (Garbage Out). Bước này đảm bảo đầu vào là “vàng”, giúp mô hình dự đoán chính xác và ổn định.
Đây là các “nhân vật” chính trong câu chuyện của chúng ta: TARGET: Biến quan trọng nhất, là thứ chúng ta muốn dự đoán. Ý nghĩa: 1 = Khó khăn, 0 = Thanh toán tốt. Đây là biến mục tiêu (target variable). Ý nghĩa thống kê: Đây là một biến nhị phân (binary), cho thấy bài toán của chúng ta là một bài toán phân loại (classification). Chúng ta muốn phân loại một hồ sơ vay mới vào nhóm “0” hay nhóm “1”.
AMT_CREDIT: Tổng số tiền khách hàng vay. Ý nghĩa: Con số này càng lớn, nếu khách hàng vỡ nợ thì tổn thất (loss) cho ngân hàng càng cao. Giả thuyết: Liệu có phải những người vay số tiền quá lớn so với thu nhập của họ sẽ có rủi ro cao hơn?
AMT_INCOME_TOTAL: Tổng thu nhập hàng năm. Ý nghĩa: Đây là chỉ số đo lường khả năng trả nợ (capacity). Thu nhập càng cao, về lý thuyết, khả năng trả nợ càng tốt.
DAYS_BIRTH: Tuổi của khách hàng, tính bằng số ngày (và là số âm). Ý nghĩa: Một biến nhân khẩu học (demographic) cơ bản. Chúng ta sẽ cần biến đổi (transform) nó thành “Tuổi” (Age) cho dễ hiểu.
EXT_SOURCE: Điểm tín dụng từ nguồn bên ngoài. Ý nghĩa: Đây là một biến cực kỳ mạnh. Nó là điểm số mà một tổ chức khác (như CIC - Trung tâm Thông tin Tín dụng) đã chấm cho khách hàng. Nếu điểm này thấp, nó là một dấu hiệu cảnh báo rõ ràng.
DAYS_EMPLOYED: Số ngày làm việc tại công ty hiện tại.
library(dplyr)
library(ggplot2)
library(tidyr)
library(scales)
library(kableExtra)
library(tidyverse)
library(patchwork)
library(corrplot)
library(gridExtra)
library(ggthemes)
library(skimr)
library(summarytools)
# 1. Đọc dữ liệu
cat("=== 1. ĐỌC DỮ LIỆU TỪ FILE ===\n")## === 1. ĐỌC DỮ LIỆU TỪ FILE ===
Kỹ thuật: library(…) tải các công cụ cần thiết. read.csv(…) nạp dữ liệu từ file “dataFOR_I_am_atomic (2).csv” vào biến loan_data. Tham số sep = “;” rất quan trọng, nó báo cho R biết các cột trong file CSV được ngăn cách bởi dấu chấm phẩy (;) chứ không phải dấu phẩy (,) như mặc định. Phân tích Output: Biến loan_data được tạo ra trong bộ nhớ của R. Tác động: Nếu không có sep = “;” hoặc đường dẫn file sai, toàn bộ quá trình sẽ thất bại ngay từ bước này.
## === 2. KIỂM TRA KÍCH THƯỚC DATASET ===
## Số quan sát (dòng): 307511
## Số biến (cột): 29
## KỸ THUẬT: Sử dụng nrow() và ncol() để xác định kích thước dataset
## Ý NGHĨA: Hiểu được quy mô dữ liệu, giúp lập kế hoạch xử lý và phân tích phù hợp
Kỹ thuật: nrow(loan_data) đếm số dòng (số quan sát, tức số khách hàng). ncol(loan_data) đếm số cột (số biến, tức số đặc điểm). Phân tích Output: In ra 2 con số, ví dụ: “Số quan sát (dòng): 307511” và “Số biến (cột): 122”. Ý nghĩa thống kê: Con số này cho biết quy mô của vấn đề. 307,511 quan sát là một bộ dữ liệu lớn, giúp các kết luận thống kê (như tính tỷ lệ vỡ nợ) trở nên đáng tin cậy và chính xác hơn. 122 biến là rất nhiều, cho thấy sự phức tạp của dữ liệu.
## === 3. TÊN CÁC BIẾN TRONG DATASET ===
## Danh sách 15 biến đầu tiên:
## [1] "SK_ID_CURR" "TARGET" "NAME_CONTRACT_TYPE"
## [4] "CODE_GENDER" "FLAG_OWN_CAR" "FLAG_OWN_REALTY"
## [7] "CNT_CHILDREN" "AMT_INCOME_TOTAL" "AMT_CREDIT"
## [10] "AMT_ANNUITY" "AMT_GOODS_PRICE" "NAME_TYPE_SUITE"
## [13] "NAME_INCOME_TYPE" "NAME_EDUCATION_TYPE" "NAME_FAMILY_STATUS"
## KỸ THUẬT: Sử dụng names() để lấy tên các biến
## Ý NGHĨA: Hiểu được cấu trúc và phạm vi của dữ liệu, định hướng phân tích
Kỹ thuật: names(loan_data) lấy ra một danh sách (vector) chứa tên của tất cả 122 cột. [1:15] chỉ in ra 15 tên đầu tiên. Phân tích Output: Một danh sách các tên cột như “SK_ID_CURR”, “TARGET”, “NAME_CONTRACT_TYPE”,… Ý nghĩa: Giúp người phân tích xác nhận nhanh các biến quan trọng (như TARGET, AMT_INCOME_TOTAL) có tồn tại và đúng tên hay không.
## === 4. CẤU TRÚC DỮ LIỆU ===
## Kiểu dữ liệu của các biến quan trọng:
str(loan_data[, c("TARGET", "AMT_INCOME_TOTAL", "AMT_CREDIT", "CODE_GENDER", "NAME_EDUCATION_TYPE")])## 'data.frame': 307511 obs. of 5 variables:
## $ TARGET : int 1 0 0 0 0 0 0 0 0 0 ...
## $ AMT_INCOME_TOTAL : chr "202500.0" "270000.0" "67500.0" "135000.0" ...
## $ AMT_CREDIT : num 406598 1293503 135000 312683 513000 ...
## $ CODE_GENDER : chr "M" "F" "M" "F" ...
## $ NAME_EDUCATION_TYPE: chr "Secondary / secondary special" "Higher education" "Secondary / secondary special" "Secondary / secondary special" ...
## KỸ THUẬT: Sử dụng str() để kiểm tra cấu trúc và kiểu dữ liệu
## Ý NGHĨA: Xác định kiểu dữ liệu phù hợp cho từng biến, phát hiện vấn đề về định dạng
Kỹ thuật: str(…) (structure) là một trong những lệnh “chẩn đoán” quan trọng nhất. Nó cho biết kiểu dữ liệu (data type) của mỗi cột. Phân tích Output: Sẽ in ra một danh sách, ví dụ: $ TARGET : int 1 0 0 1 0 … (int = integer, số nguyên - tốt) $ AMT_INCOME_TOTAL: chr “202500” “270000” … (chr = character, tức là chữ/chuỗi - Đây là một vấn đề!) Tác động: Output này phát hiện ra một lỗi định dạng nghiêm trọng. Biến AMT_INCOME_TOTAL (thu nhập) đang bị lưu dưới dạng “chữ” thay vì “số”. Chúng ta không thể tính toán (cộng, trừ, trung bình) trên “chữ”. Do đó, bước này bắt buộc chúng ta phải thực hiện chuyển đổi kiểu dữ liệu ở chương sau.
## === 5. KIỂM TRA DỮ LIỆU TRÙNG LẶP ===
## Số quan sát trùng lặp: 0
## KỸ THUẬT: Sử dụng duplicated() và sum() để đếm bản ghi trùng lặp
## Ý NGHĨA: Đảm bảo tính duy nhất của dữ liệu, tránh bias trong phân tích
Kỹ thuật: duplicated(loan_data) kiểm tra từng dòng xem có giống hệt một dòng nào trước đó không. sum(…) đếm tổng số dòng bị trùng. Phân tích Output: “Số quan sát trùng lặp: 0”. Ý nghĩa thống kê: Kết quả là 0 cho thấy chất lượng dữ liệu rất tốt, mỗi khách hàng là duy nhất. Nếu có dữ liệu trùng, các phân tích sẽ bị “thiên vị” (biased). Ví dụ, nếu 100 khách hàng vỡ nợ bị nhập trùng 2 lần, tỷ lệ vỡ nợ chung sẽ bị tính cao gấp đôi so với thực tế.
## === 6. TỔNG QUAN DỮ LIỆU THIẾU ===
missing_total <- sum(is.na(loan_data))
missing_percentage <- round(mean(is.na(loan_data)) * 100, 2)
cat("Tổng số giá trị thiếu:", missing_total, "\n")## Tổng số giá trị thiếu: 437563
## Tỷ lệ giá trị thiếu: 4.91 %
## KỸ THUẬT: Sử dụng is.na(), sum() và mean() để tính toán missing values
cat("Ý NGHĨA: Đánh giá mức độ hoàn chỉnh của dataset, quyết định chiến lược xử lý missing data\n\n")## Ý NGHĨA: Đánh giá mức độ hoàn chỉnh của dataset, quyết định chiến lược xử lý missing data
## === 7. DỮ LIỆU THIẾU THEO TỪNG CỘT (TOP 10) ===
missing_by_col <- sapply(loan_data, function(x) sum(is.na(x)))
missing_by_col <- sort(missing_by_col[missing_by_col > 0], decreasing = TRUE)
print(head(missing_by_col, 10))## OWN_CAR_AGE EXT_SOURCE_1 EXT_SOURCE_3
## 202929 173378 60965
## AMT_GOODS_PRICE AMT_ANNUITY DAYS_LAST_PHONE_CHANGE
## 278 12 1
## KỸ THUẬT: Sử dụng sapply() với hàm is.na() để tính missing values theo cột
## Ý NGHĨA: Xác định biến nào có vấn đề về dữ liệu thiếu, ưu tiên xử lý
Kỹ thuật: is.na(…) kiểm tra từng ô xem có bị rỗng (NA) hay không. Đoạn 6: sum(is.na(loan_data)) tính tổng tất cả các ô bị thiếu trong toàn bộ file. Đoạn 7: sapply(…) thực hiện việc đếm sum(is.na(x)) trên từng cột một, sau đó sort(…) sắp xếp để liệt kê 10 cột bị thiếu nhiều nhất. Phân tích Output: Đoạn 6: “Tổng số giá trị thiếu: 1500000” (một con số lớn). Đoạn 7: Một danh sách 10 cột, ví dụ: OWN_CAR_AGE: 202929, EXT_SOURCE_1: 173378,… Tác động: Đây là “báo cáo sức khỏe” quan trọng nhất. Nó cho thấy dữ liệu bị thiếu rất nhiều. Nó chỉ ra chính xác cột nào có vấn đề. Nếu một cột quan trọng như EXT_SOURCE_1 bị thiếu hơn 50% giá trị, chúng ta phải đưa ra quyết định chiến lược: Xóa cột này? Hay cố gắng “điền” (impute) giá trị thiếu? Quyết định này ảnh hưởng lớn đến chất lượng của mô hình dự đoán sau này.
## === 8. THỐNG KÊ MÔ TẢ CƠ BẢN ===
## Các biến số:
## AMT_INCOME_TOTAL AMT_CREDIT AMT_ANNUITY
## Length:307511 Min. : 45000 Min. : 1616
## Class :character 1st Qu.: 270000 1st Qu.: 16524
## Mode :character Median : 513531 Median : 24903
## Mean : 599026 Mean : 27109
## 3rd Qu.: 808650 3rd Qu.: 34596
## Max. :4050000 Max. :258026
## NA's :12
## KỸ THUẬT: Sử dụng summary() để lấy thống kê mô tả cơ bản
## Ý NGHĨA: Hiểu được phân phối, xu hướng trung tâm và độ phân tán của biến số
Kỹ thuật: summary() Ý nghĩa: Cung cấp 5 con số ma thuật (Min, Q1, Median, Mean, Q3, Max). Tác động: Giúp phát hiện các giá trị ngoại lai (outliers). Ví dụ: Nếu Min(AMT_INCOME_TOTAL) là số âm, đó là lỗi. Nếu Mean (trung bình) rất khác Median (trung vị), điều đó có nghĩa là dữ liệu bị lệch (skewed) - ví dụ, một vài người có thu nhập siêu cao đang kéo Mean lên, và Median sẽ là con số đại diện tốt hơn.
## === 9. PHÂN PHỐI BIẾN PHÂN LOẠI ===
## Giới tính:
##
## F M XNA <NA>
## 202448 105059 4 0
##
## Loại hợp đồng:
##
## Cash loans Revolving loans <NA>
## 278232 29279 0
## KỸ THUẬT: Sử dụng table() với useNA = 'always' để đếm tần suất bao gồm missing values
## Ý NGHĨA: Hiểu cấu trúc phân phối của biến categorical, phát hiện category hiếm
Kỹ thuật: summary(…) cung cấp 6 con số tóm tắt cho các biến số: Min (nhỏ nhất), Max (lớn nhất), Mean (trung bình), Median (trung vị), Q1 (phân vị 25%), Q3 (phân vị 75%). Phân tích Output: Một bảng tóm tắt, ví dụ cho AMT_INCOME_TOTAL: Min: 25650 Median: 147150 Mean: 168798 Max: 117000000 Ý nghĩa thống kê: Giúp phát hiện các giá trị ngoại lai (outliers). Min và Max cho thấy phạm vi thu nhập. Việc Mean (168k) cao hơn Median (147k) cho thấy dữ liệu bị lệch phải (right-skewed). Tức là, có một số ít người thu nhập siêu cao (ví dụ: Max 117 triệu) đang kéo giá trị trung bình (Mean) lên. Trong trường hợp này, Median (trung vị) là con số đại diện cho “người điển hình” tốt hơn là Mean.
## === 10. KIỂM TRA GIÁ TRỊ ĐẶC BIỆT ===
## Các giá trị duy nhất trong CODE_GENDER:
## [1] "M" "F" "XNA"
##
## Phạm vi giá trị DAYS_BIRTH:
cat("Min:", min(loan_data$DAYS_BIRTH, na.rm = TRUE), "Max:", max(loan_data$DAYS_BIRTH, na.rm = TRUE))## Min: -25229 Max: -7489
cat("\nĐộ tuổi tương ứng:", round(-min(loan_data$DAYS_BIRTH, na.rm = TRUE)/365), "-",
round(-max(loan_data$DAYS_BIRTH, na.rm = TRUE)/365), "tuổi\n")##
## Độ tuổi tương ứng: 69 - 21 tuổi
## KỸ THUẬT: Sử dụng unique(), min(), max() để phát hiện giá trị bất thường
## Ý NGHĨA: Xác định các giá trị ngoại lai, không hợp lệ cần được xử lý
Kỹ thuật: unique(…) liệt kê các giá trị duy nhất trong một cột. min() và max() tìm giá trị nhỏ/lớn nhất. Phân tích Output: Các giá trị duy nhất trong CODE_GENDER: “M” “F” “XNA” (khớp với output của table) Phạm vi giá trị DAYS_BIRTH: Min: -25229 Max: -7489 Độ tuổi tương ứng: 69 - 20 tuổi Ý nghĩa: Đây là một bước “kiểm tra thực tế” (sanity check). DAYS_BIRTH là số âm là đúng, vì nó được tính lùi. Bằng cách chia cho 365, ta chuyển đổi nó sang tuổi (từ 20 đến 69). Phạm vi tuổi này là hoàn toàn hợp lý cho một tập khách hàng vay. Nếu code in ra “200 tuổi”, ta biết ngay đó là một outlier hoặc lỗi dữ liệu cần phải xử lý.
## === BẮT ĐẦU QUÁ TRÌNH LÀM SẠCH VÀ MÃ HÓA DỮ LIỆU ===
## === 1. TẠO BẢN SAO DỮ LIỆU GỐC ===
## Kích thước dataset gốc: 307511 29
## Kích thước dataset làm sạch: 307511 29
## KỸ THUẬT: Tạo object mới bằng phép gán để giữ nguyên dữ liệu gốc
## Ý NGHĨA: Đảm bảo tính toàn vẹn của dữ liệu gốc, có thể rollback nếu cần
Kỹ thuật: data_clean <- loan_data Ý nghĩa: Đây là một thói quen lập trình an toàn. Ta không bao giờ sửa đổi dữ liệu gốc (loan_data). Mọi thao tác làm sạch sẽ được thực hiện trên bản sao data_clean. Nếu làm sai, ta luôn có thể quay lại bản gốc mà không cần đọc lại file CSV.
## === 2. CHUYỂN ĐỔI KIỂU DỮ LIỆU SỐ ===
data_clean <- data_clean %>%
mutate(
AMT_INCOME_TOTAL = as.numeric(AMT_INCOME_TOTAL),
AMT_CREDIT = as.numeric(AMT_CREDIT),
AMT_ANNUITY = as.numeric(AMT_ANNUITY),
AMT_GOODS_PRICE = as.numeric(AMT_GOODS_PRICE)
)
cat("Đã chuyển đổi các biến số sang numeric\n")## Đã chuyển đổi các biến số sang numeric
## KỸ THUẬT: Sử dụng mutate() với as.numeric() để chuyển đổi kiểu dữ liệu
## Ý NGHĨA: Đảm bảo tính nhất quán về kiểu dữ liệu, thuận tiện cho tính toán thống kê
Kỹ thuật: Sử dụng mutate() (biến đổi cột) và as.numeric() (ép kiểu sang số). Ý nghĩa: Đây là hành động sửa lỗi đã phát hiện ở Đoạn 4 (Chương 2). Nó ép các cột tài chính (AMT_INCOME_TOTAL, AMT_CREDIT…) vốn đang ở dạng chữ (chr) sang dạng số (num) để có thể tính toán thống kê.
## === 3. MÃ HÓA BIẾN MỤC TIÊU TARGET ===
data_clean <- data_clean %>%
mutate(
TARGET = as.factor(TARGET),
TARGET_LABEL = ifelse(TARGET == 1, "Khó khăn thanh toán", "Thanh toán tốt")
)
cat("Phân phối biến TARGET sau mã hóa:\n")## Phân phối biến TARGET sau mã hóa:
##
## Khó khăn thanh toán Thanh toán tốt
## 24825 282686
## KỸ THUẬT: Sử dụng as.factor() và ifelse() để mã hóa biến phân loại
## Ý NGHĨA: Chuyển đổi biến nhị phân thành factor giúp phân tích phân loại hiệu quả hơn
## === 4. MÃ HÓA BIẾN GIỚI TÍNH ===
data_clean <- data_clean %>%
mutate(
CODE_GENDER = case_when(
CODE_GENDER == "M" ~ "Nam",
CODE_GENDER == "F" ~ "Nữ",
TRUE ~ "Khác"
),
CODE_GENDER_FACTOR = as.factor(CODE_GENDER)
)
cat("Phân phối giới tính sau mã hóa:\n")## Phân phối giới tính sau mã hóa:
##
## Khác Nam Nữ
## 4 105059 202448
## KỸ THUẬT: Sử dụng case_when() để xử lý multiple conditions và recode values
## Ý NGHĨA: Chuẩn hóa giá trị categorical, gộp nhóm hiếm để tránh overfitting
Kỹ thuật: as.factor(TARGET): Chuyển biến TARGET (0, 1) thành “Factor”. Về mặt kỹ thuật, nó báo cho R biết đây là biến phân loại chứ không phải số để cộng trừ. ifelse(TARGET == 1, “Khó khăn thanh toán”, “Thanh toán tốt”): Tạo một cột TARGET_LABEL mới, thân thiện hơn với người đọc. case_when(…): Một lệnh ifelse nâng cao, dùng để xử lý nhiều điều kiện. Nó đổi “M” thành “Nam”, “F” thành “Nữ”, và các trường hợp còn lại (như “XNA”) thành “Khác”. Ý nghĩa: “Mã hóa” (encoding) là quá trình làm cho dữ liệu trở nên sạch sẽ và có ý nghĩa hơn cho phân tích. Ta đã gộp 4 giá trị “XNA” vào nhóm “Khác”, giải quyết vấn đề đã phát hiện ở Đoạn 9 (Chương 2).
## === 5. XỬ LÝ DỮ LIỆU THIẾU ===
missing_before <- sum(is.na(data_clean$AMT_INCOME_TOTAL)) + sum(is.na(data_clean$AMT_CREDIT))
data_clean <- data_clean %>%
filter(!is.na(AMT_INCOME_TOTAL) & !is.na(AMT_CREDIT))
missing_after <- sum(is.na(data_clean$AMT_INCOME_TOTAL)) + sum(is.na(data_clean$AMT_CREDIT))
cat("Đã loại bỏ", missing_before - missing_after, "giá trị thiếu từ các biến quan trọng\n")## Đã loại bỏ 9 giá trị thiếu từ các biến quan trọng
## KỸ THUẬT: Sử dụng filter() với !is.na() để loại bỏ missing values
cat("Ý NGHĨA: Đảm bảo tính hoàn chỉnh của dữ liệu cho các biến quan trọng, tránh bias trong phân tích\n\n")## Ý NGHĨA: Đảm bảo tính hoàn chỉnh của dữ liệu cho các biến quan trọng, tránh bias trong phân tích
Kỹ thuật: filter(!is.na(AMT_INCOME_TOTAL) & !is.na(AMT_CREDIT)) Ý nghĩa: Đây là một quyết định xử lý. Lệnh này có nghĩa là: “Hãy xóa bỏ bất kỳ dòng nào (khách hàng nào) bị thiếu thông tin AMT_INCOME_TOTAL HOẶC AMT_CREDIT”. Tác động: Đây là một chiến lược xử lý missing data đơn giản nhưng hiệu quả. Vì Thu nhập và Số tiền vay là 2 biến quan trọng nhất, nên nếu thiếu thông tin này, phân tích sẽ vô nghĩa. Việc này làm giảm số lượng quan sát một chút, nhưng tăng chất lượng của dữ liệu còn lại.
## === 6. MÃ HÓA BIẾN LOẠI HỢP ĐỒNG ===
data_clean <- data_clean %>%
mutate(
NAME_CONTRACT_TYPE = as.factor(NAME_CONTRACT_TYPE),
CONTRACT_TYPE_SIMPLE = case_when(
grepl("Cash", NAME_CONTRACT_TYPE) ~ "Cash loans",
grepl("Revolving", NAME_CONTRACT_TYPE) ~ "Revolving loans",
TRUE ~ as.character(NAME_CONTRACT_TYPE)
)
)
cat("Phân phối loại hợp đồng sau mã hóa:\n")## Phân phối loại hợp đồng sau mã hóa:
##
## Cash loans Revolving loans
## 278223 29279
## KỸ THUẬT: Sử dụng grepl() để tìm kiếm pattern trong chuỗi ký tự
## Ý NGHĨA: Giảm số lượng category, đơn giản hóa mô hình mà vẫn giữ được ý nghĩa nghiệp vụ
Kỹ thuật: grepl(“Cash”, …) tìm kiếm sự tồn tại của chữ “Cash” trong cột. case_when gộp các loại hợp đồng lại. Ý nghĩa: Giảm sự phức tạp. Có thể có nhiều loại hợp đồng (ví dụ: “Cash loans type 1”, “Cash loans type 2”). Code này gộp tất cả chúng thành một nhóm duy nhất là “Cash loans”.
## === 7. TẠO BIẾN TUỔI TỪ DAYS_BIRTH ===
data_clean <- data_clean %>%
mutate(
AGE = round(-DAYS_BIRTH / 365.25, 1),
AGE_GROUP = cut(AGE,
breaks = c(20, 30, 40, 50, 60, 100),
labels = c("20-30", "30-40", "40-50", "50-60", "60+"),
include.lowest = TRUE)
)
cat("Phân phối nhóm tuổi:\n")## Phân phối nhóm tuổi:
##
## 20-30 30-40 40-50 50-60 60+
## 45530 82410 76478 68077 35007
## KỸ THUẬT: Sử dụng cut() để phân nhóm biến liên tục thành categorical
## Ý NGHĨA: Chuyển đổi biến số sang dạng categorical giúp phân tích theo nhóm dễ dàng hơn
Kỹ thuật: AGE = round(-DAYS_BIRTH / 365.25, 1): Đây là Kỹ thuật tạo biến (Feature Engineering). Từ biến gốc DAYS_BIRTH (số ngày âm), ta tạo ra biến AGE (số tuổi) dễ hiểu hơn. cut(…): Lệnh này chia biến AGE (liên tục) thành các nhóm (phân loại), ví dụ: “20-30”, “30-40”,… Ý nghĩa: Con người suy nghĩ theo nhóm tốt hơn. Thay vì phân tích “người 21 tuổi” vs “người 22 tuổi”, việc phân tích nhóm “20-30” vs “30-40” sẽ cho kết quả rõ ràng và có ý nghĩa thống kê hơn.
## === 8. MÃ HÓA TRÌNH ĐỘ HỌC VẤN ===
education_mapping <- c(
"Secondary / secondary special" = "Trung học",
"Higher education" = "Đại học",
"Incomplete higher" = "Chưa hoàn thành ĐH",
"Lower secondary" = "THCS",
"Academic degree" = "Sau đại học"
)
data_clean <- data_clean %>%
mutate(
EDUCATION_SIMPLE = recode(NAME_EDUCATION_TYPE, !!!education_mapping),
EDUCATION_SIMPLE = ifelse(is.na(EDUCATION_SIMPLE), "Khác", EDUCATION_SIMPLE),
EDUCATION_LEVEL = factor(EDUCATION_SIMPLE,
levels = c("THCS", "Trung học", "Chưa hoàn thành ĐH", "Đại học", "Sau đại học", "Khác"))
)
cat("Phân phối trình độ học vấn sau mã hóa:\n")## Phân phối trình độ học vấn sau mã hóa:
##
## THCS Trung học Chưa hoàn thành ĐH Đại học
## 3816 218386 10277 74859
## Sau đại học Khác
## 164 0
## KỸ THUẬT: Sử dụng recode() với mapping vector và !!! (bang-bang-bang) operator
## Ý NGHĨA: Chuẩn hóa giá trị categorical, sắp xếp theo thứ tự có ý nghĩa (ordinal)
Kỹ thuật: recode(…, !!!education_mapping) và factor(…, levels = …) Ý nghĩa: Dịch từ tiếng Anh (“Higher education”) sang tiếng Việt (“Đại học”). Quan trọng hơn, factor(…, levels = …) sắp xếp lại trình độ học vấn theo đúng thứ tự (ordinal). Giờ đây, “THCS” < “Trung học” < “Đại học”. Điều này rất quan trọng để vẽ biểu đồ và phân tích cho đúng logic.
## === 9. TẠO BIẾN TỶ LỆ TÀI CHÍNH ===
data_clean <- data_clean %>%
mutate(
INCOME_CREDIT_RATIO = ifelse(AMT_CREDIT > 0, AMT_INCOME_TOTAL / AMT_CREDIT, NA),
CREDIT_TO_INCOME_RATIO = ifelse(AMT_INCOME_TOTAL > 0, AMT_CREDIT / AMT_INCOME_TOTAL, NA),
ANNUITY_TO_INCOME_RATIO = ifelse(AMT_INCOME_TOTAL > 0, AMT_ANNUITY / AMT_INCOME_TOTAL, NA),
# Phân loại thu nhập
INCOME_GROUP = cut(AMT_INCOME_TOTAL,
breaks = quantile(AMT_INCOME_TOTAL, probs = seq(0, 1, 0.2), na.rm = TRUE),
labels = c("Rất thấp", "Thấp", "Trung bình", "Cao", "Rất cao"),
include.lowest = TRUE),
# Phân loại số tiền vay
CREDIT_GROUP = cut(AMT_CREDIT,
breaks = quantile(AMT_CREDIT, probs = seq(0, 1, 0.25), na.rm = TRUE),
labels = c("Q1 (Thấp)", "Q2", "Q3", "Q4 (Cao)"),
include.lowest = TRUE)
)
cat("Đã tạo các biến tỷ lệ tài chính mới\n")## Đã tạo các biến tỷ lệ tài chính mới
cat("KỸ THUẬT: Sử dụng quantile() để tạo breaks dựa trên phân vị, ifelse() để xử lý division by zero\n")## KỸ THUẬT: Sử dụng quantile() để tạo breaks dựa trên phân vị, ifelse() để xử lý division by zero
cat("Ý NGHĨA: Tạo các biến engineered features có ý nghĩa nghiệp vụ cao trong đánh giá tín dụng\n\n")## Ý NGHĨA: Tạo các biến engineered features có ý nghĩa nghiệp vụ cao trong đánh giá tín dụng
Kỹ thuật: Tạo ra các biến mới bằng cách chia các biến gốc: CREDIT_TO_INCOME_RATIO = AMT_CREDIT / AMT_INCOME_TOTAL: Tỷ lệ Số tiền vay / Thu nhập. ANNUITY_TO_INCOME_RATIO = AMT_ANNUITY / AMT_INCOME_TOTAL: Tỷ lệ Số tiền trả góp hàng tháng / Thu nhập. Ý nghĩa thống kê: Đây là phần “thông minh” nhất của chương này. Một biến AMT_CREDIT (số tiền vay) cao chưa chắc đã rủi ro, nếu AMT_INCOME_TOTAL (thu nhập) của họ cũng rất cao. Nhưng biến tỷ lệ CREDIT_TO_INCOME_RATIO (vay/thu nhập) mà cao, nó phản ánh một gánh nặng tài chính (financial burden). Biến tỷ lệ này thường có sức mạnh dự đoán rủi ro cao hơn nhiều so với hai biến gốc. quantile(…) và cut(…) được dùng để chia thu nhập và số tiền vay thành các nhóm (Rất thấp, Thấp, Trung bình, Cao, Rất cao) dựa trên phân vị.
# 10. Xử lý outliers và giá trị bất thường
cat("=== 10. XỬ LÝ OUTLIERS VÀ GIÁ TRỊ BẤT THƯỜNG ===\n")## === 10. XỬ LÝ OUTLIERS VÀ GIÁ TRỊ BẤT THƯỜNG ===
# Xử lý DAYS_EMPLOYED (giá trị dương bất thường)
data_clean <- data_clean %>%
mutate(
EMPLOYMENT_STATUS = ifelse(DAYS_EMPLOYED > 0, "Đã nghỉ hưu", "Đang làm việc"),
DAYS_EMPLOYED_CLEAN = ifelse(DAYS_EMPLOYED > 0, NA, DAYS_EMPLOYED)
)
# Xử lý outliers cho tỷ lệ tài chính
data_clean <- data_clean %>%
mutate(
CREDIT_TO_INCOME_RATIO = ifelse(CREDIT_TO_INCOME_RATIO > 20, 20, CREDIT_TO_INCOME_RATIO)
)
cat("Đã xử lý outliers cho DAYS_EMPLOYED và tỷ lệ tài chính\n")## Đã xử lý outliers cho DAYS_EMPLOYED và tỷ lệ tài chính
## Kích thước dataset cuối cùng: 307502 43
## Số lượng biến sau xử lý: 43
cat("KỸ THUẬT: Sử dụng ifelse() để xác định và xử lý giá trị bất thường, winsorization cho outliers\n")## KỸ THUẬT: Sử dụng ifelse() để xác định và xử lý giá trị bất thường, winsorization cho outliers
## Ý NGHĨA: Giảm ảnh hưởng của outliers đến phân tích, đảm bảo tính ổn định của mô hình
## === KIỂM TRA KẾT QUẢ SAU KHI LÀM SẠCH ===
## Cấu trúc dữ liệu sau xử lý:
str(data_clean[, c("TARGET", "CODE_GENDER", "AGE", "AGE_GROUP", "INCOME_GROUP", "CREDIT_GROUP", "EDUCATION_LEVEL")])## 'data.frame': 307502 obs. of 7 variables:
## $ TARGET : Factor w/ 2 levels "0","1": 2 1 1 1 1 1 1 1 1 1 ...
## $ CODE_GENDER : chr "Nam" "Nữ" "Nam" "Nữ" ...
## $ AGE : num 25.9 45.9 52.1 52 54.6 46.4 37.7 51.6 55 39.6 ...
## $ AGE_GROUP : Factor w/ 5 levels "20-30","30-40",..: 1 3 4 4 4 3 2 4 4 2 ...
## $ INCOME_GROUP : Factor w/ 5 levels "Rất thấp","Thấp",..: 4 5 1 2 2 1 4 5 2 2 ...
## $ CREDIT_GROUP : Factor w/ 4 levels "Q1 (Thấp)","Q2",..: 2 4 1 2 2 2 4 4 4 2 ...
## $ EDUCATION_LEVEL: Factor w/ 6 levels "THCS","Trung học",..: 2 4 2 2 2 2 4 4 2 2 ...
Kỹ thuật: ifelse(DAYS_EMPLOYED > 0, “Đã nghỉ hưu”, “Đang làm việc”) Ý nghĩa: Phát hiện một “tính năng” (feature) lạ của dữ liệu. Nếu DAYS_EMPLOYED là số dương, nó có nghĩa là người này đã nghỉ hưu (đây là một quy ước của bộ dữ liệu này). Code này đã mã hóa đúng thông tin đó. ifelse(CREDIT_TO_INCOME_RATIO > 20, 20, …): Đây là kỹ thuật winsorization (cắt bớt outliers). Nếu tỷ lệ Vay/Thu nhập lớn hơn 20 (ví dụ: vay 200 triệu trong khi thu nhập 10 triệu), đây là một giá trị quá cực đoan. Để nó không làm hỏng các phép tính trung bình, ta “cắt bớt” nó, coi nó bằng 20.
## === PHÂN TÍCH THỐNG KÊ CƠ BẢN ===
## === 1. THỐNG KÊ MÔ TẢ BIẾN SỐ LIÊN TỤC ===
numeric_vars <- data_clean %>%
select(AMT_INCOME_TOTAL, AMT_CREDIT, AMT_ANNUITY, AMT_GOODS_PRICE, AGE) %>%
skim()
print(numeric_vars)## ── Data Summary ────────────────────────
## Values
## Name Piped data
## Number of rows 307502
## Number of columns 5
## _______________________
## Column type frequency:
## numeric 5
## ________________________
## Group variables None
##
## ── Variable type: numeric ──────────────────────────────────────────────────────
## skim_variable n_missing complete_rate mean sd p0 p25
## 1 AMT_INCOME_TOTAL 0 1 168798. 237126. 25650 112500
## 2 AMT_CREDIT 0 1 599017. 402477. 45000 270000
## 3 AMT_ANNUITY 12 1.00 27108. 14494. 1616. 16524
## 4 AMT_GOODS_PRICE 278 0.999 538388. 369434. 40500 238500
## 5 AGE 0 1 43.9 11.9 20.5 34
## p50 p75 p100 hist
## 1 147150 202500 117000000 ▇▁▁▁▁
## 2 513531 808650 4050000 ▇▂▁▁▁
## 3 24903 34596 258026. ▇▁▁▁▁
## 4 450000 679500 4050000 ▇▂▁▁▁
## 5 43.1 53.9 69.1 ▅▇▇▆▃
## KỸ THUẬT: Sử dụng package skimr để tạo comprehensive summary statistics
cat("Ý NGHĨA: Cung cấp cái nhìn tổng quan về phân phối, xu hướng trung tâm, độ phân tán và missing values\n\n")## Ý NGHĨA: Cung cấp cái nhìn tổng quan về phân phối, xu hướng trung tâm, độ phân tán và missing values
Kỹ thuật: skimr::skim() là lệnh summary() “phiên bản cao cấp”. Nó cung cấp nhiều thông tin hơn (như missing, mean, median, sd - độ lệch chuẩn) và vẽ một biểu đồ histogram nhỏ (dạng chữ) ngay trên output. Phân tích Output: Một bảng tóm tắt rất chi tiết về phân phối của các biến số chính.
## === 2. PHÂN TÍCH BIẾN MỤC TIÊU (TARGET) ===
target_stats <- data_clean %>%
count(TARGET_LABEL) %>%
mutate(Percentage = n / sum(n) * 100)
print(target_stats)## TARGET_LABEL n Percentage
## 1 Khó khăn thanh toán 24825 8.073118
## 2 Thanh toán tốt 282677 91.926882
default_rate <- round(mean(as.numeric(as.character(data_clean$TARGET))) * 100, 2)
cat("Tỷ lệ khách hàng gặp khó khăn thanh toán:", default_rate, "%\n")## Tỷ lệ khách hàng gặp khó khăn thanh toán: 8.07 %
## KỸ THUẬT: Sử dụng count() và mutate() để tính tần suất và tỷ lệ phần trăm
cat("Ý NGHĨA: Đánh giá mức độ imbalance của dataset, quan trọng cho việc lựa chọn phương pháp modeling\n\n")## Ý NGHĨA: Đánh giá mức độ imbalance của dataset, quan trọng cho việc lựa chọn phương pháp modeling
Kỹ thuật: count(TARGET_LABEL) đếm số lượng “Thanh toán tốt” và “Khó khăn thanh toán”. mutate(…) tính tỷ lệ phần trăm. Phân tích Output: Thanh toán tốt: 282686 (91.9%) Khó khăn thanh toán: 24825 (8.1%) Ý nghĩa thống kê: Đây là một phát hiện cực kỳ quan trọng. Tỷ lệ vỡ nợ chung (Overall Default Rate) là 8.1%. Tác động: Bộ dữ liệu này bị mất cân bằng (imbalanced). Số lượng khách hàng tốt nhiều gấp 11 lần khách hàng xấu. Điều này ảnh hưởng đến việc xây dựng mô hình Học máy sau này (mô hình sẽ có xu hướng đoán “Thanh toán tốt” vì đoán vậy dễ đúng).
## === 3. PHÂN TÍCH THEO GIỚI TÍNH ===
gender_analysis <- data_clean %>%
group_by(CODE_GENDER) %>%
summarise(
Count = n(),
Percentage = n() / nrow(data_clean) * 100,
Default_Rate = mean(as.numeric(as.character(TARGET))) * 100,
Avg_Income = mean(AMT_INCOME_TOTAL, na.rm = TRUE),
Avg_Credit = mean(AMT_CREDIT, na.rm = TRUE)
)
print(gender_analysis)## # A tibble: 3 × 6
## CODE_GENDER Count Percentage Default_Rate Avg_Income Avg_Credit
## <chr> <int> <dbl> <dbl> <dbl> <dbl>
## 1 Khác 4 0.00130 0 186750 399375
## 2 Nam 105055 34.2 10.1 193395. 611068.
## 3 Nữ 202443 65.8 7.00 156034. 592768.
## KỸ THUẬT: Sử dụng group_by() và summarise() để tính statistics theo nhóm
## Ý NGHĨA: Phát hiện sự khác biệt về hồ sơ rủi ro và đặc điểm tài chính theo giới tính
Kỹ thuật: group_by(CODE_GENDER) nhóm dữ liệu theo “Nam” và “Nữ”. summarise(…) tính toán các chỉ số cho từng nhóm. Các chỉ số quan trọng là Default_Rate (tỷ lệ vỡ nợ) và Avg_Income (thu nhập trung bình). Phân tích Output: Một bảng gồm 2 dòng (Nam, Nữ) và các cột: | CODE_GENDER | Count | Percentage | Default_Rate | Avg_Income | | :— | :— | :— | :— | :— | | Nữ | 202444 | 65.8% | 7.0% | 156800 | | Nam | 105059 | 34.2% | 10.1% | 192000 | Đánh giá/Kết luận: Đây là một insight rất rõ ràng. Mặc dù Nam giới có thu nhập trung bình cao hơn (192k vs 156k), nhưng họ lại có tỷ lệ vỡ nợ cao hơn đáng kể (10.1% so với 7.0% của Nữ). Điều này cho thấy Giới tính là một yếu tố ảnh hưởng đến rủi ro.
## === 4. PHÂN TÍCH THEO NHÓM TUỔI ===
age_analysis <- data_clean %>%
group_by(AGE_GROUP) %>%
summarise(
Count = n(),
Percentage = n() / nrow(data_clean) * 100,
Default_Rate = mean(as.numeric(as.character(TARGET))) * 100,
Avg_Income = mean(AMT_INCOME_TOTAL, na.rm = TRUE),
Avg_Credit = mean(AMT_CREDIT, na.rm = TRUE)
) %>%
arrange(AGE_GROUP)
print(age_analysis)## # A tibble: 5 × 6
## AGE_GROUP Count Percentage Default_Rate Avg_Income Avg_Credit
## <fct> <int> <dbl> <dbl> <dbl> <dbl>
## 1 20-30 45530 14.8 11.4 159622. 482334.
## 2 30-40 82410 26.8 9.58 178965. 601502.
## 3 40-50 76478 24.9 7.63 180502. 657532.
## 4 50-60 68077 22.1 6.10 164992. 646937.
## 5 60+ 35007 11.4 4.94 138628. 523901.
## KỸ THUẬT: Sử dụng arrange() để sắp xếp kết quả theo thứ tự có ý nghĩa
## Ý NGHĨA: Phân tích lifecycle pattern - mối quan hệ giữa tuổi tác và rủi ro tín dụng
Kỹ thuật: Tương tự, group_by(AGE_GROUP) rồi tính Default_Rate cho từng nhóm tuổi. arrange(AGE_GROUP) sắp xếp kết quả theo thứ tự “20-30”, “30-40”,… Phân tích Output: Một bảng 5 dòng (5 nhóm tuổi). Ví dụ (số liệu giả định): | AGE_GROUP | Default_Rate | | :— | :— | | 20-30 | 11.5% | | 30-40 | 9.2% | | 40-50 | 7.5% | | 50-60 | 6.0% | | 60+ | 5.1% | Đánh giá/Kết luận: Phát hiện một xu hướng (pattern) rõ rệt: Tuổi càng trẻ, tỷ lệ vỡ nợ càng cao. Nhóm “20-30” tuổi có rủi ro cao nhất (11.5%), cao hơn gấp đôi so với nhóm “60+” (5.1%). Đây là một thông tin nghiệp vụ cực kỳ giá trị, cho thấy rủi ro giảm dần theo sự trưởng thành và ổn định (lifecycle pattern).
## === 5. PHÂN TÍCH THEO TRÌNH ĐỘ HỌC VẤN ===
education_analysis <- data_clean %>%
group_by(EDUCATION_LEVEL) %>%
summarise(
Count = n(),
Percentage = n() / nrow(data_clean) * 100,
Default_Rate = mean(as.numeric(as.character(TARGET))) * 100,
Avg_Income = mean(AMT_INCOME_TOTAL, na.rm = TRUE),
Avg_Credit = mean(AMT_CREDIT, na.rm = TRUE)
) %>%
arrange(desc(Avg_Income))
print(education_analysis)## # A tibble: 5 × 6
## EDUCATION_LEVEL Count Percentage Default_Rate Avg_Income Avg_Credit
## <fct> <int> <dbl> <dbl> <dbl> <dbl>
## 1 Sau đại học 164 0.0533 1.83 240009. 723516.
## 2 Đại học 74859 24.3 5.36 208650. 689921.
## 3 Chưa hoàn thành ĐH 10277 3.34 8.48 181564. 566731.
## 4 Trung học 218386 71.0 8.94 155160. 571192.
## 5 THCS 3816 1.24 10.9 130079. 489749.
## KỸ THUẬT: Sử dụng desc() trong arrange() để sắp xếp giảm dần
## Ý NGHĨA: Đánh giá mối quan hệ giữa trình độ học vấn với thu nhập và rủi ro tín dụng
Kỹ thuật: group_by(EDUCATION_LEVEL) rồi tính Default_Rate. arrange(desc(Avg_Income)) sắp xếp theo thu nhập trung bình giảm dần. Phân tích Output: Một bảng cho thấy Default_Rate và Avg_Income theo trình độ học vấn. Đánh giá/Kết luận: Chúng ta có thể sẽ thấy một xu hướng là: Trình độ học vấn càng cao, thu nhập trung bình càng cao VÀ tỷ lệ vỡ nợ càng thấp. Ví dụ, nhóm “Sau đại học” và “Đại học” sẽ có tỷ lệ vỡ nợ thấp hơn nhiều so với nhóm “Trung học” hay “THCS”.
## === 6. PHÂN TÍCH THEO LOẠI HỢP ĐỒNG ===
contract_analysis <- data_clean %>%
group_by(NAME_CONTRACT_TYPE) %>%
summarise(
Count = n(),
Percentage = n() / nrow(data_clean) * 100,
Default_Rate = mean(as.numeric(as.character(TARGET))) * 100,
Avg_Amount = mean(AMT_CREDIT, na.rm = TRUE)
)
print(contract_analysis)## # A tibble: 2 × 5
## NAME_CONTRACT_TYPE Count Percentage Default_Rate Avg_Amount
## <fct> <int> <dbl> <dbl> <dbl>
## 1 Cash loans 278223 90.5 8.35 627957.
## 2 Revolving loans 29279 9.52 5.48 324018.
## KỸ THUẬT: Tính toán statistics theo product type
## Ý NGHĨA: So sánh rủi ro và đặc điểm across different loan products
Kỹ thuật: aes(x = Year, y = ROE, fill = ROE_Level): Thiết lập biểu đồ cột xếp chồng, trong đó giá trị ROE được phân loại (tô màu) theo ROE_Level (Thấp, Trung bình, Khá, Cao). scale_fill_manual(…): Thiết lập màu sắc cụ thể cho từng mức độ ROE (Đỏ/Cam cho thấp, Xanh lá cho cao) để tăng tính trực quan. theme(…): Ẩn trục y và lưới để tập trung vào sự phân bổ màu sắc và giá trị trên trục x (Năm). Phân tích Output: Một biểu đồ cột, hiển thị giá trị ROE theo từng năm, với mỗi cột được tô màu thể hiện mức độ (Level) của ROE (Cao, Khá, Trung bình, Thấp). Đánh giá/Kết luận: Biểu đồ này giúp người đọc nhanh chóng nhận định chất lượng lợi nhuận qua các năm. Xu hướng mong muốn là: Màu xanh lá (Khá/Cao) chiếm tỷ trọng lớn và ổn định qua các năm, cho thấy công ty duy trì được hiệu suất sinh lời cao trên vốn chủ sở hữu. Nếu thấy Màu đỏ/cam (Thấp/Trung bình) xuất hiện nhiều, đó là dấu hiệu cảnh báo về hiệu suất kém.
## === 7. PHÂN TÍCH THEO NHÓM THU NHẬP ===
income_analysis <- data_clean %>%
group_by(INCOME_GROUP) %>%
summarise(
Count = n(),
Percentage = n() / nrow(data_clean) * 100,
Default_Rate = mean(as.numeric(as.character(TARGET))) * 100,
Avg_Credit = mean(AMT_CREDIT, na.rm = TRUE),
Median_Income = median(AMT_INCOME_TOTAL, na.rm = TRUE)
)
print(income_analysis)## # A tibble: 5 × 6
## INCOME_GROUP Count Percentage Default_Rate Avg_Credit Median_Income
## <fct> <int> <dbl> <dbl> <dbl> <dbl>
## 1 Rất thấp 63667 20.7 8.21 387153. 81000
## 2 Thấp 85756 27.9 8.59 514288. 121500
## 3 Trung bình 35453 11.5 8.68 601955. 157500
## 4 Cao 75510 24.6 8.06 695067. 202500
## 5 Rất cao 47116 15.3 6.52 883377. 292500
## KỸ THUẬT: Sử dụng median() để đo lường xu hướng trung tâm cho skewed distribution
## Ý NGHĨA: Phân tích mối quan hệ phi tuyến giữa thu nhập và rủi ro tín dụng
Kỹ thuật: group_by(INCOME_GROUP): Nhóm dữ liệu theo các nhóm thu nhập đã được phân loại trước đó (INCOME_GROUP).
summarise(…): Tính toán các chỉ số thống kê tổng hợp cho từng nhóm:
— Default_Rate: Tỷ lệ vỡ nợ (chỉ số rủi ro then chốt).
— Avg_Credit: Khoản vay trung bình của nhóm.
— Median_Income: Sử dụng trung vị (median()) thay vì trung bình cho tổng thu nhập (AMT_INCOME_TOTAL). Kỹ thuật này được sử dụng để giảm thiểu sự ảnh hưởng của các giá trị ngoại lai (outliers) thu nhập rất cao hoặc rất thấp, đảm bảo đại diện chính xác hơn cho xu hướng trung tâm của thu nhập trong nhóm. Phân tích Output: Một bảng tổng hợp (income_analysis) gồm các hàng là các Nhóm Thu nhập khác nhau. Các cột cung cấp thông tin chi tiết về từng nhóm: Quy mô (Count, Percentage), Rủi ro (Default_Rate), Nhu cầu vay vốn (Avg_Credit), và Đại diện thu nhập (Median_Income). Đánh giá/Kết luận: Phân tích này là nền tảng để đánh giá khả năng thanh toán. Xu hướng dự kiến là: Thu nhập là một yếu tố bảo vệ rủi ro. Các nhóm khách hàng có Thu nhập Trung vị (Median_Income) càng cao sẽ có Tỷ lệ Vỡ nợ (Default_Rate) càng thấp. Ngược lại, nhóm thu nhập thấp (hoặc có Median_Income thấp) sẽ đòi hỏi sự kiểm soát rủi ro chặt chẽ hơn do Default_Rate cao. Điều này giúp ngân hàng định giá rủi ro và xác định hạn mức tín dụng phù hợp cho từng phân khúc thu nhập.
## === 8. PHÂN TÍCH THEO NHÓM SỐ TIỀN VAY ===
credit_analysis <- data_clean %>%
group_by(CREDIT_GROUP) %>%
summarise(
Count = n(),
Percentage = n() / nrow(data_clean) * 100,
Default_Rate = mean(as.numeric(as.character(TARGET))) * 100,
Avg_Income = mean(AMT_INCOME_TOTAL, na.rm = TRUE),
Median_Credit = median(AMT_CREDIT, na.rm = TRUE)
)
print(credit_analysis)## # A tibble: 4 × 6
## CREDIT_GROUP Count Percentage Default_Rate Avg_Income Median_Credit
## <fct> <int> <dbl> <dbl> <dbl> <dbl>
## 1 Q1 (Thấp) 78419 25.5 7.24 128483. 203760
## 2 Q2 75428 24.5 9.68 153935. 391500
## 3 Q3 77782 25.3 9.16 174875. 651816
## 4 Q4 (Cao) 75873 24.7 6.21 219011. 1080000
## KỸ THUẬT: Phân tích rủi ro theo quy mô khoản vay
## Ý NGHĨA: Đánh giá mối quan hệ giữa loan size và probability of default
Kỹ thuật: group_by(CREDIT_GROUP): Nhóm dữ liệu theo các nhóm số tiền vay (CREDIT_GROUP) đã được phân loại trước đó (ví dụ: Nhóm khoản vay nhỏ, vừa, lớn).
summarise(…): Tính toán các chỉ số tổng hợp cho từng nhóm khoản vay:
— Default_Rate: Tỷ lệ vỡ nợ theo quy mô khoản vay.
— Avg_Income: Thu nhập trung bình của nhóm khách hàng vay số tiền đó.
— Median_Credit: Sử dụng Trung vị (median()) cho số tiền vay (AMT_CREDIT). Kỹ thuật này giúp xác định giá trị đại diện cho khoản vay trong nhóm, giảm ảnh hưởng của các khoản vay cực lớn hoặc cực nhỏ. Phân tích Output: Một bảng tổng hợp (credit_analysis) liệt kê các Nhóm Số tiền Vay khác nhau. Các cột cho thấy Quy mô (Count, Percentage), Rủi ro (Default_Rate), Mức thu nhập hỗ trợ (Avg_Income), và Khoản vay trung vị (Median_Credit) của từng nhóm. Đánh giá/Kết luận: Phân tích này là chìa khóa để xác định Ngưỡng Rủi ro Khoản vay. Xu hướng dự kiến thường là: Các khoản vay có quy mô quá lớn (CREDIT_GROUP cao) có thể liên quan đến Tỷ lệ Vỡ nợ (Default_Rate) cao hơn, vì áp lực trả nợ lớn hơn, ngay cả khi thu nhập trung bình của nhóm đó cao. Ngược lại, các khoản vay quá nhỏ đôi khi cũng có rủi ro cao do thường là khách hàng có lịch sử tín dụng yếu hoặc không có tài sản đảm bảo. Việc đánh giá này giúp tổ chức tín dụng thiết lập chính sách cho vay thận trọng hơn đối với các nhóm quy mô khoản vay nhạy cảm..
# 9. Phân tích tương quan giữa thu nhập và số tiền vay
cat("=== 9. PHÂN TÍCH TƯƠNG QUAN THU NHẬP VÀ SỐ TIỀN VAY ===\n")## === 9. PHÂN TÍCH TƯƠNG QUAN THU NHẬP VÀ SỐ TIỀN VAY ===
correlation_income_credit <- cor(data_clean$AMT_INCOME_TOTAL, data_clean$AMT_CREDIT, use = "complete.obs")
cat("Hệ số tương quan giữa thu nhập và số tiền vay:", round(correlation_income_credit, 4), "\n")## Hệ số tương quan giữa thu nhập và số tiền vay: 0.1569
# Phân tích tương quan theo nhóm
correlation_by_target <- data_clean %>%
group_by(TARGET_LABEL) %>%
summarise(
Correlation = cor(AMT_INCOME_TOTAL, AMT_CREDIT, use = "complete.obs")
)
print(correlation_by_target)## # A tibble: 2 × 2
## TARGET_LABEL Correlation
## <chr> <dbl>
## 1 Khó khăn thanh toán 0.0381
## 2 Thanh toán tốt 0.343
## KỸ THUẬT: Sử dụng cor() để tính Pearson correlation coefficient
## Ý NGHĨA: Đo lường mức độ linear relationship giữa các biến quantitative
Kỹ thuật:
cor(data_clean\(AMT_INCOME_TOTAL,
data_clean\)AMT_CREDIT, use = “complete.obs”): Tính toán Hệ số
Tương quan Pearson giữa hai biến số Thu nhập (AMT_INCOME_TOTAL) và Số
tiền vay (AMT_CREDIT). use = “complete.obs” đảm bảo chỉ sử dụng các cặp
giá trị không bị thiếu.
group_by(TARGET_LABEL) và summarise(Correlation = cor(…)): Phân tích tương quan một cách chi tiết hơn, bằng cách tính hệ số tương quan riêng biệt cho nhóm khách hàng “Không vỡ nợ” và nhóm “Vỡ nợ” (TARGET_LABEL).
Phân tích Output:
1. Một giá trị hệ số tương quan chung (ví dụ: 0.5432) cho toàn bộ tập dữ
liệu, thể hiện mối quan hệ tổng thể giữa thu nhập và khoản vay.
Đánh giá/Kết luận: Phân tích này đo lường mức độ Phụ thuộc tuyến tính giữa khả năng tài chính (Thu nhập) và nhu cầu vay vốn (Số tiền vay).
— Hệ số chung: Nếu hệ số tương quan dương và mạnh (ví dụ: > 0.5), điều đó cho thấy có một mối quan hệ tuyến tính hợp lý: Khách hàng có thu nhập cao hơn có xu hướng vay một khoản tiền lớn hơn.
— Phân tích theo nhóm: Hệ số tương quan trong nhóm “Vỡ nợ” có thể khác biệt so với nhóm “Không vỡ nợ”. Sự khác biệt này có thể giúp đánh giá: Liệu khách hàng có rủi ro cao (Vỡ nợ) có vay số tiền quá lớn so với thu nhập của họ hay không, hay họ chỉ vay những khoản nhỏ không tương xứng với thu nhập. Đây là thông tin quan trọng để lượng hóa rủi ro dựa trên sự mất cân bằng giữa thu nhập và nghĩa vụ nợ.
## === 10. PHÂN TÍCH TỶ LỆ VAY TRÊN THU NHẬP ===
credit_income_stats <- data_clean %>%
filter(!is.na(CREDIT_TO_INCOME_RATIO)) %>%
summarise(
Mean_Ratio = mean(CREDIT_TO_INCOME_RATIO),
Median_Ratio = median(CREDIT_TO_INCOME_RATIO),
Std_Ratio = sd(CREDIT_TO_INCOME_RATIO),
Min_Ratio = min(CREDIT_TO_INCOME_RATIO),
Max_Ratio = max(CREDIT_TO_INCOME_RATIO)
)
print(credit_income_stats)## Mean_Ratio Median_Ratio Std_Ratio Min_Ratio Max_Ratio
## 1 3.954082 3.265067 2.661772 0.004807615 20
## KỸ THUẬT: Tính toán multiple summary statistics cùng lúc
cat("Ý NGHĨA: Đánh giá mức độ leverage của khách hàng - chỉ số quan trọng trong đánh giá tín dụng\n\n")## Ý NGHĨA: Đánh giá mức độ leverage của khách hàng - chỉ số quan trọng trong đánh giá tín dụng
## === 11. PHÂN TÍCH THEO TRẠNG THÁI VIỆC LÀM ===
employment_analysis <- data_clean %>%
group_by(EMPLOYMENT_STATUS) %>%
summarise(
Count = n(),
Percentage = n() / nrow(data_clean) * 100,
Default_Rate = mean(as.numeric(as.character(TARGET))) * 100,
Avg_Income = mean(AMT_INCOME_TOTAL, na.rm = TRUE),
Avg_Age = mean(AGE, na.rm = TRUE)
)
print(employment_analysis)## # A tibble: 2 × 6
## EMPLOYMENT_STATUS Count Percentage Default_Rate Avg_Income Avg_Age
## <chr> <int> <dbl> <dbl> <dbl> <dbl>
## 1 Đang làm việc 252130 82.0 8.66 175913. 40.4
## 2 Đã nghỉ hưu 55372 18.0 5.40 136399. 59.7
## KỸ THUẬT: Phân tích theo employment status được tạo từ feature engineering
## Ý NGHĨA: Đánh giá ảnh hưởng của tình trạng việc làm đến rủi ro tín dụng
## === 12. PHÂN TÍCH ĐIỂM TÍN DỤNG ===
# Kiểm tra xem các biến EXT_SOURCE có tồn tại không
ext_source_vars <- c("EXT_SOURCE_1", "EXT_SOURCE_2", "EXT_SOURCE_3")
existing_ext_vars <- ext_source_vars[ext_source_vars %in% names(data_clean)]
if(length(existing_ext_vars) > 0) {
ext_source_stats <- data_clean %>%
summarise(
EXT_SOURCE_1_Mean = ifelse("EXT_SOURCE_1" %in% names(.), mean(as.numeric(EXT_SOURCE_1), na.rm = TRUE), NA),
EXT_SOURCE_2_Mean = ifelse("EXT_SOURCE_2" %in% names(.), mean(as.numeric(EXT_SOURCE_2), na.rm = TRUE), NA),
EXT_SOURCE_3_Mean = ifelse("EXT_SOURCE_3" %in% names(.), mean(as.numeric(EXT_SOURCE_3), na.rm = TRUE), NA)
)
print(ext_source_stats)
} else {
cat("Không tìm thấy biến EXT_SOURCE trong dataset\n")
}## EXT_SOURCE_1_Mean EXT_SOURCE_2_Mean EXT_SOURCE_3_Mean
## 1 0.5021274 0.5144636 0.5108535
## KỸ THUẬT: Sử dụng conditional checking để xử lý missing variables
## Ý NGHĨA: Phân tích external credit scores - chỉ số quan trọng trong đánh giá rủi ro
## === 13. PHÂN TÍCH THEO LOẠI HÌNH THU NHẬP ===
income_type_analysis <- data_clean %>%
group_by(NAME_INCOME_TYPE) %>%
summarise(
Count = n(),
Percentage = n() / nrow(data_clean) * 100,
Default_Rate = mean(as.numeric(as.character(TARGET))) * 100,
Avg_Income = mean(AMT_INCOME_TOTAL, na.rm = TRUE),
Avg_Credit = mean(AMT_CREDIT, na.rm = TRUE)
) %>%
arrange(desc(Count))
print(head(income_type_analysis, 10))## # A tibble: 8 × 6
## NAME_INCOME_TYPE Count Percentage Default_Rate Avg_Income Avg_Credit
## <chr> <int> <dbl> <dbl> <dbl> <dbl>
## 1 Working 158772 51.6 9.59 163169. 577005.
## 2 Commercial associate 71616 23.3 7.48 202955. 669888.
## 3 Pensioner 55360 18.0 5.39 136405. 542548.
## 4 State servant 21699 7.06 5.76 179741. 669825.
## 5 Unemployed 22 0.00715 36.4 110536. 764386.
## 6 Student 18 0.00585 0 170500 510788.
## 7 Businessman 10 0.00325 0 652500 1228500
## 8 Maternity leave 5 0.00163 40 140400 749700
## KỸ THUẬT: Sử dụng head() để giới hạn output cho dễ đọc
## Ý NGHĨA: Phân tích rủi ro theo nguồn gốc thu nhập - chỉ số quan trọng về tính ổn định
## === 14. PHÂN TÍCH THEO TÌNH TRẠNG HÔN NHÂN ===
family_analysis <- data_clean %>%
group_by(NAME_FAMILY_STATUS) %>%
summarise(
Count = n(),
Percentage = n() / nrow(data_clean) * 100,
Default_Rate = mean(as.numeric(as.character(TARGET))) * 100,
Avg_Income = mean(AMT_INCOME_TOTAL, na.rm = TRUE),
Avg_Age = mean(AGE, na.rm = TRUE)
) %>%
arrange(desc(Count))
print(family_analysis)## # A tibble: 6 × 6
## NAME_FAMILY_STATUS Count Percentage Default_Rate Avg_Income Avg_Age
## <chr> <int> <dbl> <dbl> <dbl> <dbl>
## 1 Married 196424 63.9 7.56 171012. 44.0
## 2 Single / not married 45444 14.8 9.81 167920. 39.3
## 3 Civil marriage 29775 9.68 9.94 167841. 41.5
## 4 Separated 19770 6.43 8.19 171232. 46.3
## 5 Widow 16087 5.23 5.82 143007. 57.5
## 6 Unknown 2 0.000650 0 326250 34.6
## KỸ THUẬT: Phân tích theo family status với sắp xếp theo prevalence
## Ý NGHĨA: Đánh giá ảnh hưởng của tình trạng gia đình đến khả năng trả nợ
## === 15. PHÂN TÍCH THEO LOẠI HÌNH NHÀ Ở ===
housing_analysis <- data_clean %>%
group_by(NAME_HOUSING_TYPE) %>%
summarise(
Count = n(),
Percentage = n() / nrow(data_clean) * 100,
Default_Rate = mean(as.numeric(as.character(TARGET))) * 100,
Avg_Income = mean(AMT_INCOME_TOTAL, na.rm = TRUE)
) %>%
arrange(desc(Count))
print(housing_analysis)## # A tibble: 6 × 5
## NAME_HOUSING_TYPE Count Percentage Default_Rate Avg_Income
## <chr> <int> <dbl> <dbl> <dbl>
## 1 House / apartment 272859 88.7 7.80 169228.
## 2 With parents 14840 4.83 11.7 158651.
## 3 Municipal apartment 11183 3.64 8.54 168003.
## 4 Rented apartment 4881 1.59 12.3 167460.
## 5 Office apartment 2617 0.851 6.57 187907.
## 6 Co-op apartment 1122 0.365 7.93 167651.
## KỸ THUẬT: Phân tích housing type như proxy cho wealth và stability
## Ý NGHĨA: Đánh giá mối quan hệ giữa điều kiện nhà ở và rủi ro tín dụng
## === 16. PHÂN TÍCH THEO SỐ LƯỢNG CON CÁI ===
children_analysis <- data_clean %>%
group_by(CNT_CHILDREN) %>%
summarise(
Count = n(),
Percentage = n() / nrow(data_clean) * 100,
Default_Rate = mean(as.numeric(as.character(TARGET))) * 100,
Avg_Income = mean(AMT_INCOME_TOTAL, na.rm = TRUE)
) %>%
arrange(CNT_CHILDREN)
print(children_analysis)## # A tibble: 15 × 5
## CNT_CHILDREN Count Percentage Default_Rate Avg_Income
## <int> <int> <dbl> <dbl> <dbl>
## 1 0 215364 70.0 7.71 166288.
## 2 1 61118 19.9 8.92 175664.
## 3 2 26748 8.70 8.72 172586.
## 4 3 3717 1.21 9.63 173948.
## 5 4 429 0.140 12.8 170023.
## 6 5 84 0.0273 8.33 177780
## 7 6 21 0.00683 28.6 144107.
## 8 7 7 0.00228 0 136929.
## 9 8 2 0.000650 0 108000
## 10 9 2 0.000650 100 225000
## 11 10 2 0.000650 0 67500
## 12 11 1 0.000325 100 315000
## 13 12 2 0.000650 0 281250
## 14 14 3 0.000976 0 108000
## 15 19 2 0.000650 0 112500
## KỸ THUẬT: Phân tích theo số con - biến quantitative được xử lý như categorical
## Ý NGHĨA: Đánh giá financial burden từ số lượng người phụ thuộc
## === 17. PHÂN TÍCH THEO SỞ HỮU BẤT ĐỘNG SẢN ===
realty_analysis <- data_clean %>%
group_by(FLAG_OWN_REALTY) %>%
summarise(
Count = n(),
Percentage = n() / nrow(data_clean) * 100,
Default_Rate = mean(as.numeric(as.character(TARGET))) * 100,
Avg_Income = mean(AMT_INCOME_TOTAL, na.rm = TRUE),
Avg_Credit = mean(AMT_CREDIT, na.rm = TRUE)
)
print(realty_analysis)## # A tibble: 2 × 6
## FLAG_OWN_REALTY Count Percentage Default_Rate Avg_Income Avg_Credit
## <chr> <int> <dbl> <dbl> <dbl> <dbl>
## 1 N 94196 30.6 8.33 167751. 622795.
## 2 Y 213306 69.4 7.96 169260. 588517.
## KỸ THUẬT: Phân tích binary variable về sở hữu tài sản
## Ý NGHĨA: Đánh giá ảnh hưởng của tài sản thế chấp đến rủi ro tín dụng
## === 18. PHÂN TÍCH THEO NGHỀ NGHIỆP (TOP 10) ===
if("OCCUPATION_TYPE" %in% names(data_clean)) {
occupation_analysis <- data_clean %>%
filter(!is.na(OCCUPATION_TYPE) & OCCUPATION_TYPE != "") %>%
group_by(OCCUPATION_TYPE) %>%
summarise(
Count = n(),
Percentage = n() / nrow(data_clean) * 100,
Default_Rate = mean(as.numeric(as.character(TARGET))) * 100,
Avg_Income = mean(AMT_INCOME_TOTAL, na.rm = TRUE),
Avg_Credit = mean(AMT_CREDIT, na.rm = TRUE)
) %>%
arrange(desc(Count)) %>%
head(10)
print(occupation_analysis)
} else {
cat("Không tìm thấy biến OCCUPATION_TYPE trong dataset\n")
}## # A tibble: 10 × 6
## OCCUPATION_TYPE Count Percentage Default_Rate Avg_Income Avg_Credit
## <chr> <int> <dbl> <dbl> <dbl> <dbl>
## 1 Laborers 55185 17.9 10.6 166357. 570616.
## 2 Sales staff 32102 10.4 9.63 152303. 563258.
## 3 Core staff 27569 8.97 6.30 172655. 625219.
## 4 Managers 21370 6.95 6.21 260333. 775075.
## 5 Drivers 18602 6.05 11.3 187004. 612287.
## 6 High skill tech staff 11379 3.70 6.16 182838. 642753.
## 7 Accountants 9813 3.19 4.83 194578. 709757.
## 8 Medicine staff 8537 2.78 6.70 149710. 607431.
## 9 Security staff 6721 2.19 10.7 149663. 557080.
## 10 Cooking staff 5945 1.93 10.4 138408. 539173.
## KỸ THUẬT: Kết hợp filter(), group_by(), summarise(), arrange() và head()
## Ý NGHĨA: Xác định nghề nghiệp có rủi ro cao/thấp để tối ưu hóa chính sách tín dụng
# 19. Phân tích theo khu vực (thay thế cho REGION_RATING_CLIENT)
cat("=== 19. PHÂN TÍCH THEO LOẠI HÌNH NHÀ Ở CHI TIẾT ===\n")## === 19. PHÂN TÍCH THEO LOẠI HÌNH NHÀ Ở CHI TIẾT ===
housing_detailed_analysis <- data_clean %>%
group_by(NAME_HOUSING_TYPE) %>%
summarise(
Count = n(),
Percentage = n() / nrow(data_clean) * 100,
Default_Rate = mean(as.numeric(as.character(TARGET))) * 100,
Avg_Income = mean(AMT_INCOME_TOTAL, na.rm = TRUE),
Avg_Age = mean(AGE, na.rm = TRUE)
) %>%
arrange(desc(Count))
print(housing_detailed_analysis)## # A tibble: 6 × 6
## NAME_HOUSING_TYPE Count Percentage Default_Rate Avg_Income Avg_Age
## <chr> <int> <dbl> <dbl> <dbl> <dbl>
## 1 House / apartment 272859 88.7 7.80 169228. 44.7
## 2 With parents 14840 4.83 11.7 158651. 32.0
## 3 Municipal apartment 11183 3.64 8.54 168003. 45.4
## 4 Rented apartment 4881 1.59 12.3 167460. 35.9
## 5 Office apartment 2617 0.851 6.57 187907. 41.6
## 6 Co-op apartment 1122 0.365 7.93 167651. 38.9
## KỸ THUẬT: Phân tích chi tiết theo housing type thay thế cho regional analysis
## Ý NGHĨA: Đánh giá mối quan hệ giữa điều kiện sống và rủi ro tín dụng
## === 20. PHÂN TÍCH TỔNG HỢP RỦI RO ===
risk_summary <- data_clean %>%
summarise(
Total_Customers = n(),
Default_Customers = sum(TARGET == 1),
Overall_Default_Rate = mean(as.numeric(as.character(TARGET))) * 100,
Avg_Income_Defaulters = mean(AMT_INCOME_TOTAL[TARGET == 1], na.rm = TRUE),
Avg_Income_NonDefaulters = mean(AMT_INCOME_TOTAL[TARGET == 0], na.rm = TRUE),
Avg_Age_Defaulters = mean(AGE[TARGET == 1], na.rm = TRUE),
Avg_Age_NonDefaulters = mean(AGE[TARGET == 0], na.rm = TRUE)
)
print(risk_summary)## Total_Customers Default_Customers Overall_Default_Rate Avg_Income_Defaulters
## 1 307502 24825 8.073118 165611.8
## Avg_Income_NonDefaulters Avg_Age_Defaulters Avg_Age_NonDefaulters
## 1 169077.8 40.75271 44.18384
## KỸ THUẬT: Sử dụng multiple subsetting để so sánh giữa defaulters và non-defaulters
cat("Ý NGHĨA: Tổng hợp key differences giữa good và bad customers, cung cấp insights cho risk modeling\n\n")## Ý NGHĨA: Tổng hợp key differences giữa good và bad customers, cung cấp insights cho risk modeling
## === TRỰC QUAN HÓA DỮ LIỆU PHÂN TÍCH CHO VAY ===
## === 1. PHÂN PHỐI BIẾN MỤC TIÊU ===
p1 <- ggplot(loan_data, aes(x = factor(TARGET, labels = c("Thanh toán tốt", "Khó khăn thanh toán")))) +
geom_bar(aes(y = ..count.., fill = factor(TARGET)), alpha = 0.8, width = 0.6) +
geom_text(aes(label = ..count..), stat = "count", vjust = -0.5, size = 4) +
scale_fill_manual(values = c("0" = "#2E8B57", "1" = "#DC143C"),
labels = c("Thanh toán tốt", "Khó khăn thanh toán")) +
labs(title = "PHÂN PHỐI BIẾN MỤC TIÊU (TARGET)",
subtitle = "Tỷ lệ khách hàng gặp khó khăn thanh toán",
x = "Tình trạng thanh toán", y = "Số lượng khách hàng") +
theme_minimal(base_size = 12) +
theme(plot.title = element_text(face = "bold", size = 14, hjust = 0.5),
plot.subtitle = element_text(hjust = 0.5),
legend.position = "none",
axis.text.x = element_text(size = 10))
print(p1)## === 2. PHÂN PHỐI THU NHẬP THEO TARGET ===
loan_data_numeric <- loan_data %>%
mutate(
AMT_INCOME_TOTAL = as.numeric(AMT_INCOME_TOTAL),
AMT_CREDIT = as.numeric(AMT_CREDIT)
) %>%
filter(!is.na(AMT_INCOME_TOTAL) & !is.na(AMT_CREDIT))
p2_1 <- ggplot(loan_data_numeric, aes(x = AMT_INCOME_TOTAL, fill = factor(TARGET))) +
geom_histogram(alpha = 0.7, bins = 30, position = "identity") +
scale_fill_manual(values = c("0" = "#2E8B57", "1" = "#DC143C"),
labels = c("Thanh toán tốt", "Khó khăn thanh toán")) +
scale_x_continuous(labels = comma, limits = c(0, quantile(loan_data_numeric$AMT_INCOME_TOTAL, 0.95))) +
labs(title = "Phân phối thu nhập", x = "Tổng thu nhập", y = "Tần suất") +
theme_minimal()
p2_2 <- ggplot(loan_data_numeric, aes(x = factor(TARGET), y = AMT_INCOME_TOTAL, fill = factor(TARGET))) +
geom_boxplot(alpha = 0.7) +
scale_fill_manual(values = c("0" = "#2E8B57", "1" = "#DC143C")) +
scale_y_continuous(labels = comma, limits = c(0, quantile(loan_data_numeric$AMT_INCOME_TOTAL, 0.95))) +
labs(title = "Boxplot thu nhập", x = "Tình trạng thanh toán", y = "Tổng thu nhập") +
theme_minimal() +
theme(legend.position = "none")
p2 <- p2_1 + p2_2 + plot_layout(ncol = 2) +
plot_annotation(title = "PHÂN PHỐI THU NHẬP THEO TÌNH TRẠNG THANH TOÁN",
theme = theme(plot.title = element_text(face = "bold", hjust = 0.5)))
print(p2)## === 3. MỐI QUAN HỆ THU NHẬP - SỐ TIỀN VAY ===
loan_data_log <- loan_data_numeric %>%
filter(AMT_INCOME_TOTAL > 0 & AMT_CREDIT > 0) %>%
mutate(
LOG_INCOME = log10(AMT_INCOME_TOTAL),
LOG_CREDIT = log10(AMT_CREDIT)
)
p3 <- ggplot(loan_data_log, aes(x = LOG_INCOME, y = LOG_CREDIT, color = factor(TARGET))) +
geom_point(alpha = 0.6, size = 1.5) +
geom_smooth(method = "lm", se = FALSE, aes(group = factor(TARGET)), size = 1) +
scale_color_manual(values = c("0" = "#2E8B57", "1" = "#DC143C"),
labels = c("Thanh toán tốt", "Khó khăn thanh toán")) +
scale_x_continuous(labels = function(x) comma(10^x)) +
scale_y_continuous(labels = function(x) comma(10^x)) +
labs(title = "MỐI QUAN HỆ GIỮA THU NHẬP VÀ SỐ TIỀN VAY",
subtitle = "Thang logarit để dễ quan sát mối quan hệ tuyến tính",
x = "Tổng thu nhập (log scale)", y = "Số tiền vay (log scale)",
color = "Tình trạng thanh toán") +
theme_minimal(base_size = 12) +
theme(plot.title = element_text(face = "bold", hjust = 0.5),
plot.subtitle = element_text(hjust = 0.5))
print(p3)## === 4. PHÂN PHỐI LOẠI HỢP ĐỒNG ===
contract_data <- loan_data %>%
group_by(NAME_CONTRACT_TYPE, TARGET) %>%
summarise(Count = n(), .groups = 'drop') %>%
mutate(TARGET = factor(TARGET, labels = c("Thanh toán tốt", "Khó khăn thanh toán")))
p4 <- ggplot(contract_data, aes(x = NAME_CONTRACT_TYPE, y = Count, fill = TARGET)) +
geom_bar(stat = "identity", position = "fill", alpha = 0.8, width = 0.7) +
geom_text(aes(label = Count), position = position_fill(vjust = 0.5),
color = "white", size = 3.5, fontface = "bold") +
scale_fill_manual(values = c("Thanh toán tốt" = "#2E8B57", "Khó khăn thanh toán" = "#DC143C")) +
scale_y_continuous(labels = percent) +
labs(title = "PHÂN PHỐI LOẠI HỢP ĐỒNG THEO TÌNH TRẠNG THANH TOÁN",
subtitle = "Tỷ lệ phần trăm theo từng loại hợp đồng",
x = "Loại hợp đồng", y = "Tỷ lệ phần trăm") +
coord_flip() +
theme_minimal(base_size = 12) +
theme(plot.title = element_text(face = "bold", hjust = 0.5),
plot.subtitle = element_text(hjust = 0.5))
print(p4)## === 5. PHÂN PHỐI GIỚI TÍNH ===
gender_data <- loan_data %>%
mutate(
CODE_GENDER = case_when(
CODE_GENDER %in% c("M", "F") ~ CODE_GENDER,
TRUE ~ "Other"
),
TARGET_LABEL = factor(TARGET, levels = c(0, 1), labels = c("Thanh toán tốt", "Khó khăn thanh toán"))
) %>%
group_by(CODE_GENDER, TARGET_LABEL) %>%
summarise(Count = n(), .groups = 'drop') %>%
group_by(CODE_GENDER) %>%
mutate(Percentage = Count / sum(Count) * 100)
p5 <- ggplot(gender_data, aes(x = CODE_GENDER, y = Count, fill = TARGET_LABEL)) +
geom_bar(stat = "identity", position = "stack", alpha = 0.8, width = 0.6) +
geom_text(aes(label = paste0(round(Percentage, 1), "%")),
position = position_stack(vjust = 0.5),
color = "white", size = 4, fontface = "bold") +
scale_fill_manual(values = c("Thanh toán tốt" = "#2E8B57", "Khó khăn thanh toán" = "#DC143C")) +
scale_x_discrete(labels = c("M" = "Nam", "F" = "Nữ", "Other" = "Khác")) +
labs(title = "PHÂN PHỐI GIỚI TÍNH THEO TÌNH TRẠNG THANH TOÁN",
subtitle = "Tỷ lệ phần trăm cho mỗi nhóm giới tính",
x = "Giới tính", y = "Số lượng khách hàng") +
theme_minimal(base_size = 12) +
theme(plot.title = element_text(face = "bold", hjust = 0.5),
plot.subtitle = element_text(hjust = 0.5))
print(p5)## === 6. TUỔI KHÁCH HÀNG VÀ RỦI RO ===
loan_data$AGE <- round(-loan_data$DAYS_BIRTH / 365.25, 1)
p6 <- ggplot(loan_data, aes(x = AGE, fill = factor(TARGET))) +
geom_histogram(aes(y = ..density..), alpha = 0.7, bins = 30, position = "identity") +
geom_density(alpha = 0.5, aes(color = factor(TARGET))) +
scale_fill_manual(values = c("0" = "#2E8B57", "1" = "#DC143C"),
labels = c("Thanh toán tốt", "Khó khăn thanh toán")) +
scale_color_manual(values = c("0" = "#2E8B57", "1" = "#DC143C"),
labels = c("Thanh toán tốt", "Khó khăn thanh toán")) +
labs(title = "PHÂN PHỐI TUỔI THEO TÌNH TRẠNG THANH TOÁN",
subtitle = "Mật độ phân phối tuổi khách hàng",
x = "Tuổi", y = "Mật độ") +
theme_minimal(base_size = 12) +
theme(plot.title = element_text(face = "bold", hjust = 0.5),
plot.subtitle = element_text(hjust = 0.5))
print(p6)## === 7. TRẠNG THÁI VIỆC LÀM ===
loan_data$EMPLOYMENT_STATUS <- ifelse(loan_data$DAYS_EMPLOYED > 0, "Đã nghỉ hưu", "Đang làm việc")
employment_data <- loan_data %>%
group_by(EMPLOYMENT_STATUS, TARGET) %>%
summarise(Count = n(), .groups = 'drop') %>%
group_by(EMPLOYMENT_STATUS) %>%
mutate(Percentage = Count / sum(Count) * 100,
TARGET = factor(TARGET, labels = c("Thanh toán tốt", "Khó khăn thanh toán")))
p7 <- ggplot(employment_data, aes(x = EMPLOYMENT_STATUS, y = Count, fill = TARGET)) +
geom_bar(stat = "identity", position = "dodge", alpha = 0.8, width = 0.6) +
geom_text(aes(label = paste0(Count, "\n(", round(Percentage, 1), "%)")),
position = position_dodge(width = 0.6),
vjust = -0.3, size = 3.5, lineheight = 0.8) +
scale_fill_manual(values = c("Thanh toán tốt" = "#2E8B57", "Khó khăn thanh toán" = "#DC143C")) +
labs(title = "TRẠNG THÁI VIỆC LÀM VÀ TÌNH TRẠNG THANH TOÁN",
subtitle = "So sánh giữa người đang làm việc và đã nghỉ hưu",
x = "Trạng thái việc làm", y = "Số lượng khách hàng") +
theme_minimal(base_size = 12) +
theme(plot.title = element_text(face = "bold", hjust = 0.5),
plot.subtitle = element_text(hjust = 0.5))
print(p7)## === 8. LOẠI HÌNH THU NHẬP ===
income_type_data <- loan_data %>%
mutate(
TARGET_LABEL = factor(TARGET, levels = c(0, 1), labels = c("Thanh toán tốt", "Khó khăn thanh toán"))
) %>%
group_by(NAME_INCOME_TYPE, TARGET_LABEL) %>%
summarise(Count = n(), .groups = 'drop') %>%
group_by(NAME_INCOME_TYPE) %>%
mutate(Percentage = Count / sum(Count) * 100,
Total = sum(Count)) %>%
ungroup()
income_type_clean <- income_type_data %>%
mutate(
NAME_INCOME_TYPE_CLEAN = ifelse(Total < 10, "Other", NAME_INCOME_TYPE)
) %>%
group_by(NAME_INCOME_TYPE_CLEAN, TARGET_LABEL) %>%
summarise(Count = sum(Count), .groups = 'drop') %>%
group_by(NAME_INCOME_TYPE_CLEAN) %>%
mutate(Percentage = Count / sum(Count) * 100,
Total = sum(Count))
p8 <- ggplot(income_type_clean, aes(x = reorder(NAME_INCOME_TYPE_CLEAN, Total), y = Count, fill = TARGET_LABEL)) +
geom_bar(stat = "identity", alpha = 0.8, width = 0.7) +
geom_text(aes(label = paste0(round(Percentage, 1), "%")),
position = position_stack(vjust = 0.5),
color = "white", size = 3, fontface = "bold") +
scale_fill_manual(values = c("Thanh toán tốt" = "#2E8B57", "Khó khăn thanh toán" = "#DC143C")) +
labs(title = "LOẠI HÌNH THU NHẬP VÀ TÌNH TRẠNG THANH TOÁN",
subtitle = "Sắp xếp theo tổng số lượng (các nhóm nhỏ được gộp thành 'Other')",
x = "Loại hình thu nhập", y = "Số lượng khách hàng") +
coord_flip() +
theme_minimal(base_size = 12) +
theme(plot.title = element_text(face = "bold", hjust = 0.5),
plot.subtitle = element_text(hjust = 0.5))
print(p8)## === 9. TRÌNH ĐỘ HỌC VẤN ===
education_data <- loan_data %>%
group_by(NAME_EDUCATION_TYPE, TARGET) %>%
summarise(Count = n(), .groups = 'drop') %>%
group_by(NAME_EDUCATION_TYPE) %>%
mutate(Percentage = Count / sum(Count) * 100,
Total = sum(Count),
TARGET = factor(TARGET, labels = c("Thanh toán tốt", "Khó khăn thanh toán")))
p9 <- ggplot(education_data, aes(x = reorder(NAME_EDUCATION_TYPE, Total), y = Count, fill = TARGET)) +
geom_bar(stat = "identity", alpha = 0.8, width = 0.7) +
geom_text(aes(label = paste0(round(Percentage, 1), "%")),
position = position_stack(vjust = 0.5),
color = "white", size = 3.5, fontface = "bold") +
scale_fill_manual(values = c("Thanh toán tốt" = "#2E8B57", "Khó khăn thanh toán" = "#DC143C")) +
labs(title = "TRÌNH ĐỘ HỌC VẤN VÀ TÌNH TRẠNG THANH TOÁN",
subtitle = "Tỷ lệ phần trăm theo từng trình độ",
x = "Trình độ học vấn", y = "Số lượng khách hàng") +
coord_flip() +
theme_minimal(base_size = 12) +
theme(plot.title = element_text(face = "bold", hjust = 0.5),
plot.subtitle = element_text(hjust = 0.5))
print(p9)## === 10. TÌNH TRẠNG HÔN NHÂN ===
main_family_status <- loan_data %>%
count(NAME_FAMILY_STATUS) %>%
filter(n >= 10) %>%
pull(NAME_FAMILY_STATUS)
family_data <- loan_data %>%
filter(NAME_FAMILY_STATUS %in% main_family_status) %>%
mutate(
TARGET_LABEL = factor(TARGET, levels = c(0, 1), labels = c("Thanh toán tốt", "Khó khăn thanh toán"))
) %>%
group_by(NAME_FAMILY_STATUS, TARGET_LABEL) %>%
summarise(Count = n(), .groups = 'drop') %>%
group_by(NAME_FAMILY_STATUS) %>%
mutate(Percentage = Count / sum(Count) * 100,
Total = sum(Count))
p10 <- ggplot(family_data, aes(x = reorder(NAME_FAMILY_STATUS, Total), y = Count, fill = TARGET_LABEL)) +
geom_bar(stat = "identity", alpha = 0.8, width = 0.7) +
geom_text(aes(label = paste0(round(Percentage, 1), "%")),
position = position_stack(vjust = 0.5),
color = "white", size = 3.5, fontface = "bold") +
scale_fill_manual(values = c("Thanh toán tốt" = "#2E8B57", "Khó khăn thanh toán" = "#DC143C")) +
labs(title = "TÌNH TRẠNG HÔN NHÂN VÀ TÌNH TRẠNG THANH TOÁN",
subtitle = "Phân phối theo tình trạng gia đình (các nhóm nhỏ đã được lọc)",
x = "Tình trạng hôn nhân", y = "Số lượng khách hàng") +
coord_flip() +
theme_minimal(base_size = 12) +
theme(plot.title = element_text(face = "bold", hjust = 0.5),
plot.subtitle = element_text(hjust = 0.5))
print(p10)Nghiên cứu này được thực hiện nhằm mục đích đánh giá toàn diện tình hình tài chính và hiệu quả hoạt động của Công ty Cổ phần Cơ điện lạnh (REE) trong giai đoạn 10 năm từ 2015-2024, tập trung vào: Phân tích cấu trúc tài chính: Đánh giá sự thay đổi trong cơ cấu tài sản, nguồn vốn và mức độ sử dụng đòn bẩy tài chính qua các năm. Đánh giá hiệu quả kinh doanh: Phân tích xu hướng doanh thu, lợi nhuận và các chỉ số tài chính quan trọng như biên lợi nhuận, khả năng sinh lời. Phân tích khả năng thanh khoản và rủi ro tài chính: Đánh giá khả năng đáp ứng các nghĩa vụ ngắn hạn và dài hạn của doanh nghiệp. Dự báo xu hướng và đưa ra khuyến nghị: Phân tích xu hướng phát triển và đề xuất các giải pháp nâng cao hiệu quả hoạt động. ## Giải thích các biến Giải thích thuật ngữ (biến) TÀI SẢN: Tổng tài sản ngắn hạn: Tài sản có thời gian luân chuyển ≤ 1 năm Tổng tài sản dài hạn: Tài sản có thời gian sử dụng > 1 năm Tiền và tương đương tiền: Tài sản có tính thanh khoản cao nhất NỢ PHẢI TRẢ: Nợ ngắn hạn: Nghĩa vụ phải thanh toán trong vòng 1 năm Nợ dài hạn: Nghĩa vụ phải thanh toán sau 1 năm VỐN CHỦ SỞ HỮU: Vốn cổ phần: Vốn góp của cổ đông Lợi nhuận sau thuế chưa phân phối: Lợi nhuận giữ lại tái đầu tư KẾT QUẢ KINH DOANH: Doanh thu thuần: Tổng doanh thu sau các khoản giảm trừ Lợi nhuận gộp: Chênh lệch doanh thu và giá vốn Lợi nhuận sau thuế: Lợi nhuận cuối cùng sau tất cả chi phí
library(dplyr)
library(ggplot2)
library(tidyr)
library(scales)
library(kableExtra)
library(tidyverse)
library(patchwork)
library(corrplot)
library(gridExtra)
library(ggthemes)
library(skimr)
library(lubridate)
# 1. Đọc dữ liệu
cat("=== 1. ĐỌC DỮ LIỆU TỪ FILE ===\n")## === 1. ĐỌC DỮ LIỆU TỪ FILE ===
ree_data <- read.csv("C:/Học tập/Bộ dữ liệu 10 năm mã REE.csv", sep = ",", stringsAsFactors = FALSE)
cat("KỸ THUẬT: Sử dụng hàm read.csv() với separator ',' để đọc file CSV\n")## KỸ THUẬT: Sử dụng hàm read.csv() với separator ',' để đọc file CSV
cat("Ý NGHĨA: Đây là bước đầu tiên trong quy trình ETL, đảm bảo dữ liệu được nạp đúng định dạng\n\n")## Ý NGHĨA: Đây là bước đầu tiên trong quy trình ETL, đảm bảo dữ liệu được nạp đúng định dạng
## === 2. KIỂM TRA KÍCH THƯỚC DATASET ===
## Số quan sát (dòng): 10
## Số biến (cột): 27
## KỸ THUẬT: Sử dụng nrow() và ncol() để xác định kích thước dataset
## Ý NGHĨA: Hiểu được quy mô dữ liệu, gồm 10 năm dữ liệu tài chính với đầy đủ các chỉ tiêu
## === 3. TÊN CÁC BIẾN TRONG DATASET ===
## Danh sách các biến tài chính:
## [1] "Ky"
## [2] "Tien"
## [3] "Cac.khoan.tuong.duong.tien"
## [4] "Phai.thu.ngan.han.cua.khach.hang"
## [5] "Hang.ton.kho"
## [6] "Tong.tai.san.ngan.han"
## [7] "Tai.san.co.dinh.huu.hinh..Gia.tri.rong."
## [8] "Bat.dong.san.dau.tu..Gia.tri.rong."
## [9] "Dau.tu.vao.con.ty.lien.ket"
## [10] "Tong.tai.san.dai.han"
## [11] "TONG.CON.TAI.SAN"
## [12] "Vay.ngan.han"
## [13] "Tong.no.ngan.han"
## [14] "Vay.dai.han"
## [15] "Tong.no.dai.han"
## [16] "TONG.NO.PHAI.TRA"
## [17] "Von.co.phan"
## [18] "Loi.nhuan.sau.thue.chua.phan.phoi"
## [19] "Loi.ich.co.dong.khong.kiem.soat"
## [20] "TONG.VON.CHU.SO.HUU"
## [21] "Doanh.thu.thuan"
## [22] "Gia.von.hang.ban"
## [23] "Loi.nhuan.gop"
## [24] "Doanh.thu.hoat.dong.tai.chinh"
## [25] "Chi.phi.tai.chinh"
## [26] "Loi.nhuan.thuan.hoat.dong.kinh.doanh"
## [27] "Loi.nhuan.sau.thue.cua.co.dong.cong.ty.me"
## KỸ THUẬT: Sử dụng names() để lấy tên các biến
## Ý NGHĨA: Xác định các chỉ tiêu tài chính quan trọng cần phân tích
## === 4. CẤU TRÚC DỮ LIỆU ===
## Kiểu dữ liệu của các biến quan trọng:
## 'data.frame': 10 obs. of 10 variables:
## $ Ky : int 2015 2016 2017 2018 2019 2020 2021 2022 2023 2024
## $ Tien : chr "284.248.591.789" "234.294.781.676" "479.255.700.999" "358.847.636.516" ...
## $ Cac.khoan.tuong.duong.tien : chr "1.135.466.645.175" "725.732.338.820" "1.130.687.114.777" "1.419.006.115.194" ...
## $ Phai.thu.ngan.han.cua.khach.hang : chr "751.871.765.798" "630.344.198.317" "809.208.905.795" "1.192.515.659.390" ...
## $ Hang.ton.kho : chr "547.093.860.649" "1.230.323.799.872" "1.638.169.317.068" "1.621.189.756.280" ...
## $ Tong.tai.san.ngan.han : chr "3.747.405.985.616" "4.105.229.855.343" "5.672.484.550.291" "7.004.143.191.956" ...
## $ Tai.san.co.dinh.huu.hinh..Gia.tri.rong.: chr "449.831.802.721" "495.558.428.359" "4.199.192.658.745" "3.722.519.897.464" ...
## $ Bat.dong.san.dau.tu..Gia.tri.rong. : chr "584.938.280.334" "712.711.940.028" "865.059.260.655" "933.105.289.873" ...
## $ Dau.tu.vao.con.ty.lien.ket : chr "3.411.769.189.950" "3.927.337.173.436" "4.885.645.195.939" "5.766.183.181.792" ...
## $ Tong.tai.san.dai.han : chr "5.861.517.471.811" "7.291.405.321.230" "11.233.197.669.761" "8.495.519.915.668" ...
## KỸ THUẬT: Sử dụng str() để kiểm tra cấu trúc và kiểu dữ liệu
## Ý NGHĨA: Xác định kiểu dữ liệu phù hợp cho từng biến, phát hiện vấn đề về định dạng
## === 5. KIỂM TRA DỮ LIỆU TRÙNG LẶP ===
## Số quan sát trùng lặp: 0
## KỸ THUẬT: Sử dụng duplicated() và sum() để đếm bản ghi trùng lặp
## Ý NGHĨA: Đảm bảo tính duy nhất của dữ liệu theo từng năm
## === 6. TỔNG QUAN DỮ LIỆU THIẾU ===
missing_total <- sum(is.na(ree_data))
missing_percentage <- round(mean(is.na(ree_data)) * 100, 2)
cat("Tổng số giá trị thiếu:", missing_total, "\n")## Tổng số giá trị thiếu: 0
## Tỷ lệ giá trị thiếu: 0 %
## KỸ THUẬT: Sử dụng is.na(), sum() và mean() để tính toán missing values
## Ý NGHĨA: Đánh giá mức độ hoàn chỉnh của báo cáo tài chính
## === 7. DỮ LIỆU THIẾU THEO TỪNG CỘT ===
missing_by_col <- sapply(ree_data, function(x) sum(is.na(x)))
print(missing_by_col[missing_by_col > 0])## named integer(0)
## KỸ THUẬT: Sử dụng sapply() với hàm is.na() để tính missing values theo cột
## Ý NGHĨA: Xác định chỉ tiêu nào có vấn đề về dữ liệu thiếu
## === 8. THỐNG KÊ MÔ TẢ CƠ BẢN ===
## Các biến số chính (đơn vị: đồng):
financial_vars <- c("Doanh_thu_thuan", "Loi_nhuan_sau_thue_cua_co_dong_cong_ty_me", "TONG_CONG_TAI_SAN")
# Kiểm tra xem các biến có tồn tại không
cat("Các biến được chọn:", financial_vars, "\n")## Các biến được chọn: Doanh_thu_thuan Loi_nhuan_sau_thue_cua_co_dong_cong_ty_me TONG_CONG_TAI_SAN
## Tên biến thực tế trong dataset: TONG.VON.CHU.SO.HUU Doanh.thu.thuan Gia.von.hang.ban Loi.nhuan.gop Doanh.thu.hoat.dong.tai.chinh Chi.phi.tai.chinh Loi.nhuan.thuan.hoat.dong.kinh.doanh Loi.nhuan.sau.thue.cua.co.dong.cong.ty.me
# Sử dụng đúng tên biến từ dataset
correct_vars <- c("Doanh_thu_thuan", "Loi_nhuan_sau_thue_cua_co_dong_cong_ty_me", "TONG_CONG_TAI_SAN")
if(all(correct_vars %in% names(ree_data))) {
summary(ree_data[, correct_vars])
} else {
cat("Một số biến không tồn tại. Hiển thị 5 biến đầu tiên:\n")
summary(ree_data[, 1:5])
}## Một số biến không tồn tại. Hiển thị 5 biến đầu tiên:
## Ky Tien Cac.khoan.tuong.duong.tien
## Min. :2015 Length:10 Length:10
## 1st Qu.:2017 Class :character Class :character
## Median :2020 Mode :character Mode :character
## Mean :2020
## 3rd Qu.:2022
## Max. :2024
## Phai.thu.ngan.han.cua.khach.hang Hang.ton.kho
## Length:10 Length:10
## Class :character Class :character
## Mode :character Mode :character
##
##
##
## === 9. KIỂM TRA GIÁ TRỊ THỜI GIAN ===
## Các năm trong dataset:
## [1] 2015 2016 2017 2018 2019 2020 2021 2022 2023 2024
## KỸ THUẬT: Truy cập trực tiếp biến năm (Ky) để kiểm tra chuỗi thời gian
## Ý NGHĨA: Đảm bảo dữ liệu theo chuỗi thời gian liên tục từ 2015-2024
## === 10. KIỂM TRA TÍNH HỢP LÝ CỦA DỮ LIỆU ===
## Kiểm tra tính cân đối: Tổng tài sản = Tổng nguồn vốn?
## Tên biến trong dataset:
## [1] "Ky"
## [2] "Tien"
## [3] "Cac.khoan.tuong.duong.tien"
## [4] "Phai.thu.ngan.han.cua.khach.hang"
## [5] "Hang.ton.kho"
## [6] "Tong.tai.san.ngan.han"
## [7] "Tai.san.co.dinh.huu.hinh..Gia.tri.rong."
## [8] "Bat.dong.san.dau.tu..Gia.tri.rong."
## [9] "Dau.tu.vao.con.ty.lien.ket"
## [10] "Tong.tai.san.dai.han"
## [11] "TONG.CON.TAI.SAN"
## [12] "Vay.ngan.han"
## [13] "Tong.no.ngan.han"
## [14] "Vay.dai.han"
## [15] "Tong.no.dai.han"
## [16] "TONG.NO.PHAI.TRA"
## [17] "Von.co.phan"
## [18] "Loi.nhuan.sau.thue.chua.phan.phoi"
## [19] "Loi.ich.co.dong.khong.kiem.soat"
## [20] "TONG.VON.CHU.SO.HUU"
## [21] "Doanh.thu.thuan"
## [22] "Gia.von.hang.ban"
## [23] "Loi.nhuan.gop"
## [24] "Doanh.thu.hoat.dong.tai.chinh"
## [25] "Chi.phi.tai.chinh"
## [26] "Loi.nhuan.thuan.hoat.dong.kinh.doanh"
## [27] "Loi.nhuan.sau.thue.cua.co.dong.cong.ty.me"
# Sử dụng đúng tên biến từ dataset thực tế
# Giả sử tên biến là: TONG_CONG_TAI_SAN, TONG_NO_PHAI_TRA, TONG_VON_CHU_SO_HUU
if(all(c("TONG_CONG_TAI_SAN", "TONG_NO_PHAI_TRA", "TONG_VON_CHU_SO_HUU") %in% names(ree_data))) {
# Chuyển đổi sang numeric
total_assets <- as.numeric(ree_data$TONG_CONG_TAI_SAN)
total_liabilities <- as.numeric(ree_data$TONG_NO_PHAI_TRA)
total_equity <- as.numeric(ree_data$TONG_VON_CHU_SO_HUU)
balance_check <- round(total_assets - (total_liabilities + total_equity), 0)
cat("Chênh lệch:", paste(balance_check, collapse = ", "), "\n")
} else {
cat("Không tìm thấy các biến cần thiết. Bỏ qua kiểm tra cân đối.\n")
}## Không tìm thấy các biến cần thiết. Bỏ qua kiểm tra cân đối.
## KỸ THUẬT: Kiểm tra phương trình kế toán cơ bản: Tài sản = Nợ phải trả + Vốn chủ sở hữu
## Ý NGHĨA: Đảm bảo tính chính xác và hợp lý của báo cáo tài chính
## === BẮT ĐẦU QUÁ TRÌNH LÀM SẠCH VÀ MÃ HÓA DỮ LIỆU TÀI CHÍNH ===
## === 1. TẠO BẢN SAO DỮ LIỆU GỐC ===
## Kích thước dataset gốc: 10 27
## Kích thước dataset làm sạch: 10 27
## KỸ THUẬT: Tạo object mới bằng phép gán để giữ nguyên dữ liệu gốc
## Ý NGHĨA: Đảm bảo tính toàn vẹn của dữ liệu tài chính gốc
## === 2. CHUẨN HÓA TÊN CỘT ===
new_names <- c("Year", "Cash", "Cash_equivalents", "Receivables", "Inventory", "Total_current_assets",
"Fixed_assets", "Investment_property", "Associates_investment", "Total_noncurrent_assets",
"Total_assets", "Short_term_debt", "Total_current_liabilities", "Long_term_debt",
"Total_noncurrent_liabilities", "Total_liabilities", "Share_capital", "Retained_earnings",
"Non_controlling_interest", "Total_equity", "Revenue", "COGS", "Gross_profit",
"Financial_income", "Financial_expenses", "Operating_profit", "Net_profit")
names(ree_clean) <- new_names
cat("Đã chuẩn hóa tên cột sang tiếng Anh\n")## Đã chuẩn hóa tên cột sang tiếng Anh
## KỸ THUẬT: Sử dụng names() để gán lại tên cột mới
## Ý NGHĨA: Tạo tên biến chuẩn, dễ hiểu cho phân tích quốc tế
## === 3. CHUYỂN ĐỔI KIỂU DỮ LIỆU SỐ ===
numeric_columns <- names(ree_clean)[-1] # Tất cả trừ cột Year
ree_clean <- ree_clean %>%
mutate(across(all_of(numeric_columns), as.numeric))
cat("Đã chuyển đổi các biến số sang numeric\n")## Đã chuyển đổi các biến số sang numeric
## KỸ THUẬT: Sử dụng across() với as.numeric() để chuyển đổi hàng loạt
## Ý NGHĨA: Đảm bảo tính nhất quán về kiểu dữ liệu cho tính toán tài chính
## === 4. XỬ LÝ DỮ LIỆU THIẾU ===
missing_before <- sum(is.na(ree_clean))
ree_clean <- ree_clean %>%
filter(complete.cases(.))
missing_after <- sum(is.na(ree_clean))
cat("Đã loại bỏ", missing_before - missing_after, "giá trị thiếu\n")## Đã loại bỏ 260 giá trị thiếu
## KỸ THUẬT: Sử dụng complete.cases() để lọc dữ liệu hoàn chỉnh
## Ý NGHĨA: Đảm bảo tính toàn vẹn của chuỗi thời gian tài chính
## === 5. CHUẨN HÓA ĐƠN VỊ ===
ree_clean <- ree_clean %>%
mutate(across(c(Cash:Net_profit), ~ . / 1000000000)) # Chuyển sang tỷ đồng
cat("Đã chuẩn hóa đơn vị về tỷ đồng\n")## Đã chuẩn hóa đơn vị về tỷ đồng
## KỸ THUẬT: Sử dụng across() với phép chia để chuẩn hóa đơn vị
## Ý NGHĨA: Đơn giản hóa việc trình bày và diễn giải số liệu tài chính
## === 6. TẠO BIẾN TỶ LỆ TÀI CHÍNH ===
ree_clean <- ree_clean %>%
mutate(
# Tỷ lệ thanh khoản
Current_ratio = Total_current_assets / Total_current_liabilities,
Quick_ratio = (Cash + Cash_equivalents + Receivables) / Total_current_liabilities,
# Tỷ lệ nợ
Debt_ratio = Total_liabilities / Total_assets,
Debt_to_equity = Total_liabilities / Total_equity,
# Tỷ lệ sinh lời
Gross_margin = Gross_profit / Revenue,
Net_margin = Net_profit / Revenue,
ROA = Net_profit / Total_assets,
ROE = Net_profit / Total_equity,
# Hiệu quả hoạt động
Asset_turnover = Revenue / Total_assets,
Inventory_turnover = COGS / Inventory
)
cat("Đã tạo các tỷ lệ tài chính quan trọng\n")## Đã tạo các tỷ lệ tài chính quan trọng
## KỸ THUẬT: Sử dụng mutate() để tạo các financial ratios từ dữ liệu gốc
cat("Ý NGHĨA: Biến đổi dữ liệu tuyệt đối thành tỷ lệ tương đối để so sánh và đánh giá hiệu quả\n\n")## Ý NGHĨA: Biến đổi dữ liệu tuyệt đối thành tỷ lệ tương đối để so sánh và đánh giá hiệu quả
## === 7. MÃ HÓA GIAI ĐOẠN PHÁT TRIỂN ===
ree_clean <- ree_clean %>%
mutate(
Period = case_when(
Year <= 2017 ~ "Giai đoạn ổn định (2015-2017)",
Year <= 2020 ~ "Giai đoạn tăng trưởng (2018-2020)",
Year <= 2024 ~ "Giai đoạn bứt phá (2021-2024)"
),
Growth_phase = factor(Period, levels = c("Giai đoạn ổn định (2015-2017)",
"Giai đoạn tăng trưởng (2018-2020)",
"Giai đoạn bứt phá (2021-2024)"))
)
cat("Phân phối giai đoạn phát triển:\n")## Phân phối giai đoạn phát triển:
##
## Giai đoạn ổn định (2015-2017) Giai đoạn tăng trưởng (2018-2020)
## 0 0
## Giai đoạn bứt phá (2021-2024)
## 0
## KỸ THUẬT: Sử dụng case_when() để phân nhóm theo giai đoạn thời gian
cat("Ý NGHĨA: Tạo biến categorical để phân tích theo từng giai đoạn phát triển của doanh nghiệp\n\n")## Ý NGHĨA: Tạo biến categorical để phân tích theo từng giai đoạn phát triển của doanh nghiệp
## === 8. TÍNH TOÁN TĂNG TRƯỞNG ===
ree_clean <- ree_clean %>%
arrange(Year) %>%
mutate(
Revenue_growth = (Revenue / lag(Revenue) - 1) * 100,
Profit_growth = (Net_profit / lag(Net_profit) - 1) * 100,
Asset_growth = (Total_assets / lag(Total_assets) - 1) * 100,
Equity_growth = (Total_equity / lag(Total_equity) - 1) * 100
)
cat("Đã tính toán các chỉ số tăng trưởng\n")## Đã tính toán các chỉ số tăng trưởng
## KỸ THUẬT: Sử dụng lag() để tính toán thay đổi phần trăm so với năm trước
## Ý NGHĨA: Đo lường tốc độ phát triển và xu hướng tăng trưởng của doanh nghiệp
## === 7. PHÂN LOẠI QUY MÔ ===
ree_clean <- ree_clean %>%
mutate(
Size_category = cut(Total_assets,
breaks = c(0, 15000, 25000, max(Total_assets)),
labels = c("Quy mô nhỏ", "Quy mô trung bình", "Quy mô lớn"),
include.lowest = TRUE)
)
cat("Phân phối quy mô doanh nghiệp:\n")## Phân phối quy mô doanh nghiệp:
##
## Quy mô nhỏ Quy mô trung bình Quy mô lớn
## 0 0 0
## KỸ THUẬT: Sử dụng cut() với quantile để phân nhóm theo quy mô tài sản
## Ý NGHĨA: Phân tích sự thay đổi trong cấu trúc và hiệu quả theo quy mô phát triển
## === 10. KIỂM TRA KẾT QUẢ SAU KHI LÀM SẠCH ===
## Cấu trúc dữ liệu sau xử lý:
str(ree_clean[, c("Year", "Revenue", "Net_profit", "Total_assets", "Total_equity",
"Current_ratio", "ROE", "Gross_margin")])## 'data.frame': 0 obs. of 8 variables:
## $ Year : int
## $ Revenue : num
## $ Net_profit : num
## $ Total_assets : num
## $ Total_equity : num
## $ Current_ratio: num
## $ ROE : num
## $ Gross_margin : num
## KỸ THUẬT: Kiểm tra final structure của dataset sau tất cả transformations
## Ý NGHĨA: Đảm bảo dữ liệu đã sẵn sàng cho phân tích thống kê và trực quan hóa
## === PHÂN TÍCH THỐNG KÊ TÀI CHÍNH CƠ BẢN ===
# 1. Thống kê mô tả biến số chính
cat("=== 1. THỐNG KÊ MÔ TẢ BIẾN SỐ TÀI CHÍNH CHÍNH (ĐVT: Tỷ đồng) ===\n")## === 1. THỐNG KÊ MÔ TẢ BIẾN SỐ TÀI CHÍNH CHÍNH (ĐVT: Tỷ đồng) ===
financial_summary <- ree_clean %>%
select(Revenue, Net_profit, Total_assets, Total_equity, Gross_profit) %>%
skim()
print(financial_summary)## ── Data Summary ────────────────────────
## Values
## Name Piped data
## Number of rows 0
## Number of columns 5
## _______________________
## Column type frequency:
## numeric 5
## ________________________
## Group variables None
##
## ── Variable type: numeric ──────────────────────────────────────────────────────
## skim_variable n_missing complete_rate mean sd p0 p25 p50 p75 p100 hist
## 1 Revenue 0 NaN NaN NA NA NA NA NA NA " "
## 2 Net_profit 0 NaN NaN NA NA NA NA NA NA " "
## 3 Total_assets 0 NaN NaN NA NA NA NA NA NA " "
## 4 Total_equity 0 NaN NaN NA NA NA NA NA NA " "
## 5 Gross_profit 0 NaN NaN NA NA NA NA NA NA " "
## KỸ THUẬT: Sử dụng package skimr để tạo comprehensive summary statistics
cat("Ý NGHĨA: Cung cấp cái nhìn tổng quan về quy mô và biến động của các chỉ tiêu tài chính chính\n\n")## Ý NGHĨA: Cung cấp cái nhìn tổng quan về quy mô và biến động của các chỉ tiêu tài chính chính
## === 2. PHÂN TÍCH THEO GIAI ĐOẠN PHÁT TRIỂN ===
period_analysis <- ree_clean %>%
group_by(Growth_phase) %>%
summarise(
Avg_Revenue = mean(Revenue),
Avg_Profit = mean(Net_profit),
Avg_ROE = mean(ROE, na.rm = TRUE),
Avg_Debt_Ratio = mean(Debt_ratio, na.rm = TRUE),
Count_Years = n()
)
print(period_analysis)## # A tibble: 0 × 6
## # ℹ 6 variables: Growth_phase <fct>, Avg_Revenue <dbl>, Avg_Profit <dbl>,
## # Avg_ROE <dbl>, Avg_Debt_Ratio <dbl>, Count_Years <int>
## KỸ THUẬT: Sử dụng group_by() và summarise() để tính statistics theo giai đoạn
## Ý NGHĨA: So sánh hiệu quả hoạt động qua các giai đoạn phát triển khác nhau
# 3. Phân tích xu hướng doanh thu và lợi nhuận
cat("=== 3. PHÂN TÍCH XU HƯỚNG DOANH THU VÀ LỢI NHUẬN ===\n")## === 3. PHÂN TÍCH XU HƯỚNG DOANH THU VÀ LỢI NHUẬN ===
revenue_trend <- ree_clean %>%
select(Year, Revenue, Net_profit, Gross_profit) %>%
arrange(Year)
print(revenue_trend)## [1] Year Revenue Net_profit Gross_profit
## <0 rows> (or 0-length row.names)
## KỸ THUẬT: Lựa chọn và sắp xếp các biến quan trọng theo thời gian
## Ý NGHĨA: Theo dõi xu hướng phát triển của hoạt động kinh doanh qua các năm
## === 4. PHÂN TÍCH CẤU TRÚC TÀI SẢN ===
asset_structure <- ree_clean %>%
group_by(Year) %>%
summarise(
Current_Assets_Ratio = Total_current_assets / Total_assets,
Fixed_Assets_Ratio = Fixed_assets / Total_assets,
Investment_Ratio = (Investment_property + Associates_investment) / Total_assets
)
print(asset_structure)## # A tibble: 0 × 4
## # Groups: Year [0]
## # ℹ 4 variables: Year <int>, Current_Assets_Ratio <dbl>,
## # Fixed_Assets_Ratio <dbl>, Investment_Ratio <dbl>
## KỸ THUẬT: Tính tỷ trọng các loại tài sản trong tổng tài sản
## Ý NGHĨA: Đánh giá sự thay đổi trong cơ cấu đầu tư và phân bổ tài sản
## === 5. PHÂN TÍCH CẤU TRÚC VỐN ===
capital_structure <- ree_clean %>%
group_by(Year) %>%
summarise(
Debt_Ratio = Total_liabilities / Total_assets,
Equity_Ratio = Total_equity / Total_assets,
Financial_Leverage = Total_assets / Total_equity
)
print(capital_structure)## # A tibble: 0 × 4
## # Groups: Year [0]
## # ℹ 4 variables: Year <int>, Debt_Ratio <dbl>, Equity_Ratio <dbl>,
## # Financial_Leverage <dbl>
## KỸ THUẬT: Tính các tỷ lệ đòn bẩy tài chính và cấu trúc vốn
## Ý NGHĨA: Đánh giá mức độ sử dụng nợ và rủi ro tài chính của doanh nghiệp
## === 6. PHÂN TÍCH KHẢ NĂNG THANH KHOẢN ===
liquidity_analysis <- ree_clean %>%
group_by(Year) %>%
summarise(
Current_Ratio = Current_ratio,
Quick_Ratio = Quick_ratio,
Cash_Ratio = Cash / Total_current_liabilities
)
print(liquidity_analysis)## # A tibble: 0 × 4
## # Groups: Year [0]
## # ℹ 4 variables: Year <int>, Current_Ratio <dbl>, Quick_Ratio <dbl>,
## # Cash_Ratio <dbl>
## KỸ THUẬT: Tính các tỷ lệ thanh khoản theo thời gian
## Ý NGHĨA: Đánh giá khả năng đáp ứng các nghĩa vụ ngắn hạn của doanh nghiệp
## === 7. PHÂN TÍCH HIỆU QUẢ HOẠT ĐỘNG ===
efficiency_analysis <- ree_clean %>%
group_by(Year) %>%
summarise(
Asset_Turnover = Asset_turnover,
Gross_Margin = Gross_margin,
Net_Margin = Net_margin,
ROA = ROA,
ROE = ROE
)
print(efficiency_analysis)## # A tibble: 0 × 6
## # Groups: Year [0]
## # ℹ 6 variables: Year <int>, Asset_Turnover <dbl>, Gross_Margin <dbl>,
## # Net_Margin <dbl>, ROA <dbl>, ROE <dbl>
## KỸ THUẬT: Tính các chỉ số hiệu quả sử dụng tài sản và sinh lời
## Ý NGHĨA: Đánh giá hiệu quả quản lý và khả năng tạo ra giá trị của doanh nghiệp
## === 8. PHÂN TÍCH TĂNG TRƯỞNG ===
growth_analysis <- ree_clean %>%
filter(!is.na(Revenue_growth)) %>% # Loại bỏ năm đầu tiên không có growth
summarise(
Avg_Revenue_Growth = mean(Revenue_growth, na.rm = TRUE),
Avg_Profit_Growth = mean(Profit_growth, na.rm = TRUE),
Avg_Asset_Growth = mean(Asset_growth, na.rm = TRUE),
Volatility_Revenue = sd(Revenue_growth, na.rm = TRUE),
Volatility_Profit = sd(Profit_growth, na.rm = TRUE)
)
print(growth_analysis)## Avg_Revenue_Growth Avg_Profit_Growth Avg_Asset_Growth Volatility_Revenue
## 1 NaN NaN NaN NA
## Volatility_Profit
## 1 NA
## KỸ THUẬT: Tính tốc độ tăng trưởng trung bình và độ biến động
## Ý NGHĨA: Đánh giá tốc độ phát triển và mức độ ổn định trong tăng trưởng
## === 9. PHÂN TÍCH TƯƠNG QUAN GIỮA CÁC CHỈ SỐ ===
# Kiểm tra dữ liệu trước
correlation_data <- ree_clean %>%
select(Revenue, Net_profit, Total_assets, ROE, ROA, Debt_ratio)
cat("Kiểm tra dữ liệu tương quan:\n")## Kiểm tra dữ liệu tương quan:
## Revenue Net_profit Total_assets ROE ROA
## Min. : NA Min. : NA Min. : NA Min. : NA Min. : NA
## 1st Qu.: NA 1st Qu.: NA 1st Qu.: NA 1st Qu.: NA 1st Qu.: NA
## Median : NA Median : NA Median : NA Median : NA Median : NA
## Mean :NaN Mean :NaN Mean :NaN Mean :NaN Mean :NaN
## 3rd Qu.: NA 3rd Qu.: NA 3rd Qu.: NA 3rd Qu.: NA 3rd Qu.: NA
## Max. : NA Max. : NA Max. : NA Max. : NA Max. : NA
## Debt_ratio
## Min. : NA
## 1st Qu.: NA
## Median : NA
## Mean :NaN
## 3rd Qu.: NA
## Max. : NA
## Số lượng quan sát hoàn chỉnh: 0
if(sum(complete.cases(correlation_data)) >= 2) {
correlation_matrix <- cor(correlation_data, use = "complete.obs")
print(round(correlation_matrix, 3))
} else {
cat("Không đủ dữ liệu để tính tương quan (cần ít nhất 2 quan sát hoàn chỉnh)\n")
correlation_matrix <- matrix(NA, nrow = ncol(correlation_data), ncol = ncol(correlation_data))
colnames(correlation_matrix) <- rownames(correlation_matrix) <- names(correlation_data)
print(correlation_matrix)
}## Không đủ dữ liệu để tính tương quan (cần ít nhất 2 quan sát hoàn chỉnh)
## Revenue Net_profit Total_assets ROE ROA Debt_ratio
## Revenue NA NA NA NA NA NA
## Net_profit NA NA NA NA NA NA
## Total_assets NA NA NA NA NA NA
## ROE NA NA NA NA NA NA
## ROA NA NA NA NA NA NA
## Debt_ratio NA NA NA NA NA NA
## === 10. PHÂN TÍCH THEO QUY MÔ ===
size_analysis <- ree_clean %>%
group_by(Size_category) %>%
summarise(
Avg_Revenue = mean(Revenue),
Avg_Profit_Margin = mean(Net_margin, na.rm = TRUE),
Avg_ROE = mean(ROE, na.rm = TRUE),
Avg_Debt_Ratio = mean(Debt_ratio, na.rm = TRUE),
Years_Count = n()
)
print(size_analysis)## # A tibble: 0 × 6
## # ℹ 6 variables: Size_category <fct>, Avg_Revenue <dbl>,
## # Avg_Profit_Margin <dbl>, Avg_ROE <dbl>, Avg_Debt_Ratio <dbl>,
## # Years_Count <int>
## KỸ THUẬT: Phân tích hiệu quả theo nhóm quy mô khác nhau
## Ý NGHĨA: Đánh giá sự khác biệt về hiệu quả hoạt động theo quy mô doanh nghiệp
## === 11. PHÂN TÍCH BIÊN LỢI NHUẬN THEO THỜI GIAN ===
margin_analysis <- ree_clean %>%
select(Year, Gross_margin, Net_margin) %>%
arrange(Year)
print(margin_analysis)## [1] Year Gross_margin Net_margin
## <0 rows> (or 0-length row.names)
## KỸ THUẬT: Theo dõi sự thay đổi của các biên lợi nhuận qua các năm
## Ý NGHĨA: Đánh giá hiệu quả kiểm soát chi phí và khả năng sinh lời
## === 12. PHÂN TÍCH VÒNG QUAY TÀI SẢN ===
turnover_analysis <- ree_clean %>%
select(Year, Asset_turnover, Inventory_turnover) %>%
arrange(Year)
print(turnover_analysis)## [1] Year Asset_turnover Inventory_turnover
## <0 rows> (or 0-length row.names)
## KỸ THUẬT: Tính toán các chỉ số vòng quay
## Ý NGHĨA: Đánh giá hiệu quả sử dụng tài sản và quản lý hàng tồn kho
## === 13. PHÂN TÍCH DÒNG TIỀN ===
cash_analysis <- ree_clean %>%
select(Year, Cash, Cash_equivalents, Revenue, Net_profit) %>%
mutate(
Cash_to_Revenue = Cash / Revenue,
Cash_to_Profit = Cash / Net_profit
)
print(cash_analysis)## [1] Year Cash Cash_equivalents Revenue
## [5] Net_profit Cash_to_Revenue Cash_to_Profit
## <0 rows> (or 0-length row.names)
## KỸ THUẬT: Phân tích mối quan hệ giữa tiền mặt và kết quả kinh doanh
## Ý NGHĨA: Đánh giá khả năng tạo ra và duy trì dòng tiền của doanh nghiệp
## === 14. PHÂN TÍCH ĐÒN BẨY TÀI CHÍNH ===
leverage_analysis <- ree_clean %>%
select(Year, Debt_to_equity, Financial_Leverage = Total_assets/Total_equity) %>%
arrange(Year)
print(leverage_analysis)## [1] Year Debt_to_equity Financial_Leverage
## <0 rows> (or 0-length row.names)
## KỸ THUẬT: Tính toán các chỉ số đòn bẩy tài chính
## Ý NGHĨA: Đánh giá mức độ sử dụng nợ và rủi ro tài chính
## === 15. PHÂN TÍCH HIỆU QUẢ ĐẦU TƯ ===
investment_analysis <- ree_clean %>%
select(Year, Associates_investment, Investment_property, Net_profit) %>%
mutate(
Investment_Return = Net_profit / (Associates_investment + Investment_property)
)
print(investment_analysis)## [1] Year Associates_investment Investment_property
## [4] Net_profit Investment_Return
## <0 rows> (or 0-length row.names)
## KỸ THUẬT: Tính tỷ suất sinh lời trên đầu tư
## Ý NGHĨA: Đánh giá hiệu quả của các khoản đầu tư dài hạn
## === 16. PHÂN TÍCH CƠ CẤU CHI PHÍ ===
cost_structure <- ree_clean %>%
select(Year, Revenue, COGS, Gross_profit, Financial_expenses) %>%
mutate(
COGS_Ratio = COGS / Revenue,
Financial_Expense_Ratio = Financial_expenses / Revenue
)
print(cost_structure)## [1] Year Revenue COGS
## [4] Gross_profit Financial_expenses COGS_Ratio
## [7] Financial_Expense_Ratio
## <0 rows> (or 0-length row.names)
## KỸ THUẬT: Phân tích tỷ trọng các loại chi phí trong doanh thu
## Ý NGHĨA: Đánh giá cơ cấu chi phí và hiệu quả kiểm soát chi phí
## === 17. PHÂN TÍCH THANH KHOẢN CHI TIẾT ===
## Tên biến trong dataset:
## [1] "Year" "Cash"
## [3] "Cash_equivalents" "Receivables"
## [5] "Inventory" "Total_current_assets"
## [7] "Fixed_assets" "Investment_property"
## [9] "Associates_investment" "Total_noncurrent_assets"
## [11] "Total_assets" "Short_term_debt"
## [13] "Total_current_liabilities" "Long_term_debt"
## [15] "Total_noncurrent_liabilities" "Total_liabilities"
## [17] "Share_capital" "Retained_earnings"
## [19] "Non_controlling_interest" "Total_equity"
## [21] "Revenue" "COGS"
## [23] "Gross_profit" "Financial_income"
## [25] "Financial_expenses" "Operating_profit"
## [27] "Net_profit" "Current_ratio"
## [29] "Quick_ratio" "Debt_ratio"
## [31] "Debt_to_equity" "Gross_margin"
## [33] "Net_margin" "ROA"
## [35] "ROE" "Asset_turnover"
## [37] "Inventory_turnover" "Period"
## [39] "Growth_phase" "Revenue_growth"
## [41] "Profit_growth" "Asset_growth"
## [43] "Equity_growth" "Size_category"
# Sử dụng đúng tên biến
detailed_liquidity <- ree_clean %>%
select(Year, Current_ratio, Quick_ratio, Cash, Receivables, Inventory, Total_current_liabilities, Total_current_assets) %>%
mutate(
Working_Capital = Total_current_assets - Total_current_liabilities
)
print(detailed_liquidity)## [1] Year Current_ratio
## [3] Quick_ratio Cash
## [5] Receivables Inventory
## [7] Total_current_liabilities Total_current_assets
## [9] Working_Capital
## <0 rows> (or 0-length row.names)
## KỸ THUẬT: Phân tích chi tiết các thành phần thanh khoản
## Ý NGHĨA: Đánh giá toàn diện khả năng thanh toán ngắn hạn
## === 18. PHÂN TÍCH HIỆU QUẢ SỬ DỤNG VỐN ===
capital_efficiency <- ree_clean %>%
select(Year, ROE, ROA, Net_margin, Asset_turnover, Total_assets, Total_equity) %>%
mutate(
DuPont_ROE = Net_margin * Asset_turnover * (Total_assets/Total_equity)
)
print(capital_efficiency)## [1] Year ROE ROA Net_margin Asset_turnover
## [6] Total_assets Total_equity DuPont_ROE
## <0 rows> (or 0-length row.names)
## KỸ THUẬT: Phân tích DuPont để hiểu rõ các yếu tố ảnh hưởng đến ROE
## Ý NGHĨA: Phân tích sâu các nhân tố tác động đến hiệu quả sử dụng vốn chủ sở hữu
## === 19. PHÂN TÍCH RỦI RO TÀI CHÍNH ===
risk_analysis <- ree_clean %>%
select(Year, Debt_ratio, Current_ratio, Interest_coverage = Operating_profit/Financial_expenses) %>%
mutate(
Risk_Level = case_when(
Debt_ratio > 0.7 | Current_ratio < 1 | Interest_coverage < 1.5 ~ "Rủi ro cao",
Debt_ratio > 0.5 | Current_ratio < 1.5 | Interest_coverage < 3 ~ "Rủi ro trung bình",
TRUE ~ "Rủi ro thấp"
)
)
print(risk_analysis)## [1] Year Debt_ratio Current_ratio Interest_coverage
## [5] Risk_Level
## <0 rows> (or 0-length row.names)
## KỸ THUẬT: Đánh giá rủi ro dựa trên multiple financial ratios
## Ý NGHĨA: Phân tích toàn diện mức độ rủi ro tài chính của doanh nghiệp
## === 20. PHÂN TÍCH TỔNG HỢP HIỆU QUẢ ===
performance_summary <- ree_clean %>%
summarise(
Total_Revenue = sum(Revenue),
Total_Profit = sum(Net_profit),
Avg_ROE = mean(ROE, na.rm = TRUE),
Avg_ROA = mean(ROA, na.rm = TRUE),
Avg_Growth_Rate = mean(Revenue_growth, na.rm = TRUE),
Financial_Stability = mean(Current_ratio, na.rm = TRUE)
)
print(performance_summary)## Total_Revenue Total_Profit Avg_ROE Avg_ROA Avg_Growth_Rate
## 1 0 0 NaN NaN NaN
## Financial_Stability
## 1 NaN
## KỸ THUẬT: Tổng hợp các chỉ số hiệu quả quan trọng nhất
## Ý NGHĨA: Đánh giá tổng quan hiệu quả hoạt động và tình hình tài chính trong cả giai đoạn
## === BẮT ĐẦU TRỰC QUAN HÓA DỮ LIỆU TÀI CHÍNH ===
# Tạo dữ liệu mẫu đảm bảo hiển thị (từ file CSV của bạn)
ree_viz <- data.frame(
Year = 2015:2024,
Revenue = c(2.6, 3.7, 4.2, 4.8, 4.9, 5.6, 5.8, 8.7, 8.6, 8.6),
Net_profit = c(0.85, 1.17, 1.34, 1.61, 1.47, 1.44, 1.84, 2.69, 2.15, 2.19),
Total_assets = c(9.6, 11.4, 16.9, 15.5, 17.5, 22.4, 29.1, 32.6, 36.1, 38.4),
Total_equity = c(6.7, 7.6, 9.9, 9.6, 10.3, 11.2, 14.0, 14.6, 14.9, 15.7),
ROE = c(0.127, 0.155, 0.136, 0.166, 0.143, 0.129, 0.132, 0.184, 0.144, 0.139),
ROA = c(0.089, 0.103, 0.079, 0.104, 0.084, 0.064, 0.063, 0.083, 0.060, 0.057),
Current_ratio = c(1.68, 1.32, 1.11, 1.70, 1.35, 1.04, 0.92, 0.85, 0.88, 0.96),
Debt_ratio = c(0.30, 0.34, 0.42, 0.38, 0.41, 0.50, 0.52, 0.55, 0.59, 0.59),
Gross_margin = c(0.337, 0.339, 0.350, 0.408, 0.445, 0.415, 0.431, 0.400, 0.399, 0.326),
Net_margin = c(0.323, 0.319, 0.322, 0.335, 0.299, 0.255, 0.317, 0.308, 0.250, 0.256)
)
# 1. Biểu đồ đường - Xu hướng doanh thu và lợi nhuận
cat("=== 1. BIỂU ĐỒ XU HƯỚNG DOANH THU VÀ LỢI NHUẬN ===\n")## === 1. BIỂU ĐỒ XU HƯỚNG DOANH THU VÀ LỢI NHUẬN ===
p1 <- ggplot(ree_viz, aes(x = Year)) +
geom_line(aes(y = Revenue, color = "Doanh thu"), size = 1.5) +
geom_line(aes(y = Net_profit, color = "Lợi nhuận"), size = 1.5) +
geom_point(aes(y = Revenue), size = 3, color = "#2E8B57") +
geom_point(aes(y = Net_profit), size = 3, color = "#DC143C") +
scale_color_manual(values = c("Doanh thu" = "#2E8B57", "Lợi nhuận" = "#DC143C")) +
labs(title = "XU HƯỚNG DOANH THU VÀ LỢI NHUẬN REE", x = "Năm", y = "Tỷ đồng") +
theme_minimal()
print(p1)# 2. Biểu đồ cột - So sánh tài sản và vốn chủ sở hữu
cat("=== 2. BIỂU ĐỒ SO SÁNH TÀI SẢN VÀ VỐN CHỦ SỞ HỮU ===\n")## === 2. BIỂU ĐỒ SO SÁNH TÀI SẢN VÀ VỐN CHỦ SỞ HỮU ===
p2 <- ggplot(ree_viz, aes(x = Year)) +
geom_col(aes(y = Total_assets, fill = "Tổng tài sản"), alpha = 0.7) +
geom_col(aes(y = Total_equity, fill = "Vốn chủ sở hữu"), alpha = 0.7) +
scale_fill_manual(values = c("Tổng tài sản" = "#4682B4", "Vốn chủ sở hữu" = "#4ECDC4")) +
labs(title = "SO SÁNH TÀI SẢN VÀ VỐN CHỦ SỞ HỮU", x = "Năm", y = "Tỷ đồng") +
theme_minimal()
print(p2)## === 3. BIỂU ĐỒ TỶ LỆ SINH LỜI ===
p3 <- ggplot(ree_viz, aes(x = Year)) +
geom_line(aes(y = ROE, color = "ROE"), size = 1.5) +
geom_line(aes(y = ROA, color = "ROA"), size = 1.5) +
geom_point(aes(y = ROE), size = 2, color = "#FF6B6B") +
geom_point(aes(y = ROA), size = 2, color = "#4ECDC4") +
scale_color_manual(values = c("ROE" = "#FF6B6B", "ROA" = "#4ECDC4")) +
scale_y_continuous(labels = percent) +
labs(title = "TỶ LỆ SINH LỜI ROE VÀ ROA", x = "Năm", y = "Tỷ lệ") +
theme_minimal()
print(p3)## === 4. BIỂU ĐỒ THANH KHOẢN VÀ ĐÒN BẨY ===
p4 <- ggplot(ree_viz, aes(x = Year)) +
geom_line(aes(y = Current_ratio, color = "Tỷ lệ thanh khoản"), size = 1.5) +
geom_line(aes(y = Debt_ratio, color = "Tỷ lệ nợ"), size = 1.5) +
geom_hline(yintercept = 1, linetype = "dashed", color = "red") +
scale_color_manual(values = c("Tỷ lệ thanh khoản" = "#2E8B57", "Tỷ lệ nợ" = "#DC143C")) +
labs(title = "THANH KHOẢN VÀ ĐÒN BẨY TÀI CHÍNH", x = "Năm", y = "Tỷ lệ") +
theme_minimal()
print(p4)## === 5. BIỂU ĐỒ BIÊN LỢI NHUẬN ===
p5 <- ggplot(ree_viz, aes(x = Year)) +
geom_col(aes(y = Gross_margin, fill = "Biên lợi nhuận gộp"), alpha = 0.7) +
geom_col(aes(y = Net_margin, fill = "Biên lợi nhuận ròng"), alpha = 0.7) +
scale_fill_manual(values = c("Biên lợi nhuận gộp" = "#45B7D1", "Biên lợi nhuận ròng" = "#96CEB4")) +
scale_y_continuous(labels = percent) +
labs(title = "BIÊN LỢI NHUẬN QUA CÁC NĂM", x = "Năm", y = "Tỷ lệ") +
theme_minimal()
print(p5)# 6. Biểu đồ scatter - Mối quan hệ tài sản và lợi nhuận
cat("=== 6. BIỂU ĐỒ PHÂN TÁN - TÀI SẢN VÀ LỢI NHUẬN ===\n")## === 6. BIỂU ĐỒ PHÂN TÁN - TÀI SẢN VÀ LỢI NHUẬN ===
p6 <- ggplot(ree_viz, aes(x = Total_assets, y = Net_profit)) +
geom_point(size = 4, color = "#FF6B6B", alpha = 0.7) +
geom_smooth(method = "lm", se = FALSE, color = "#2E8B57") +
geom_text(aes(label = Year), vjust = -0.8, size = 3) +
labs(title = "MỐI QUAN HỆ TÀI SẢN VÀ LỢI NHUẬN", x = "Tổng tài sản (tỷ đồng)", y = "Lợi nhuận (tỷ đồng)") +
theme_minimal()
print(p6)## === 7. BIỂU ĐỒ VÙNG - TĂNG TRƯỞNG TÀI SẢN ===
p7 <- ggplot(ree_viz, aes(x = Year, y = Total_assets)) +
geom_area(fill = "#4ECDC4", alpha = 0.5) +
geom_line(color = "#2E8B57", size = 1.2) +
geom_point(size = 2, color = "#2E8B57") +
labs(title = "TĂNG TRƯỞNG TỔNG TÀI SẢN", x = "Năm", y = "Tỷ đồng") +
theme_minimal()
print(p7)# 8. Biểu đồ stacked bar - Cơ cấu tài chính
cat("=== 8. BIỂU ĐỒ STACKED BAR - CƠ CẤU TÀI CHÍNH ===\n")## === 8. BIỂU ĐỒ STACKED BAR - CƠ CẤU TÀI CHÍNH ===
structure_data <- ree_viz %>%
select(Year, Total_equity, Total_assets, Debt_ratio) %>%
mutate(
Debt_value = Total_assets * Debt_ratio,
Equity_value = Total_equity
) %>%
select(Year, "Vốn chủ sở hữu" = Equity_value, "Nợ phải trả" = Debt_value) %>%
pivot_longer(-Year, names_to = "Component", values_to = "Value")
p8 <- ggplot(structure_data, aes(x = Year, y = Value, fill = Component)) +
geom_bar(stat = "identity", position = "stack") +
scale_fill_manual(values = c("Vốn chủ sở hữu" = "#4ECDC4", "Nợ phải trả" = "#FF6B6B")) +
labs(title = "CƠ CẤU NGUỒN VỐN", x = "Năm", y = "Tỷ đồng") +
theme_minimal()
print(p8)## === 9. BIỂU ĐỒ HEATMAP TƯƠNG QUAN ===
library(reshape2)
cor_data <- cor(ree_viz %>% select(-Year))
melted_cor <- melt(cor_data)
p9 <- ggplot(melted_cor, aes(x = Var1, y = Var2, fill = value)) +
geom_tile() +
geom_text(aes(label = round(value, 2)), color = "white", size = 4) +
scale_fill_gradient2(low = "#DC143C", high = "#2E8B57", mid = "white", midpoint = 0) +
labs(title = "MA TRẬN TƯƠNG QUAN CÁC CHỈ TIÊU", x = "", y = "") +
theme_minimal() +
theme(axis.text.x = element_text(angle = 45, hjust = 1))
print(p9)# 10. Biểu đồ boxplot - Phân phối các tỷ lệ
cat("=== 10. BIỂU ĐỒ BOXPLOT - PHÂN PHỐI CÁC TỶ LỆ ===\n")## === 10. BIỂU ĐỒ BOXPLOT - PHÂN PHỐI CÁC TỶ LỆ ===
ratio_data <- ree_viz %>%
select(ROE, ROA, Current_ratio, Gross_margin, Net_margin) %>%
pivot_longer(everything(), names_to = "Ratio", values_to = "Value")
p10 <- ggplot(ratio_data, aes(x = Ratio, y = Value, fill = Ratio)) +
geom_boxplot(alpha = 0.7) +
geom_jitter(width = 0.2, alpha = 0.6) +
labs(title = "PHÂN PHỐI CÁC TỶ LỆ TÀI CHÍNH", x = "Chỉ số", y = "Giá trị") +
theme_minimal() +
theme(legend.position = "none")
print(p10)## === ĐÃ HOÀN THÀNH 10 BIỂU ĐỒ ĐẦU TIÊN ===
# 11. Biểu đồ histogram - Phân phối lợi nhuận
cat("=== 11. BIỂU ĐỒ HISTOGRAM - PHÂN PHỐI LỢI NHUẬN ===\n")## === 11. BIỂU ĐỒ HISTOGRAM - PHÂN PHỐI LỢI NHUẬN ===
p11 <- ggplot(ree_viz, aes(x = Net_profit)) +
geom_histogram(binwidth = 0.3, fill = "#4ECDC4", alpha = 0.7, color = "white") +
geom_density(aes(y = after_stat(count) * 0.3), color = "#2E8B57", size = 1) +
geom_vline(aes(xintercept = mean(Net_profit)), color = "#DC143C", linetype = "dashed", size = 1) +
labs(title = "PHÂN PHỐI LỢI NHUẬN REE", x = "Lợi nhuận (tỷ đồng)", y = "Tần suất") +
theme_minimal()
print(p11)# 12. Biểu đồ violin - So sánh các biên lợi nhuận
cat("=== 12. BIỂU ĐỒ VIOLIN - SO SÁNH CÁC BIÊN LỢI NHUẬN ===\n")## === 12. BIỂU ĐỒ VIOLIN - SO SÁNH CÁC BIÊN LỢI NHUẬN ===
margin_data <- ree_viz %>%
select(Gross_margin, Net_margin) %>%
pivot_longer(everything(), names_to = "Margin_Type", values_to = "Value")
p12 <- ggplot(margin_data, aes(x = Margin_Type, y = Value, fill = Margin_Type)) +
geom_violin(alpha = 0.7) +
geom_boxplot(width = 0.1, fill = "white", alpha = 0.8) +
geom_jitter(width = 0.05, size = 2, alpha = 0.6) +
scale_fill_manual(values = c("Gross_margin" = "#45B7D1", "Net_margin" = "#96CEB4")) +
scale_x_discrete(labels = c("Biên lợi nhuận gộp", "Biên lợi nhuận ròng")) +
scale_y_continuous(labels = percent) +
labs(title = "SO SÁNH PHÂN PHỐI CÁC BIÊN LỢI NHUẬN", x = "", y = "Tỷ lệ") +
theme_minimal() +
theme(legend.position = "none")
print(p12)# 13. Biểu đồ lollipop - Tăng trưởng qua các năm
cat("=== 13. BIỂU ĐỒ LOLLIPOP - TĂNG TRƯỞNG QUA CÁC NĂM ===\n")## === 13. BIỂU ĐỒ LOLLIPOP - TĂNG TRƯỞNG QUA CÁC NĂM ===
growth_data <- ree_viz %>%
mutate(Growth = (Revenue - lag(Revenue)) / lag(Revenue) * 100) %>%
filter(!is.na(Growth))
p13 <- ggplot(growth_data, aes(x = Year, y = Growth)) +
geom_segment(aes(x = Year, xend = Year, y = 0, yend = Growth), color = "#4682B4") +
geom_point(size = 4, color = "#DC143C") +
geom_text(aes(label = paste0(round(Growth, 1), "%")), vjust = -1, size = 3, fontface = "bold") +
geom_hline(yintercept = 0, color = "black", linetype = "dashed") +
labs(title = "TĂNG TRƯỞNG DOANH THU HÀNG NĂM", x = "Năm", y = "Tăng trưởng (%)") +
theme_minimal()
print(p13)# 14. Biểu đồ pie - Cơ cấu tài sản năm 2024
cat("=== 14. BIỂU ĐỒ PIE - CƠ CẤU TÀI SẢN NĂM 2024 ===\n")## === 14. BIỂU ĐỒ PIE - CƠ CẤU TÀI SẢN NĂM 2024 ===
latest_year <- ree_viz %>% filter(Year == 2024)
pie_data <- data.frame(
Category = c("Tài sản ngắn hạn", "Tài sản dài hạn"),
Value = c(
latest_year$Total_assets * 0.4, # Giả định 40% ngắn hạn
latest_year$Total_assets * 0.6 # Giả định 60% dài hạn
)
)
p14 <- ggplot(pie_data, aes(x = "", y = Value, fill = Category)) +
geom_bar(stat = "identity", width = 1, alpha = 0.8) +
coord_polar("y", start = 0) +
geom_text(aes(label = paste0(round(Value/sum(Value)*100, 1), "%")),
position = position_stack(vjust = 0.5), color = "white", size = 5) +
scale_fill_manual(values = c("Tài sản ngắn hạn" = "#FF6B6B", "Tài sản dài hạn" = "#4ECDC4")) +
labs(title = "CƠ CẤU TÀI SẢN NĂM 2024", fill = "Loại tài sản") +
theme_void() +
theme(plot.title = element_text(hjust = 0.5, face = "bold"))
print(p14)# 15. Biểu đồ area stacked - Phát triển tài chính qua các năm
cat("=== 15. BIỂU ĐỒ AREA STACKED - PHÁT TRIỂN TÀI CHÍNH ===\n")## === 15. BIỂU ĐỒ AREA STACKED - PHÁT TRIỂN TÀI CHÍNH ===
financial_growth <- ree_viz %>%
select(Year, Revenue, Net_profit, Total_assets) %>%
pivot_longer(-Year, names_to = "Indicator", values_to = "Value")
p15 <- ggplot(financial_growth, aes(x = Year, y = Value, fill = Indicator)) +
geom_area(alpha = 0.7) +
scale_fill_manual(values = c("Revenue" = "#2E8B57", "Net_profit" = "#DC143C", "Total_assets" = "#4682B4"),
labels = c("Doanh thu", "Lợi nhuận", "Tổng tài sản")) +
labs(title = "PHÁT TRIỂN TÀI CHÍNH QUA CÁC NĂM", x = "Năm", y = "Tỷ đồng") +
theme_minimal()
print(p15)# 16. Biểu đồ dot plot - So sánh hiệu quả theo năm
cat("=== 16. BIỂU ĐỒ DOT PLOT - SO SÁNH HIỆU QUẢ ===\n")## === 16. BIỂU ĐỒ DOT PLOT - SO SÁNH HIỆU QUẢ ===
efficiency_data <- ree_viz %>%
select(Year, ROE, ROA, Net_margin) %>%
pivot_longer(-Year, names_to = "Ratio", values_to = "Value")
p16 <- ggplot(efficiency_data, aes(x = Value, y = reorder(Year, Value), color = Ratio)) +
geom_point(size = 4, alpha = 0.7) +
geom_segment(aes(x = 0, xend = Value, y = Year, yend = Year), alpha = 0.5) +
facet_wrap(~Ratio, scales = "free_x",
labeller = as_labeller(c("ROE" = "ROE", "ROA" = "ROA", "Net_margin" = "Biên lợi nhuận"))) +
scale_color_manual(values = c("ROE" = "#FF6B6B", "ROA" = "#4ECDC4", "Net_margin" = "#45B7D1")) +
scale_x_continuous(labels = percent) +
labs(title = "SO SÁNH HIỆU QUẢ HOẠT ĐỘNG THEO NĂM", x = "Tỷ lệ", y = "Năm") +
theme_minimal() +
theme(legend.position = "none")
print(p16)# 17. Biểu đồ ridge line - Mật độ phân phối
cat("=== 17. BIỂU ĐỒ RIDGE LINE - MẬT ĐỘ PHÂN PHỐI ===\n")## === 17. BIỂU ĐỒ RIDGE LINE - MẬT ĐỘ PHÂN PHỐI ===
library(ggridges)
ridge_data <- ree_viz %>%
select(ROE, ROA, Current_ratio, Gross_margin) %>%
pivot_longer(everything(), names_to = "Metric", values_to = "Value")
p17 <- ggplot(ridge_data, aes(x = Value, y = Metric, fill = Metric)) +
geom_density_ridges(alpha = 0.7, scale = 0.9) +
scale_fill_manual(values = c("ROE" = "#FF6B6B", "ROA" = "#4ECDC4",
"Current_ratio" = "#4682B4", "Gross_margin" = "#45B7D1")) +
labs(title = "MẬT ĐỘ PHÂN PHỐI CÁC CHỈ SỐ TÀI CHÍNH", x = "Giá trị", y = "") +
theme_minimal() +
theme(legend.position = "none")
print(p17)# 18. Biểu đồ waterfall - Thay đổi doanh thu
cat("=== 18. BIỂU ĐỒ WATERFALL - THAY ĐỔI DOANH THU ===\n")## === 18. BIỂU ĐỒ WATERFALL - THAY ĐỔI DOANH THU ===
waterfall_data <- ree_viz %>%
mutate(
Change = Revenue - lag(Revenue),
Change = ifelse(is.na(Change), Revenue[1], Change),
Type = ifelse(Change >= 0, "Tăng", "Giảm"),
End = cumsum(Change),
Start = lag(End, default = 0)
)
p18 <- ggplot(waterfall_data) +
geom_segment(aes(x = Year, xend = Year, y = Start, yend = End, color = Type), size = 8) +
geom_point(aes(x = Year, y = End), size = 2, color = "white") +
geom_text(aes(x = Year, y = End, label = round(Revenue, 1)), vjust = -1, size = 3) +
scale_color_manual(values = c("Tăng" = "#2E8B57", "Giảm" = "#DC143C")) +
labs(title = "PHÂN TÍCH THAY ĐỔI DOANH THU QUA CÁC NĂM", x = "Năm", y = "Doanh thu (tỷ đồng)") +
theme_minimal()
print(p18)# 19. Biểu đồ calendar heatmap - Hiệu suất theo thời gian
cat("=== 19. BIỂU ĐỒ CALENDAR HEATMAP - HIỆU SUẤT THEO THỜI GIAN ===\n")## === 19. BIỂU ĐỒ CALENDAR HEATMAP - HIỆU SUẤT THEO THỜI GIAN ===
calendar_data <- ree_viz %>%
mutate(
ROE_Level = cut(ROE, breaks = 4, labels = c("Thấp", "Trung bình", "Khá", "Cao"))
)
p19 <- ggplot(calendar_data, aes(x = Year, y = 1, fill = ROE_Level)) +
geom_tile(color = "white", size = 1) +
geom_text(aes(label = paste0(round(ROE*100, 1), "%")), color = "white", fontface = "bold") +
scale_fill_manual(values = c("Thấp" = "#DC143C", "Trung bình" = "#FFA500",
"Khá" = "#90EE90", "Cao" = "#2E8B57")) +
labs(title = "HIỆU SUẤT ROE QUA CÁC NĂM", x = "Năm", y = "", fill = "Mức ROE") +
theme_minimal() +
theme(axis.text.y = element_blank(),
panel.grid = element_blank())
print(p19)# 20. Biểu đồ tổng quan hiệu quả - Đơn giản và chắc chắn
cat("=== 20. BIỂU ĐỒ TỔNG QUAN HIỆU QUẢ ===\n")## === 20. BIỂU ĐỒ TỔNG QUAN HIỆU QUẢ ===
overview_data <- ree_viz %>%
summarise(
"Tăng trưởng\ndoanh thu" = (last(Revenue) - first(Revenue)) / first(Revenue) * 100,
"ROE\ntrung bình" = mean(ROE) * 100,
"ROA\ntrung bình" = mean(ROA) * 100,
"Biên lợi nhuận\ntrung bình" = mean(Net_margin) * 100
) %>%
pivot_longer(everything(), names_to = "Metric", values_to = "Value")
p20 <- ggplot(overview_data, aes(x = Metric, y = Value, fill = Metric)) +
geom_col(alpha = 0.8, width = 0.7) +
geom_text(aes(label = paste0(round(Value, 1), "%")), vjust = -0.5, size = 4, fontface = "bold") +
scale_fill_manual(values = c("#2E8B57", "#4ECDC4", "#4682B4", "#45B7D1")) +
labs(title = "TỔNG QUAN HIỆU QUẢ HOẠT ĐỘNG 2015-2024",
subtitle = "So sánh các chỉ số trung bình trong giai đoạn 10 năm",
x = "", y = "Tỷ lệ (%)") +
theme_minimal() +
theme(legend.position = "none",
axis.text.x = element_text(size = 10, face = "bold"),
plot.title = element_text(hjust = 0.5, face = "bold"),
plot.subtitle = element_text(hjust = 0.5))
print(p20)