Mục tiêu trọng tâm của việc phân tích hồ sơ vay là nhằm định lượng hóa rủi ro tín dụng một cách khách quan và có hệ thống, vượt qua các phương pháp thẩm định định tính truyền thống. Cụ thể, phân tích hướng đến việc xây dựng các mô hình chấm điểm tín dụng (credit scoring) hiệu quả, sử dụng dữ liệu lịch sử và các thuật toán thống kê (hoặc Machine Learning) để ước tính chính xác Xác suất vỡ nợ (Probability of Default - PD) của từng khách hàng. Kết quả phân tích này không chỉ phục vụ mục tiêu tức thời là hỗ trợ ra quyết định phê duyệt hay từ chối khoản vay, mà còn là cơ sở để xác định các yếu tố ảnh hưởng then chốt (key risk drivers) đến khả năng trả nợ. Thông qua đó, tổ chức tín dụng có thể thực thi chiến lược định giá dựa trên rủi ro (risk-based pricing), áp dụng các mức lãi suất, hạn mức và yêu cầu tài sản đảm bảo tương xứng với rủi ro, nhằm tối ưu hóa lợi nhuận và đảm bảo sự an toàn của danh mục cho vay. ## Giải thích thuật ngữ (biến)
TARGET: Biến mục tiêu (1 = khách hàng gặp khó khăn thanh toán, 0 = thanh toán đúng hạn) AMT_CREDIT: Tổng số tiền vay AMT_INCOME_TOTAL: Tổng thu nhập hàng năm DAYS_BIRTH: Số ngày từ ngày sinh đến thời điểm xin vay (âm) EXT_SOURCE: Điểm tín dụng từ nguồn bên ngoài (1-3) 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)
# Đọc dữ liệu
loan_data <- read.csv("C:/Users/HI HELEN/Downloads/dataFOR_I_am_atomic (1).csv", sep = ";")
# Kiểm tra cấu trúc cơ bản của dataset
cat("=== THÔNG TIN CƠ BẢN VỀ BỘ DỮ LIỆU ===\n")## === THÔNG TIN CƠ BẢN VỀ BỘ DỮ LIỆU ===
## Số quan sát (dòng): 307511
## Số biến (cột): 29
## Số quan sát trùng lặp: 0
## DỮ LIỆU THIẾU THEO TỪNG CỘT:
missing_summary <- sapply(loan_data, function(x) sum(is.na(x)))
print(head(missing_summary, 15)) # Hiển thị 15 cột đầu## SK_ID_CURR TARGET NAME_CONTRACT_TYPE CODE_GENDER
## 0 0 0 0
## FLAG_OWN_CAR FLAG_OWN_REALTY CNT_CHILDREN AMT_INCOME_TOTAL
## 0 0 0 0
## AMT_CREDIT AMT_ANNUITY AMT_GOODS_PRICE NAME_TYPE_SUITE
## 0 12 278 0
## NAME_INCOME_TYPE NAME_EDUCATION_TYPE NAME_FAMILY_STATUS
## 0 0 0
# Giải thích ý nghĩa các biến trong dữ liệu
thong_tin_bien <- data.frame(
"Tên Biến" = c("TARGET", "AMT_INCOME_TOTAL", "AMT_CREDIT", "CODE_GENDER"),
"Mô Tả" = c(
"Biến mục tiêu (1: Khó khăn thanh toán, 0: Thanh toán tốt)",
"Tổng thu nhập hàng năm của khách hàng",
"Số tiền tín dụng của khoản vay",
"Giới tính (M: Nam, F: Nữ)"
)
)
cat("=== Ý NGHĨA CÁC BIẾN QUAN TRỌNG TRONG DATASET ===\n")## === Ý NGHĨA CÁC BIẾN QUAN TRỌNG TRONG DATASET ===
## Tên.Biến Mô.Tả
## 1 TARGET Biến mục tiêu (1: Khó khăn thanh toán, 0: Thanh toán tốt)
## 2 AMT_INCOME_TOTAL Tổng thu nhập hàng năm của khách hàng
## 3 AMT_CREDIT Số tiền tín dụng của khoản vay
## 4 CODE_GENDER Giới tính (M: Nam, F: Nữ)
## === CẤU TRÚC DỮ LIỆU ===
## 'data.frame': 307511 obs. of 4 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" ...
##
## === THỐNG KÊ MÔ TẢ ===
## AMT_INCOME_TOTAL AMT_CREDIT
## Length:307511 Min. : 45000
## Class :character 1st Qu.: 270000
## Mode :character Median : 513531
## Mean : 599026
## 3rd Qu.: 808650
## Max. :4050000
# Chuyển đổi kiểu dữ liệu và xử lý dữ liệu thiếu
data_clean <- loan_data %>% # SỬA: data -> loan_data
mutate(
# Chuyển đổi sang kiểu số
AMT_INCOME_TOTAL = as.numeric(AMT_INCOME_TOTAL),
AMT_CREDIT = as.numeric(AMT_CREDIT),
# Xử lý dữ liệu phân loại
CODE_GENDER = as.factor(CODE_GENDER),
TARGET = as.factor(TARGET),
# Tạo biến mới phục vụ phân tích
INCOME_CREDIT_RATIO = AMT_INCOME_TOTAL / AMT_CREDIT,
# Phân loại thu nhập
INCOME_GROUP = case_when(
AMT_INCOME_TOTAL < 100000 ~ "Thu nhập thấp",
AMT_INCOME_TOTAL < 200000 ~ "Thu nhập trung bình",
TRUE ~ "Thu nhập cao"
),
# Phân loại số tiền vay
CREDIT_GROUP = case_when(
AMT_CREDIT < 500000 ~ "Vay nhỏ",
AMT_CREDIT < 1000000 ~ "Vay trung bình",
TRUE ~ "Vay lớn"
)
) %>%
filter(!is.na(AMT_INCOME_TOTAL) & !is.na(AMT_CREDIT)) # Loại bỏ dòng thiếu dữ liệu
# Kiểm tra lại cấu trúc sau khi chuyển đổi
cat("=== CẤU TRÚC DỮ LIỆU SAU KHI LÀM SẠCH ===\n")## === CẤU TRÚC DỮ LIỆU SAU KHI LÀM SẠCH ===
str(data_clean[, c("TARGET", "AMT_INCOME_TOTAL", "AMT_CREDIT", "CODE_GENDER",
"INCOME_CREDIT_RATIO", "INCOME_GROUP", "CREDIT_GROUP")])## 'data.frame': 307502 obs. of 7 variables:
## $ TARGET : Factor w/ 2 levels "0","1": 2 1 1 1 1 1 1 1 1 1 ...
## $ AMT_INCOME_TOTAL : num 202500 270000 67500 135000 121500 ...
## $ AMT_CREDIT : num 406598 1293503 135000 312683 513000 ...
## $ CODE_GENDER : Factor w/ 3 levels "F","M","XNA": 2 1 2 1 2 2 1 2 1 2 ...
## $ INCOME_CREDIT_RATIO: num 0.498 0.209 0.5 0.432 0.237 ...
## $ INCOME_GROUP : chr "Thu nhập cao" "Thu nhập cao" "Thu nhập thấp" "Thu nhập trung bình" ...
## $ CREDIT_GROUP : chr "Vay nhỏ" "Vay lớn" "Vay nhỏ" "Vay nhỏ" ...
##
## === 10 DÒNG ĐẦU TIÊN SAU KHI XỬ LÝ ===
## TARGET AMT_INCOME_TOTAL AMT_CREDIT CODE_GENDER
## 1 1 202500 406597.5 M
## 2 0 270000 1293502.5 F
## 3 0 67500 135000.0 M
## 4 0 135000 312682.5 F
## 5 0 121500 513000.0 M
## 6 0 99000 490495.5 M
## 7 0 171000 1560726.0 F
## 8 0 360000 1530000.0 M
## 9 0 112500 1019610.0 F
## 10 0 135000 405000.0 M
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) +
geom_text(aes(label = ..count..), stat = "count", vjust = -0.5, size = 5) +
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",
fill = "Tình trạ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 = "top")# CHUYỂN ĐỔI DỮ LIỆU TRƯỚC KHI VẼ BIỂU ĐỒ
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))
p1 <- 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) +
labs(title = "Phân phối thu nhập theo tình trạng thanh toán",
x = "Tổng thu nhập",
y = "Tần suất",
fill = "Tình trạng") +
theme_minimal()
p2 <- 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) +
labs(title = "Boxplot thu nhập theo tình trạng thanh toán",
x = "Tình trạng thanh toán",
y = "Tổng thu nhập") +
theme_minimal() +
theme(legend.position = "none")
p1 + p2 + plot_layout(ncol = 2)# ĐẢM BẢO DỮ LIỆU LÀ NUMERIC VÀ LOẠI BỎ GIÁ TRỊ <= 0
loan_data_log <- loan_data_numeric %>%
filter(AMT_INCOME_TOTAL > 0 & AMT_CREDIT > 0) %>% # LOẠI BỎ GIÁ TRỊ <= 0 VÌ KHÔNG THỂ LOG
mutate(
LOG_INCOME = log10(AMT_INCOME_TOTAL), # TÍNH LOG TRƯỚC
LOG_CREDIT = log10(AMT_CREDIT)
)
ggplot(loan_data_log, aes(x = LOG_INCOME, y = LOG_CREDIT, color = factor(TARGET))) +
geom_point(alpha = 0.6, size = 2) +
geom_smooth(method = "lm", se = FALSE, aes(group = factor(TARGET))) +
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)) + # CHUYỂN NGƯỢC LẠI ĐỂ HIỂN THỊ
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))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")))
ggplot(contract_data, aes(x = NAME_CONTRACT_TYPE, y = Count, fill = TARGET)) +
geom_bar(stat = "identity", position = "fill", alpha = 0.8) +
geom_text(aes(label = Count), position = position_fill(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_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",
fill = "Tình trạng thanh toán") +
coord_flip() +
theme_minimal(base_size = 12) +
theme(plot.title = element_text(face = "bold", hjust = 0.5),
plot.subtitle = element_text(hjust = 0.5))# XỬ LÝ DỮ LIỆU GIỚI TÍNH TRƯỚC
gender_data <- loan_data %>%
mutate(
# XỬ LÝ GIÁ TRỊ XNA TRONG CODE_GENDER
CODE_GENDER = case_when(
CODE_GENDER %in% c("M", "F") ~ CODE_GENDER,
TRUE ~ "Other" # GỘP CÁC GIÁ TRỊ KHÁC THÀNH "Other"
),
# CHUYỂN ĐỔI TARGET VỚI ĐÚNG SỐ LƯỢNG LABELS
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)
ggplot(gender_data, aes(x = CODE_GENDER, y = Count, fill = TARGET_LABEL)) +
geom_bar(stat = "identity", position = "stack", alpha = 0.8) +
geom_text(aes(label = paste0(round(Percentage, 1), "%")),
position = position_stack(vjust = 0.5),
color = "white", size = 5, 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",
fill = "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))# Chuyển đổi DAYS_BIRTH sang tuổi
loan_data$AGE <- round(-loan_data$DAYS_BIRTH / 365.25, 1)
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 độ",
fill = "Tình trạng thanh toán",
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))# Xử lý DAYS_EMPLOYED (giá trị lớn bất thường)
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")))
ggplot(employment_data, aes(x = EMPLOYMENT_STATUS, y = Count, fill = TARGET)) +
geom_bar(stat = "identity", position = "dodge", alpha = 0.8) +
geom_text(aes(label = paste0(Count, "\n(", round(Percentage, 1), "%)")),
position = position_dodge(width = 0.9),
vjust = -0.3, size = 3.5) +
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",
fill = "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))# CHUYỂN ĐỔI TẤT CẢ EXT_SOURCE VỀ NUMERIC TRƯỚC
ext_source_long <- loan_data %>%
mutate(
EXT_SOURCE_1 = as.numeric(EXT_SOURCE_1),
EXT_SOURCE_2 = as.numeric(EXT_SOURCE_2),
EXT_SOURCE_3 = as.numeric(EXT_SOURCE_3),
TARGET_LABEL = factor(TARGET, labels = c("Thanh toán tốt", "Khó khăn thanh toán"))
) %>%
select(EXT_SOURCE_1, EXT_SOURCE_2, EXT_SOURCE_3, TARGET_LABEL) %>%
pivot_longer(
cols = c(EXT_SOURCE_1, EXT_SOURCE_2, EXT_SOURCE_3),
names_to = "Score_Type",
values_to = "Score_Value"
) %>%
filter(!is.na(Score_Value)) # LOẠI BỎ GIÁ TRỊ NA
# VẼ BOXPLOT
ggplot(ext_source_long, aes(x = TARGET_LABEL, y = Score_Value, fill = TARGET_LABEL)) +
geom_boxplot(alpha = 0.7, outlier.alpha = 0.5) +
facet_wrap(~ Score_Type, ncol = 3,
labeller = as_labeller(c(
"EXT_SOURCE_1" = "EXT_SOURCE_1",
"EXT_SOURCE_2" = "EXT_SOURCE_2",
"EXT_SOURCE_3" = "EXT_SOURCE_3"
))) +
scale_fill_manual(values = c("Thanh toán tốt" = "#2E8B57", "Khó khăn thanh toán" = "#DC143C")) +
labs(title = "PHÂN PHỐI ĐIỂM TÍN DỤNG THEO TÌNH TRẠNG THANH TOÁN",
subtitle = "So sánh phân vị điểm tín dụng giữa hai nhóm",
x = "Tình trạng thanh toán",
y = "Điểm tín dụng",
fill = "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),
legend.position = "none",
axis.text.x = element_text(angle = 0, hjust = 0.5))# XỬ LÝ DỮ LIỆU - TẠO TARGET_LABEL TRƯỚC KHI GROUP
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()
# KIỂM TRA CÁC NHÓM CÓ ÍT DỮ LIỆU
small_groups <- income_type_data %>%
group_by(NAME_INCOME_TYPE) %>%
summarise(Total_Count = sum(Count)) %>%
filter(Total_Count < 10)
if(nrow(small_groups) > 0) {
cat("Các nhóm có ít dữ liệu (sẽ được gộp vào 'Other'):\n")
print(small_groups)
}## Các nhóm có ít dữ liệu (sẽ được gộp vào 'Other'):
## # A tibble: 1 × 2
## NAME_INCOME_TYPE Total_Count
## <chr> <int>
## 1 Maternity leave 5
# GỘP CÁC NHÓM NHỎ THÀNH 'Other'
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))
ggplot(income_type_clean, aes(x = reorder(NAME_INCOME_TYPE_CLEAN, Total), y = Count, fill = TARGET_LABEL)) +
geom_bar(stat = "identity", alpha = 0.8) +
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",
fill = "Tình trạng thanh toán") +
coord_flip() +
theme_minimal(base_size = 12) +
theme(plot.title = element_text(face = "bold", hjust = 0.5),
plot.subtitle = element_text(hjust = 0.5))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")))
ggplot(education_data, aes(x = reorder(NAME_EDUCATION_TYPE, Total), y = Count, fill = TARGET)) +
geom_bar(stat = "identity", alpha = 0.8) +
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",
fill = "Tình trạng thanh toán") +
coord_flip() +
theme_minimal(base_size = 12) +
theme(plot.title = element_text(face = "bold", hjust = 0.5),
plot.subtitle = element_text(hjust = 0.5))# CHỈ LẤY CÁC NHÓM CÓ ĐỦ DỮ LIỆU
main_family_status <- loan_data %>%
count(NAME_FAMILY_STATUS) %>%
filter(n >= 10) %>% # CHỈ GIỮ LẠI NHÓM CÓ >= 10 QUAN SÁT
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))
ggplot(family_data, aes(x = reorder(NAME_FAMILY_STATUS, Total), y = Count, fill = TARGET_LABEL)) +
geom_bar(stat = "identity", alpha = 0.8) +
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",
fill = "Tình trạng thanh toán") +
coord_flip() +
theme_minimal(base_size = 12) +
theme(plot.title = element_text(face = "bold", hjust = 0.5),
plot.subtitle = element_text(hjust = 0.5))# THÔNG BÁO CÁC NHÓM ĐÃ BỊ LOẠI BỎ
excluded_family <- loan_data %>%
count(NAME_FAMILY_STATUS) %>%
filter(n < 10)
if(nrow(excluded_family) > 0) {
cat("Các nhóm tình trạng hôn nhân bị loại bỏ (ít dữ liệu):\n")
print(excluded_family)
}## Các nhóm tình trạng hôn nhân bị loại bỏ (ít dữ liệu):
## NAME_FAMILY_STATUS n
## 1 Unknown 2
# GỘP CÁC NHÓM CÓ ÍT CON THÀNH NHÓM LỚN HƠN
children_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")),
# GỘP SỐ CON THÀNH NHÓM
CHILDREN_GROUP = case_when(
CNT_CHILDREN == 0 ~ "0 con",
CNT_CHILDREN == 1 ~ "1 con",
CNT_CHILDREN == 2 ~ "2 con",
CNT_CHILDREN >= 3 ~ "3+ con" # GỘP TẤT CẢ >= 3 CON
)
) %>%
group_by(CHILDREN_GROUP, TARGET_LABEL) %>%
summarise(Count = n(), .groups = 'drop') %>%
group_by(CHILDREN_GROUP) %>%
mutate(Percentage = Count / sum(Count) * 100,
Total = sum(Count))
ggplot(children_data, aes(x = factor(CHILDREN_GROUP, levels = c("0 con", "1 con", "2 con", "3+ con")),
y = Count, fill = TARGET_LABEL)) +
geom_bar(stat = "identity", position = "dodge", alpha = 0.8) +
geom_text(aes(label = paste0(round(Percentage, 1), "%")),
position = position_dodge(width = 0.9),
vjust = -0.3, 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 = "SỐ LƯỢNG CON CÁI VÀ TÌNH TRẠNG THANH TOÁN",
subtitle = "Phân tích theo nhóm số con (các nhóm >= 3 con được gộp chung)",
x = "Số lượng con cái",
y = "Số lượng khách hàng",
fill = "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))housing_data <- loan_data %>%
group_by(NAME_HOUSING_TYPE, TARGET) %>%
summarise(Count = n(), .groups = 'drop') %>%
group_by(NAME_HOUSING_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")))
ggplot(housing_data, aes(x = reorder(NAME_HOUSING_TYPE, Total), y = Count, fill = TARGET)) +
geom_bar(stat = "identity", alpha = 0.8) +
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 NHÀ Ở VÀ TÌNH TRẠNG THANH TOÁN",
subtitle = "Phân phối theo điều kiện nhà ở",
x = "Loại hình nhà ở",
y = "Số lượng khách hàng",
fill = "Tình trạng thanh toán") +
coord_flip() +
theme_minimal(base_size = 12) +
theme(plot.title = element_text(face = "bold", hjust = 0.5),
plot.subtitle = element_text(hjust = 0.5))ggplot(loan_data, aes(x = AMT_ANNUITY, 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")) +
scale_x_continuous(labels = comma) +
labs(title = "PHÂN PHỐI SỐ TIỀN TRẢ HÀNG NĂM (ANNUITY)",
subtitle = "So sánh giữa hai nhóm thanh toán",
x = "Số tiền trả hàng năm",
y = "Mật độ",
fill = "Tình trạng thanh toán",
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))# ĐẢM BẢO DỮ LIỆU LÀ NUMERIC TRƯỚC KHI TÍNH TOÁN
loan_data_ratio <- 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),
AMT_INCOME_TOTAL > 0, # LOẠI BỎ THU NHẬP <= 0
AMT_CREDIT > 0 # LOẠI BỎ SỐ TIỀN VAY <= 0
) %>%
mutate(
CREDIT_TO_INCOME_RATIO = AMT_CREDIT / AMT_INCOME_TOTAL,
TARGET_LABEL = factor(TARGET, labels = c("Thanh toán tốt", "Khó khăn thanh toán"))
) %>%
filter(CREDIT_TO_INCOME_RATIO <= 20) # LOẠI BỎ OUTLIERS LỚN
ggplot(loan_data_ratio, aes(x = CREDIT_TO_INCOME_RATIO, fill = TARGET_LABEL)) +
geom_histogram(aes(y = ..density..), alpha = 0.7, bins = 30, position = "identity") +
geom_density(alpha = 0.5, aes(color = TARGET_LABEL)) +
scale_fill_manual(values = c("Thanh toán tốt" = "#2E8B57", "Khó khăn thanh toán" = "#DC143C")) +
scale_color_manual(values = c("Thanh toán tốt" = "#2E8B57", "Khó khăn thanh toán" = "#DC143C")) +
labs(title = "TỶ LỆ VAY TRÊN THU NHẬP",
subtitle = "Chỉ số đòn bẩy tài chính (giới hạn tỷ lệ ≤ 20 để dễ quan sát)",
x = "Tỷ lệ vay/thu nhập",
y = "Mật độ",
fill = "Tình trạng thanh toán",
color = "Tình trạng thanh toán") +
xlim(0, 10) + # GIỚI HẠN ĐỂ DỄ QUAN SÁT
theme_minimal(base_size = 12) +
theme(plot.title = element_text(face = "bold", hjust = 0.5),
plot.subtitle = element_text(hjust = 0.5))# ĐẢM BẢO TẤT CẢ DỮ LIỆU LÀ NUMERIC TRƯỚC KHI PHÂN TÍCH
risk_data <- loan_data %>%
mutate(
# CHUYỂN ĐỔI CÁC BIẾN SỐ VỀ NUMERIC
AMT_INCOME_TOTAL = as.numeric(AMT_INCOME_TOTAL),
EXT_SOURCE_1 = as.numeric(EXT_SOURCE_1),
EXT_SOURCE_2 = as.numeric(EXT_SOURCE_2),
EXT_SOURCE_3 = as.numeric(EXT_SOURCE_3),
DAYS_BIRTH = as.numeric(DAYS_BIRTH)
) %>%
filter(
!is.na(AMT_INCOME_TOTAL) &
!is.na(EXT_SOURCE_1) &
!is.na(EXT_SOURCE_2) &
!is.na(EXT_SOURCE_3)
) %>%
mutate(
# TÍNH TUỔI
AGE = round(-DAYS_BIRTH / 365.25, 1),
AGE_GROUP = cut(AGE,
breaks = c(20, 30, 40, 50, 60, 70),
labels = c("20-30", "30-40", "40-50", "50-60", "60+"),
include.lowest = TRUE),
# PHÂN NHÓM 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),
# TÍNH ĐIỂM RỦI RO TRUNG BÌNH
RISK_SCORE = (EXT_SOURCE_1 + EXT_SOURCE_2 + EXT_SOURCE_3) / 3
) %>%
filter(!is.na(AGE_GROUP) & !is.na(INCOME_GROUP)) %>%
group_by(AGE_GROUP, INCOME_GROUP) %>%
summarise(
DEFAULT_RATE = mean(TARGET, na.rm = TRUE) * 100,
AVG_RISK_SCORE = mean(RISK_SCORE, na.rm = TRUE),
COUNT = n(),
.groups = 'drop'
) %>%
filter(COUNT >= 5) # CHỈ GIỮ LẠI CÁC Ô CÓ ĐỦ DỮ LIỆU
# VẼ HEATMAP
ggplot(risk_data, aes(x = AGE_GROUP, y = INCOME_GROUP, fill = DEFAULT_RATE)) +
geom_tile(color = "white", alpha = 0.8) +
geom_text(aes(label = paste0(round(DEFAULT_RATE, 1), "%")),
color = "white", size = 4, fontface = "bold") +
scale_fill_gradient2(low = "#2E8B57", mid = "#FFD700", high = "#DC143C",
midpoint = median(risk_data$DEFAULT_RATE, na.rm = TRUE),
name = "Tỷ lệ vỡ nợ (%)") +
labs(title = "MA TRẬN RỦI RO THEO TUỔI VÀ THU NHẬP",
subtitle = "Heatmap tỷ lệ khó khăn thanh toán",
x = "Nhóm tuổi",
y = "Nhóm thu nhập") +
theme_minimal(base_size = 12) +
theme(plot.title = element_text(face = "bold", hjust = 0.5),
plot.subtitle = element_text(hjust = 0.5),
axis.text.x = element_text(angle = 45, hjust = 1))