Trong bối cảnh cạnh tranh sản phẩm ngày càng gay gắt, việc duy trì và thu hút khách hàng trở thành yếu tố then chốt cho sự thành công của doanh nghiệp. Mặc dù nhiều công ty tập trung vào việc giành được khách hàng mới, nhưng tình trạng bỏ qua hoặc thiếu chú ý đến việc khách hàng hiện tại rời bỏ có thể dẫn đến một vòng luẩn quẩn: lượng khách hàng mới tăng lên nhưng lượng khách hàng cũ lại âm thầm biến mất. Do đó, việc phân tích dữ liệu rời bỏ của người dùng hiện tại để tìm ra những thông tin quan trọng, hỗ trợ các nhà ra quyết định trong việc giảm thiểu tỷ lệ rời bỏ, là một nhiệm vụ cấp thiết.
1. Mục tiêu
Mục tiêu của nghiên cứu này là hiểu sâu hơn về hồ sơ và hành vi của người dùng, khám phá các yếu tố chính ảnh hưởng đến việc rời bỏ, và dự đoán kết quả chuyển đổi từ các lượt truy cập của khách hàng bằng thuật toán. Điều này nhằm mục đích cải thiện thiết kế sản phẩm và nâng cao trải nghiệm người dùng.
2. Mô tả dữ liệu
Dữ liệu được sử dụng trong phân tích này là dữ liệu truy cập của người dùng từ một nền tảng du lịch trong vòng một tuần. Để bảo vệ quyền riêng tư của khách hàng, dữ liệu đã được ẩn danh hóa, do đó có một số khác biệt so với số liệu thực tế về đơn hàng, lượt xem và tỷ lệ chuyển đổi. Tuy nhiên, điều này không ảnh hưởng đến khả năng giải quyết vấn đề.
3. Đọc dữ liệu
Để bắt đầu, chúng ta tải dữ liệu vào DataFrame của Pandas và xem xét một vài dòng đầu tiên để hiểu cấu trúc.
import pandas as pd
import numpy as np
# Giả định dữ liệu được tải từ 'customer_churn_data.csv'
# hoặc có thể là một DataFrame đã được chuẩn bị sẵn với tên 'df_raw'
# Ví dụ:
# df_raw = pd.read_csv('customer_churn_data.csv')
# Xem các hàng đầu tiên của DataFrame
print(df_raw.head())
4. Chia dữ liệu
Dữ liệu được chia thành tập huấn luyện và tập kiểm tra để đánh giá hiệu suất của mô hình. Cột mục tiêu (label) là biến thể hiện việc khách hàng có rời bỏ hay không.
from sklearn.model_selection import train_test_split
# Giả định 'label' là cột mục tiêu
features = df_raw.drop("label", axis=1)
target = df_raw["label"]
# Chia dữ liệu với tỷ lệ 80% huấn luyện, 20% kiểm tra
X_train_data, X_test_data, y_train_labels, y_test_labels = train_test_split(
features, target, test_size=0.2, random_state=42
)
print(f"Kích thước tập huấn luyện: {X_train_data.shape}")
print(f"Kích thước tập kiểm tra: {X_test_data.shape}")
5. Khám phá dữ liệu
Có nhiều biến trong bộ dữ liệu này. Sau khi loại bỏ biến mục tiêu label, các trường có thể được phân loại thành ba nhóm chính: chỉ số liên quan đến đơn hàng, chỉ số liên quan đến hành vi khách hàng và chỉ số liên quan đến khách sạn.
6. Kỹ thuật đặc trưng
Để chuẩn bị dữ liệu cho mô hình, chúng ta cần thực hiện các bước tiền xử lý.
6.1. Tiền xử lý dữ liệu
6.1.1. Xóa các cột không cần thiết
Các cột như sampleid thường không mang lại thông tin hữu ích cho mô hình và có thể được loại bỏ.
# Xóa cột 'sampleid' khỏi cả tập huấn luyện và kiểm tra
if "sampleid" in X_train_data.columns:
X_train_data = X_train_data.drop("sampleid", axis=1)
if "sampleid" in X_test_data.columns:
X_test_data = X_test_data.drop("sampleid", axis=1)
print("Đã xóa cột 'sampleid'.")
6.1.2. Chuyển đổi kiểu dữ liệu và tạo đặc trưng mới
Các cột ngày tháng dạng chuỗi cần được chuyển đổi sang kiểu dữ liệu ngày giờ. Từ đó, chúng ta có thể tạo ra các đặc trưng phái sinh như "số ngày đặt trước", là khoảng thời gian từ ngày đặt đến ngày đến.
# Tạo bản sao để tránh thay đổi dữ liệu gốc trong quá trình tiền xử lý
df_train_processed = X_train_data.copy()
df_test_processed = X_test_data.copy()
# Chuyển đổi các cột ngày thành định dạng datetime
df_train_processed["ngay_den"] = pd.to_datetime(df_train_processed["arrival_date"])
df_train_processed["ngay_dat_phong"] = pd.to_datetime(df_train_processed["booking_date"])
df_test_processed["ngay_den"] = pd.to_datetime(df_test_processed["arrival_date"])
df_test_processed["ngay_dat_phong"] = pd.to_datetime(df_test_processed["booking_date"])
# Tạo đặc trưng "so_ngay_dat_truoc"
df_train_processed["so_ngay_dat_truoc"] = (df_train_processed["ngay_den"] - df_train_processed["ngay_dat_phong"]).dt.days
df_test_processed["so_ngay_dat_truoc"] = (df_test_processed["ngay_den"] - df_test_processed["ngay_dat_phong"]).dt.days
# Xóa các cột ngày gốc
df_train_processed.drop(columns=["arrival_date", "booking_date", "ngay_den", "ngay_dat_phong"], inplace=True)
df_test_processed.drop(columns=["arrival_date", "booking_date", "ngay_den", "ngay_dat_phong"], inplace=True)
print("Đã tạo đặc trưng 'so_ngay_dat_truoc' và xóa cột ngày gốc.")
6.1.3. Xử lý giá trị khuyết thiếu
Dựa trên kinh nghiệm nghiệp vụ, chúng ta thực hiện các chiến lược điền giá trị khuyết thiếu khác nhau:
- Các chỉ số như số lượng đơn hàng, số lượt xem chi tiết trang trong 7 ngày lịch sử được điền bằng 0.
- Với các cột như
do_nhay_cam_gia(price_sensitive) vànang_luc_tieu_thu(consuming_capacity), nếu người dùng là khách hàng mới chưa từng đặt đơn hàng (được đánh dấu bằng một cộtis_new_usergiả định), chúng sẽ được điền bằng 0. Các trường hợp khác được điền bằng giá trị trung vị. - Các biến có phân phối gần chuẩn được điền bằng giá trị trung bình, trong khi các biến có phân phối lệch được điền bằng giá trị trung vị.
# Danh sách các cột cần điền giá trị 0
cols_fill_zero = ["don_hang_7_ngay_ls", "luot_xem_chi_tiet_ls", "thoi_gian_don_hang_dau"]
for col in cols_fill_zero:
df_train_processed[col].fillna(0, inplace=True)
df_test_processed[col].fillna(0, inplace=True)
# Giả định cột 'is_new_user' tồn tại và được xác định trước
# Nếu không, cần tạo cột này dựa trên logic nghiệp vụ (ví dụ: total_orders_oneyear == 0)
df_train_processed['is_new_user'] = (df_train_processed['order_num_oneyear'].fillna(0) == 0).astype(int)
df_test_processed['is_new_user'] = (df_test_processed['order_num_oneyear'].fillna(0) == 0).astype(int)
# Xử lý các cột nhạy cảm giá và năng lực tiêu dùng
price_consumption_features = ["price_sensitive", "consuming_capacity"]
for col in price_consumption_features:
# Điền 0 nếu là người dùng mới
df_train_processed.loc[df_train_processed["is_new_user"] == 1, col] = 0
df_test_processed.loc[df_test_processed["is_new_user"] == 1, col] = 0
# Điền trung vị cho các giá trị còn lại (nếu có)
median_val = df_train_processed[col].median()
df_train_processed[col].fillna(median_val, inplace=True)
df_test_processed[col].fillna(median_val, inplace=True)
# Hàm điền giá trị khuyết thiếu chung dựa trên phân phối
def smart_fillna(dataframe):
for col in dataframe.columns:
if dataframe[col].isnull().any():
if dataframe[col].dtype == 'object': # Cột phân loại dùng mode
dataframe[col].fillna(dataframe[col].mode()[0], inplace=True)
else: # Cột số
# Kiểm tra độ lệch để quyết định dùng mean hay median
if abs(dataframe[col].skew()) > 1: # Nếu lệch nhiều, dùng median
dataframe[col].fillna(dataframe[col].median(), inplace=True)
else: # Nếu gần chuẩn, dùng mean
dataframe[col].fillna(dataframe[col].mean(), inplace=True)
return dataframe
df_train_processed = smart_fillna(df_train_processed)
df_test_processed = smart_fillna(df_test_processed)
print("Đã xử lý giá trị khuyết thiếu.")
6.1.4. Xử lý ngoại lệ
Các giá trị âm trong customer_value_profit và ctrip_profits được xử lý thành 0. Đối với delta_price1, delta_price2 và lowest_price, các giá trị âm được thay thế bằng giá trị trung vị.
# Danh sách các cột cần xử lý ngoại lệ
profit_cols = ["customer_value_profit", "ctrip_profits"]
price_cols = ["delta_price1", "delta_price2", "lowest_price"]
for col in profit_cols:
df_train_processed.loc[df_train_processed[col] < 0, col] = 0
df_test_processed.loc[df_test_processed[col] < 0, col] = 0
for col in price_cols:
median_val = df_train_processed[col].median()
df_train_processed.loc[df_train_processed[col] < 0, col] = median_val
df_test_processed.loc[df_test_processed[col] < 0, col] = median_val
print("Đã xử lý ngoại lệ cho các cột liên quan đến lợi nhuận và giá.")
6.1.5. Chuẩn hóa dữ liệu
Để đảm bảo rằng các đặc trưng có thang đo tương tự, chúng ta sử dụng MinMaxScaler để chuẩn hóa dữ liệu về khoảng [0, 1].
from sklearn.preprocessing import MinMaxScaler
# Loại bỏ cột is_new_user nếu nó chỉ là biến tạm thời cho tiền xử lý
if 'is_new_user' in df_train_processed.columns:
df_train_processed.drop('is_new_user', axis=1, inplace=True)
if 'is_new_user' in df_test_processed.columns:
df_test_processed.drop('is_new_user', axis=1, inplace=True)
# Áp dụng MinMaxScaler
scaler_data = MinMaxScaler()
# Lấy tên các cột trước khi chuẩn hóa
feature_names = df_train_processed.columns
X_train_scaled = scaler_data.fit_transform(df_train_processed)
X_test_scaled = scaler_data.transform(df_test_processed)
# Chuyển đổi lại thành DataFrame để dễ quản lý
X_train_final = pd.DataFrame(X_train_scaled, columns=feature_names, index=df_train_processed.index)
X_test_final = pd.DataFrame(X_test_scaled, columns=feature_names, index=df_test_processed.index)
print("Đã chuẩn hóa dữ liệu bằng MinMaxScaler.")
7. Lựa chọn đặc trưng
7.1. Lựa chọn đặc trưng - Loại bỏ 30% cột
Để cải thiện hiệu suất mô hình và giảm overfitting, chúng ta thực hiện lựa chọn đặc trưng. Trong ví dụ này, chúng ta sẽ sử dụng SelectKBest để chọn ra một số lượng đặc trưng nhất định dựa trên điểm số thống kê.
from sklearn.feature_selection import SelectKBest, f_classif
from sklearn.tree import DecisionTreeClassifier
from sklearn.metrics import roc_auc_score, accuracy_score
# Chọn một số lượng đặc trưng nhất định (ví dụ: giữ lại 70% số cột)
k_features_to_keep = int(X_train_final.shape[1] * 0.7)
selector = SelectKBest(score_func=f_classif, k=k_features_to_keep)
X_train_selected_features = selector.fit_transform(X_train_final, y_train_labels)
X_test_selected_features = selector.transform(X_test_final)
# Lấy tên các đặc trưng đã chọn
selected_feature_names = X_train_final.columns[selector.get_support()]
X_train_selected_df = pd.DataFrame(X_train_selected_features, columns=selected_feature_names, index=X_train_final.index)
X_test_selected_df = pd.DataFrame(X_test_selected_features, columns=selected_feature_names, index=X_test_final.index)
print(f"Đã chọn {k_features_to_keep} đặc trưng.")
# Kiểm tra nhanh với Decision Tree sau khi chọn lọc đặc trưng
model_dt_selected = DecisionTreeClassifier(random_state=42)
model_dt_selected.fit(X_train_selected_df, y_train_labels)
y_pred_proba_dt_selected = model_dt_selected.predict_proba(X_test_selected_df)[:, 1]
y_pred_dt_selected = model_dt_selected.predict(X_test_selected_df)
accuracy_dt_selected = accuracy_score(y_test_labels, y_pred_dt_selected)
auc_dt_selected = roc_auc_score(y_test_labels, y_pred_proba_dt_selected)
print(f"Độ chính xác của Decision Tree sau chọn lọc: {accuracy_dt_selected:.4f}")
print(f"AUC của Decision Tree sau chọn lọc: {auc_dt_selected:.4f}")
7.2. Kiểm tra đa cộng tuyến
Đa cộng tuyến giữa các đặc trưng có thể ảnh hưởng đến hiệu suất của một số mô hình. Chúng ta kiểm tra ma trận tương quan và trực quan hóa nó bằng heatmap.
import seaborn as sns
import matplotlib.pyplot as plt
# Tính ma trận tương quan trên tập huấn luyện đã chọn đặc trưng
correlation_matrix = X_train_selected_df.corr()
# Chỉ hiển thị các tương quan lớn hơn 0.9 (hoặc nhỏ hơn -0.9)
high_corr_matrix = correlation_matrix[(correlation_matrix > 0.9) | (correlation_matrix < -0.9)]
np.fill_diagonal(high_corr_matrix.values, 0) # Không hiển thị tương quan với chính nó
plt.figure(figsize=(15, 12))
sns.heatmap(high_corr_matrix, annot=True, cmap='coolwarm', fmt=".2f", linewidths=.5)
plt.title('Ma trận Tương quan cao giữa các Đặc trưng đã chọn')
plt.show()
print("Kiểm tra đa cộng tuyến hoàn tất. Xem heatmap để phát hiện các đặc trưng tương quan cao.")
8. Xây dựng và đánh giá mô hình
Chúng ta sẽ xây dựng và đánh giá một loạt các mô hình học máy để dự đoán việc khách hàng rời bỏ.
8.1. Hồi quy Logistic
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import roc_curve, auc, accuracy_score
# Khởi tạo và huấn luyện mô hình Hồi quy Logistic
model_lr = LogisticRegression(random_state=42, solver='liblinear')
model_lr.fit(X_train_selected_df, y_train_labels)
# Dự đoán xác suất và nhãn
y_prob_lr = model_lr.predict_proba(X_test_selected_df)[:, 1]
y_pred_lr = model_lr.predict(X_test_selected_df)
# Đánh giá mô hình
fpr_lr, tpr_lr, thresholds_lr = roc_curve(y_test_labels, y_prob_lr)
auc_lr = auc(fpr_lr, tpr_lr)
accuracy_lr = accuracy_score(y_test_labels, y_pred_lr)
print(f"Hồi quy Logistic - Độ chính xác: {accuracy_lr:.4f}, AUC: {auc_lr:.4f}")
print("="*30)
8.2. Naive Bayes
from sklearn.naive_bayes import GaussianNB
# Khởi tạo và huấn luyện mô hình Gaussian Naive Bayes
model_gnb = GaussianNB()
model_gnb.fit(X_train_selected_df, y_train_labels)
# Dự đoán xác suất và nhãn
y_prob_gnb = model_gnb.predict_proba(X_test_selected_df)[:, 1]
y_pred_gnb = model_gnb.predict(X_test_selected_df)
# Đánh giá mô hình
fpr_gnb, tpr_gnb, thresholds_gnb = roc_curve(y_test_labels, y_prob_gnb)
auc_gnb = auc(fpr_gnb, tpr_gnb)
accuracy_gnb = accuracy_score(y_test_labels, y_pred_gnb)
print(f"Naive Bayes - Độ chính xác: {accuracy_gnb:.4f}, AUC: {auc_gnb:.4f}")
print("="*30)
8.3. Máy vector hỗ trợ (SVM)
from sklearn.svm import SVC
# Khởi tạo và huấn luyện mô hình SVM (với kernel RBF để có predict_proba)
# Lưu ý: SVC với probability=True có thể chậm hơn
model_svc = SVC(kernel='rbf', C=1, random_state=42, probability=True)
model_svc.fit(X_train_selected_df, y_train_labels)
# Dự đoán xác suất và nhãn
y_prob_svc = model_svc.predict_proba(X_test_selected_df)[:, 1]
y_pred_svc = model_svc.predict(X_test_selected_df)
# Đánh giá mô hình
fpr_svc, tpr_svc, thresholds_svc = roc_curve(y_test_labels, y_prob_svc)
auc_svc = auc(fpr_svc, tpr_svc)
accuracy_svc = accuracy_score(y_test_labels, y_pred_svc)
print(f"SVM - Độ chính xác: {accuracy_svc:.4f}, AUC: {auc_svc:.4f}")
print("="*30)
8.4. Cây quyết định
# Đã khởi tạo và kiểm tra ở phần lựa chọn đặc trưng
# model_dt_selected = DecisionTreeClassifier(random_state=42)
# model_dt_selected.fit(X_train_selected_df, y_train_labels)
# y_prob_dt_selected và y_pred_dt_selected đã được tính
fpr_dt, tpr_dt, thresholds_dt = roc_curve(y_test_labels, y_prob_dt_selected)
auc_dt = auc(fpr_dt, tpr_dt)
accuracy_dt = accuracy_score(y_test_labels, y_pred_dt_selected)
print(f"Cây quyết định - Độ chính xác: {accuracy_dt:.4f}, AUC: {auc_dt:.4f}")
print("="*30)
8.5. Rừng ngẫu nhiên
from sklearn.ensemble import RandomForestClassifier
# Khởi tạo và huấn luyện mô hình Rừng ngẫu nhiên
model_rf = RandomForestClassifier(random_state=42, n_estimators=100)
model_rf.fit(X_train_selected_df, y_train_labels)
# Dự đoán xác suất và nhãn
y_prob_rf = model_rf.predict_proba(X_test_selected_df)[:, 1]
y_pred_rf = model_rf.predict(X_test_selected_df)
# Đánh giá mô hình
fpr_rf, tpr_rf, thresholds_rf = roc_curve(y_test_labels, y_prob_rf)
auc_rf = auc(fpr_rf, tpr_rf)
accuracy_rf = accuracy_score(y_test_labels, y_pred_rf)
print(f"Rừng ngẫu nhiên - Độ chính xác: {accuracy_rf:.4f}, AUC: {auc_rf:.4f}")
print("="*30)
8.6. XGBoost
import xgboost as xgb
# Chuyển đổi dữ liệu sang định dạng DMatrix của XGBoost
dtrain_xgb = xgb.DMatrix(X_train_selected_df, label=y_train_labels)
dtest_xgb = xgb.DMatrix(X_test_selected_df)
# Thiết lập tham số XGBoost
params_xgb = {
'objective': 'binary:logistic',
'eval_metric': 'auc',
'eta': 0.05, # Tốc độ học
'max_depth': 5, # Độ sâu tối đa của cây
'subsample': 0.8, # Tỷ lệ mẫu con để huấn luyện mỗi cây
'colsample_bytree': 0.8, # Tỷ lệ cột để huấn luyện mỗi cây
'seed': 42
}
# Huấn luyện mô hình
bst_xgb = xgb.train(params_xgb, dtrain_xgb, num_boost_round=200)
# Dự đoán xác suất
y_prob_xgb = bst_xgb.predict(dtest_xgb)
# Chuyển đổi xác suất thành nhãn dự đoán (sử dụng ngưỡng 0.5)
y_pred_xgb = (y_prob_xgb >= 0.5).astype(int)
# Đánh giá mô hình
fpr_xgb, tpr_xgb, thresholds_xgb = roc_curve(y_test_labels, y_prob_xgb)
auc_xgb = auc(fpr_xgb, tpr_xgb)
accuracy_xgb = accuracy_score(y_test_labels, y_pred_xgb)
print(f"XGBoost - Độ chính xác: {accuracy_xgb:.4f}, AUC: {auc_xgb:.4f}")
print("="*30)
8.7. So sánh mô hình
Chúng ta có thể so sánh hiệu suất của các mô hình bằng cách vẽ đường cong ROC.
plt.figure(figsize=(10, 8))
plt.plot(fpr_lr, tpr_lr, label=f'Hồi quy Logistic (AUC = {auc_lr:.2f})')
plt.plot(fpr_gnb, tpr_gnb, label=f'Naive Bayes (AUC = {auc_gnb:.2f})')
plt.plot(fpr_svc, tpr_svc, label=f'SVM (AUC = {auc_svc:.2f})')
plt.plot(fpr_dt, tpr_dt, label=f'Cây quyết định (AUC = {auc_dt:.2f})')
plt.plot(fpr_rf, tpr_rf, label=f'Rừng ngẫu nhiên (AUC = {auc_rf:.2f})')
plt.plot(fpr_xgb, tpr_xgb, label=f'XGBoost (AUC = {auc_xgb:.2f})')
plt.plot([0, 1], [0, 1], 'k--', label='Đường cơ sở ngẫu nhiên (AUC = 0.50)')
plt.xlabel('Tỷ lệ Dương tính Giả (False Positive Rate)')
plt.ylabel('Tỷ lệ Dương tính Thật (True Positive Rate)')
plt.title('Đường cong ROC của các Mô hình')
plt.legend(loc='lower right')
plt.grid(True)
plt.tight_layout()
plt.savefig('model_roc_comparison.jpg', dpi=300)
plt.show()
9. Phân tích nguyên nhân rời bỏ và đề xuất
9.1. Các đặc trưng quan trọng
Dựa trên mô hình Rừng ngẫu nhiên, chúng ta có thể trích xuất tầm quan trọng của các đặc trưng để hiểu yếu tố nào ảnh hưởng nhiều nhất đến việc khách hàng rời bỏ.
# Lấy tầm quan trọng của đặc trưng từ mô hình Rừng ngẫu nhiên
feature_importances = pd.Series(model_rf.feature_importances_, index=X_train_selected_df.columns)
# Sắp xếp theo thứ tự giảm dần
sorted_importances = feature_importances.sort_values(ascending=False)
print("Các đặc trưng quan trọng nhất:")
print(sorted_importances.head(10)) # Hiển thị 10 đặc trưng hàng đầu
# Trực quan hóa các đặc trưng quan trọng nhất
plt.figure(figsize=(12, 7))
sns.barplot(x=sorted_importances.head(15).values, y=sorted_importances.head(15).index, palette='viridis')
plt.title('Top 15 Đặc trưng quan trọng nhất')
plt.xlabel('Tầm quan trọng của Đặc trưng')
plt.ylabel('Đặc trưng')
plt.tight_layout()
plt.show()
9.2. Phân tích nguyên nhân rời bỏ
- Chỉ số UV và đơn hàng thành phố thấp: Khi giá trị
cityuvs(lượt xem duy nhất thành phố) vàcityorders(đơn hàng thành phố) thấp, tỷ lệ rời bỏ của người dùng cao hơn đáng kể so với mức trung bình. Điều này cho thấy nền tảng thiếu thông tin khách sạn ở các thành phố nhỏ, khiến người dùng chuyển sang các đối thủ có dữ liệu đầy đủ hơn. - Thời điểm truy cập: Người dùng có tỷ lệ rời bỏ cao hơn mức trung bình khi truy cập vào các khung giờ từ 7 giờ sáng đến 7 giờ tối. Các hoạt động khuyến mãi nên tránh những khung giờ này vào các ngày làm việc.
- Chỉ số thuộc tính thương mại khách sạn: Người dùng có chỉ số thuộc tính thương mại khách sạn (
businessrate_pre2) trong khoảng 0.3-0.9 có tỷ lệ rời bỏ cao hơn và xu hướng tăng. Điều này có thể cho thấy sự không khớp giữa các khách sạn có chỉ số thương mại cao trên nền tảng và kỳ vọng của người dùng (ví dụ: giá quá cao). - Thời gian từ lần đặt cuối cùng: Thời gian càng ngắn kể từ lần đặt cuối cùng, mức độ rời bỏ càng nghiêm trọng. Điều này có thể bị ảnh hưởng bởi những tin tức tiêu cực mà nền tảng gặp phải trong quá khứ, nhấn mạnh sự cần thiết của quản lý nội bộ chặt chẽ và xây dựng hình ảnh thương hiệu tốt.
- Năng lực tiêu dùng thấp: Người dùng có chỉ số năng lực tiêu dùng (
consuming_capacity) thấp (10-40) có tỷ lệ rời bỏ nghiêm trọng. Nhóm này chiếm khoảng 50% tổng số người dùng và cần được quan tâm đặc biệt. - Độ nhạy cảm về giá: Những người dùng có chỉ số nhạy cảm về giá (
price_sensitive) trong khoảng 5-25 có tỷ lệ rời bỏ cao hơn mức trung bình, cho thấy họ rất chú trọng đến chất lượng khách sạn. - Tỷ lệ chuyển đổi và số đơn hàng cao: Người dùng có tỷ lệ chuyển đổi, số đơn hàng hàng năm và số đơn hàng lịch sử trong 1 năm càng cao, cùng với tỷ lệ người truy cập trang điền đơn hàng trong 24 giờ càng lớn, thì mức độ rời bỏ càng nghiêm trọng. Điều này cho thấy nhu cầu theo dõi trải nghiệm sau khi đặt hàng, mời họ điền phản hồi về trải nghiệm lưu trú và cải thiện dựa trên những góp ý đó.
- Số ngày đặt trước và số đơn hàng hủy: Số ngày đặt trước càng ngắn và số đơn hàng bị hủy trong một năm càng cao thì tỷ lệ rời bỏ càng nghiêm trọng.
9.3. Đề xuất
- Cân nhắc mở rộng thị phần tại các thành phố cấp ba, bốn và phân khúc khách sạn bình dân.
- Khách hàng dễ bị ảnh hưởng bởi tin tức tiêu cực của công ty. Đề xuất công ty cần chủ động chịu trách nhiệm xã hội, tăng cường quản lý nội bộ, cải thiện hiệu quả xử lý khủng hoảng truyền thông và xây dựng hình ảnh thương hiệu tích cực.
- Gửi các thông báo khuyến mãi về khách sạn tại các điểm du lịch nổi tiếng 2-3 tuần trước các kỳ nghỉ lễ.
- Nâng cao trải nghiệm sau khi khách hàng đặt phòng bằng cách mời họ đánh giá trải nghiệm lưu trú và sử dụng phản hồi để cải thiện dịch vụ.
10. Hồ sơ khách hàng với K-Means
10.1. Phân loại người dùng
Chúng ta sử dụng thuật toán K-Means để phân cụm người dùng dựa trên các đặc trưng hành vi và giá trị, nhằm xây dựng hồ sơ khách hàng. Trước khi phân cụm, dữ liệu cần được chuẩn hóa bằng StandardScaler để K-Means hoạt động hiệu quả hơn.
from sklearn.cluster import KMeans
from sklearn.preprocessing import StandardScaler
# Các đặc trưng được sử dụng để phân cụm người dùng
user_profiling_features = [
"decision_habit_user", "star_prefer", "last_pv_gap",
"order_num_oneyear", "visit_num_oneyear", "total_visits_page_detail",
"oneyear_dealrate", "price_sensitive", "consuming_capacity"
]
# Chuẩn bị dữ liệu cho phân cụm từ X_train_final (hoặc df_raw nếu bạn muốn phân cụm toàn bộ khách hàng)
# Giả sử chúng ta sử dụng lại X_train_final đã qua xử lý và chọn đặc trưng
data_for_clustering_raw = X_train_final[user_profiling_features].copy()
# Chuẩn hóa dữ liệu cho K-Means
scaler_kmeans = StandardScaler()
scaled_data_for_clustering = scaler_kmeans.fit_transform(data_for_clustering_raw)
scaled_df_for_clustering = pd.DataFrame(scaled_data_for_clustering, columns=user_profiling_features)
# Áp dụng K-Means
# Chọn n_clusters=2 để phân thành 2 loại người dùng như trong bản gốc
kmeans_model = KMeans(n_clusters=2, random_state=42, n_init=10) # n_init để tránh local optima
kmeans_model.fit(scaled_df_for_clustering)
# Gán nhãn cụm cho từng khách hàng
X_train_final['cluster_label'] = kmeans_model.labels_
data_for_clustering_raw['cluster_label'] = kmeans_model.labels_ # Gán nhãn vào DataFrame gốc để phân tích
# Lấy tọa độ trung tâm các cụm (trên dữ liệu đã chuẩn hóa)
cluster_centers_scaled = kmeans_model.cluster_centers_
# Chuyển đổi trung tâm cụm về thang đo gốc để dễ giải thích
cluster_centers_original_scale = scaler_kmeans.inverse_transform(cluster_centers_scaled)
cluster_profiles_df = pd.DataFrame(cluster_centers_original_scale, columns=user_profiling_features)
print("Hồ sơ trung tâm các cụm (trên thang đo gốc):")
print(cluster_profiles_df)
# Trực quan hóa trung tâm các cụm
plt.figure(figsize=(10, 8))
cluster_profiles_df.T.plot(kind='bar', figsize=(12, 7), colormap='Paired')
plt.title('Hồ sơ Trung tâm các Cụm (Thang đo gốc)')
plt.ylabel('Giá trị Đặc trưng')
plt.xlabel('Đặc trưng')
plt.xticks(rotation=45, ha='right')
plt.legend(['Cụm 0', 'Cụm 1'])
plt.tight_layout()
plt.show()
10.2. Tỷ lệ các loại người dùng
# Tính tỷ lệ phần trăm của mỗi cụm
cluster_counts = data_for_clustering_raw['cluster_label'].value_counts(normalize=True) * 100
# Đổi tên cụm nếu cần để phù hợp với phân tích (ví dụ: 'Người dùng giá trị cao', 'Người dùng tiềm năng')
# Dựa trên cluster_profiles_df, hãy xác định cụm nào là "giá trị cao" và "tiềm năng"
# Giả sử Cluster 0 là "Người dùng tiềm năng" và Cluster 1 là "Người dùng giá trị cao" (tùy thuộc vào kết quả thực tế)
cluster_names = ['Người dùng tiềm năng', 'Người dùng giá trị cao'] # Điều chỉnh thứ tự nếu cần
plt.figure(figsize=(8, 8))
plt.pie(cluster_counts, labels=[cluster_names[i] for i in cluster_counts.index],
autopct='%1.2f%%', startangle=90, colors=['#ff9999','#66b3ff'])
plt.title('Tỷ lệ các loại Người dùng')
plt.axis('equal') # Đảm bảo biểu đồ hình tròn
plt.savefig('user_segmentation_pie_chart.jpg', dpi=300, bbox_inches='tight')
plt.show()
print("Tỷ lệ các cụm người dùng:")
print(cluster_counts)
10.3. Phân tích người dùng giá trị cao
Chiếm khoảng 19.02% (giả định cụm 1), những người dùng này có tần suất truy cập và đặt phòng cao, mức chi tiêu cao, giá trị khách hàng lớn và theo đuổi chất lượng cao, có yêu cầu cao về cấp sao khách sạn. Nhóm khách hàng này chủ yếu là khách hàng cũ.
Đề xuất:
- Đề xuất nhiều khách sạn thương mại chuỗi có uy tín tốt, giá cả hợp lý để thu hút người dùng.
- Gửi thông báo vào các thời điểm có lưu lượng truy cập thấp trong ngày làm việc như 11 giờ sáng, 5 giờ chiều.
- Cung cấp thêm thông tin khách sạn tại các địa điểm công tác.
- Tăng chi phí rời bỏ khách hàng: áp dụng hệ thống điểm thành viên, đưa ra thẻ giảm giá dành cho thành viên.
10.4. Phân tích người dùng tiềm năng
Chiếm khoảng 80.98% (giả định cụm 0), những người dùng này có tần suất truy cập và đặt phòng thấp, mức chi tiêu thấp, không có yêu cầu cao về cấp sao khách sạn. Nhóm khách hàng này chủ yếu là khách hàng mới, giá trị khách hàng còn chờ được khai thác.
Đề xuất:
- Vì phần lớn là người dùng mới, hãy tập trung vào trải nghiệm ban đầu của người dùng (ví dụ: ưu đãi cho lần tiêu dùng đầu tiên, hoạt động tích điểm), và thường xuyên gửi các ưu đãi khách sạn hấp dẫn để nuôi dưỡng thói quen tiêu dùng.
- Nội dung khuyến mãi nên tập trung vào các chương trình giảm giá lớn, ưu đãi đặc biệt.
- Do nhóm này chiếm tỷ lệ lớn, có thể phân tích sâu hơn các yếu tố khiến nhóm này rời bỏ để phát triển thị trường và mở rộng các phân khúc mới.