今天的主题是无监督算法中的聚类,常利用聚类来发现数据中的模式,并对每一个聚类后的类别特征进行可视化,以此得到新的特征---赋予实际含义。
# 先运行之前预处理好的代码
import pandas as pd #用于数据处理和分析,可处理表格数据。
import numpy as np #用于数值计算,提供了高效的数组操作。
import matplotlib.pyplot as plt #用于绘制各种类型的图表
import seaborn as sns #基于matplotlib的高级绘图库,能绘制更美观的统计图形。
import warnings
warnings.filterwarnings("ignore")
# 设置中文字体(解决中文显示问题)
plt.rcParams['font.sans-serif'] = ['SimHei'] # Windows系统常用黑体字体
plt.rcParams['axes.unicode_minus'] = False # 正常显示负号
data = pd.read_csv('data.csv') #读取数据
# 先筛选字符串变量
discrete_features = data.select_dtypes(include=['object']).columns.tolist()
# Home Ownership 标签编码
home_ownership_mapping = {
'Own Home': 1,
'Rent': 2,
'Have Mortgage': 3,
'Home Mortgage': 4
}
data['Home Ownership'] = data['Home Ownership'].map(home_ownership_mapping)
# Years in current job 标签编码
years_in_job_mapping = {
'< 1 year': 1,
'1 year': 2,
'2 years': 3,
'3 years': 4,
'4 years': 5,
'5 years': 6,
'6 years': 7,
'7 years': 8,
'8 years': 9,
'9 years': 10,
'10+ years': 11
}
data['Years in current job'] = data['Years in current job'].map(years_in_job_mapping)
# Purpose 独热编码,记得需要将bool类型转换为数值
data = pd.get_dummies(data, columns=['Purpose'])
data2 = pd.read_csv("data.csv") # 重新读取数据,用来做列名对比
list_final = [] # 新建一个空列表,用于存放独热编码后新增的特征名
for i in data.columns:
if i not in data2.columns:
list_final.append(i) # 这里打印出来的就是独热编码后的特征名
for i in list_final:
data[i] = data[i].astype(int) # 这里的i就是独热编码后的特征名
# Term 0 - 1 映射
term_mapping = {
'Short Term': 0,
'Long Term': 1
}
data['Term'] = data['Term'].map(term_mapping)
data.rename(columns={'Term': 'Long Term'}, inplace=True) # 重命名列
continuous_features = data.select_dtypes(include=['int64', 'float64']).columns.tolist() #把筛选出来的列名转换成列表
# 连续特征用中位数补全
for feature in continuous_features:
mode_value = data[feature].mode()[0] #获取该列的众数。
data[feature].fillna(mode_value, inplace=True) #用众数填充该列的缺失值,inplace=True表示直接在原数据上修改。
# 最开始也说了 很多调参函数自带交叉验证,甚至是必选的参数,你如果想要不交叉反而实现起来会麻烦很多
# 所以这里我们还是只划分一次数据集
from sklearn.model_selection import train_test_split
X = data.drop(['Credit Default'], axis=1) # 特征,axis=1表示按列删除
y = data['Credit Default'] # 标签
# # 按照8:2划分训练集和测试集
# X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) # 80%训练集,20%测试集
一、聚类评估指标介绍
以下是三种常用的聚类效果评估指标,分别用于衡量聚类的质量和簇的分离与紧凑程度:
1. 轮廓系数 (Silhouette Score)
-
定义:轮廓系数衡量每个样本与其所属簇的紧密程度以及与最近其他簇的分离程度。
- 取值范围:[-1, 1]
-
轮廓系数越接近 1,表示样本与其所属簇内其他样本很近,与其他簇很远,聚类效果越好。
-
轮廓系数越接近 -1,表示样本与其所属簇内样本较远,与其他簇较近,聚类效果越差(可能被错误分类)。
-
轮廓系数接近 0,表示样本在簇边界附近,聚类效果无明显好坏。
-
-
使用建议:选择轮廓系数最高的 值作为最佳簇数量。
k
2. CH 指数 (Calinski-Harabasz Index)
-
定义:CH 指数是簇间分散度与簇内分散度之比,用于评估簇的分离度和紧凑度。
- 取值范围:[0, +∞)
-
CH 指数越大,表示簇间分离度越高,簇内紧凑度越高,聚类效果越好。
-
没有固定的上限,值越大越好。
-
-
使用建议:选择 CH 指数最高的 值作为最佳簇数量。
k
3. DB 指数 (Davies-Bouldin Index)
-
定义:D B 指数衡量簇间距离与簇内分散度的比值,用于评估簇的分离度和紧凑度。
- 取值范围:[0, +∞)
-
DB 指数越小,表示簇间分离度越高,簇内紧凑度越高,聚类效果越好。
-
没有固定的上限,值越小越好。
-
-
使用建议:选择 DB 指数最低的 值作为最佳簇数量。
k
二、KMeans 聚类
1、算法原理
KMeans 是一种基于距离的聚类算法,需要预先指定聚类个数,即 。其核心步骤如下:k
-
随机选择 个样本点作为初始质心(簇中心)。
k
-
计算每个样本点到各个质心的距离,将样本点分配到距离最近的质心所在的簇。
-
更新每个簇的质心为该簇内所有样本点的均值。
-
重复步骤 2 和 3,直到质心不再变化或达到最大迭代次数为止。
2、确定簇数的方法:肘部法
-
肘部法(Elbow Method) 是一种常用的确定 值的方法。
k
-
原理:通过计算不同值下的簇内平方和(Within-Cluster Sum of Squares, WCSS),绘制 与 WCSS 的关系图。
k
k
-
选择标准:在图中找到“肘部”点,即 WCSS 下降速率明显减缓的 值,通常认为是最佳簇数。这是因为增加 值带来的收益(WCSS 减少)在该点后变得不显著。
k
k
3、KMeans 算法的优缺点
优点
-
简单高效:算法实现简单,计算速度快,适合处理大规模数据集。
-
适用性强:对球形或紧凑的簇效果较好,适用于特征空间中簇分布较为均匀的数据。
-
易于解释:聚类结果直观,簇中心具有明确的物理意义。
缺点
-
需预先指定
k
值:对簇数量 的选择敏感,不合适的 会导致聚类效果较差。k
k
-
对初始质心敏感:初始质心的随机选择可能导致结果不稳定或陷入局部最优(可通过 KMeans++ 初始化方法缓解)。
-
对噪声和异常值敏感:异常值可能会显著影响质心的位置,导致聚类结果失真。
-
不适合非球形簇:对非线性可分或形状复杂的簇效果较差,无法处理簇密度不均的情况。
三、Kmeans算法实现聚类
1、特征标准化
from sklearn.preprocessing import StandardScaler
# 标准化数据(聚类前通常需要标准化)
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
2、进行聚类和评估结果可视化
import numpy as np
import pandas as pd
# 画图
import matplotlib.pyplot as plt
import seaborn as sns
# KMeans聚类
from sklearn.cluster import KMeans
# 数据降维和可视化
from sklearn.decomposition import PCA
# 评估指标 轮廓系数、ch指数、db指数
from sklearn.metrics import silhouette_score, calinski_harabasz_score, davies_bouldin_score
# 评估不同k值下的指标
k_range = range(2, 11) # 测试 k 从 2 到 10
inertia_values = [] # 存储每个 k 值对应的 SSE(Inertia)
silhouette_scores = [] # 存储每个k值对应的轮廓系数
ch_scores = [] # 存储每个k值对应的CH系数
db_scores = [] # 存储每个k值对应的DB系数
for k in k_range:
kmeans = KMeans(n_clusters=k, random_state=42) # 初始化KMeans模型
kmeans_labels = kmeans.fit_predict(X_scaled) # 训练模型并获取聚类标签
inertia_values.append(kmeans.inertia_) # 计算并存储每个k值的惯性(肘部法则)
silhouette = silhouette_score(X_scaled, kmeans_labels) # 轮廓系数
silhouette_scores.append(silhouette)
ch = calinski_harabasz_score(X_scaled, kmeans_labels) # CH 指数
ch_scores.append(ch)
db = davies_bouldin_score(X_scaled, kmeans_labels) # DB 指数
db_scores.append(db)
print(f"k={k}, 惯性:{kmeans.inertia_:.2f}, 轮廓系数:{silhouette:.3f}, CH指数:{ch:.2f}, DB指数:{db:.3f}")
# 绘制评估指标图
plt.figure(figsize=(15, 10))
# 肘部法则图
plt.subplot(2, 2, 1)
plt.plot(k_range, inertia_values, marker='o')
plt.title('肘部法则确定最优聚类数 k (惯性,越小越好)')
plt.xlabel('聚类数(k)')
plt.ylabel('惯性')
plt.grid(True)
# 轮廓系数图
plt.subplot(2, 2, 2)
plt.plot(k_range, silhouette_scores, marker='o', color='red')
plt.title('轮廓系数确定最优聚类数 k (越大越好)')
plt.xlabel('聚类数(k)')
plt.ylabel('轮廓系数')
plt.grid(True)
# CH 指数图
plt.subplot(2, 2, 3)
plt.plot(k_range, ch_scores, marker='o', color='green')
plt.title('Calinski-Harabasz 指数确定最优聚类数 k (越大越好)')
plt.xlabel('聚类数(k)')
plt.ylabel('CH 指数')
plt.grid(True)
# DB 指数图
plt.subplot(2, 2, 4)
plt.plot(k_range, db_scores, marker='o', color='yellow')
plt.title('Davies-Bouldin 指数确定最优聚类数 k (越小越好)')
plt.xlabel('聚类数(k)')
plt.ylabel('DB 指数')
plt.grid(True)
plt.tight_layout()
plt.show()
输出:
k=2, 惯性:218529.50, 轮廓系数:0.320, CH指数:479.34, DB指数:3.222
k=3, 惯性:207982.87, 轮廓系数:0.209, CH指数:441.88, DB指数:2.906
k=4, 惯性:200477.28, 轮廓系数:0.220, CH指数:399.12, DB指数:2.441
k=5, 惯性:192940.36, 轮廓系数:0.224, CH指数:384.19, DB指数:2.042
k=6, 惯性:185411.81, 轮廓系数:0.227, CH指数:380.64, DB指数:1.733
k=7, 惯性:178444.49, 轮廓系数:0.130, CH指数:378.31, DB指数:1.633
k=8, 惯性:174920.27, 轮廓系数:0.143, CH指数:352.31, DB指数:1.817
k=9, 惯性:167383.96, 轮廓系数:0.150, CH指数:364.27, DB指数:1.636
k=10, 惯性:159824.84, 轮廓系数:0.156, CH指数:378.43, DB指数:1.502
我和大家说下上面这几个图怎么看,
- 肘部法则图: 找下降速率变慢的拐点,这里都差不多
- 轮廓系数图:找局部最高点,这里选6不能选7
- CH指数图: 找局部最高点,这里选7之前的都还行
- DB指数图:找局部最低点,这里选6 7 9 10都行
综上,选择 6 比较合适。
- 为什么选择局部最优的点,因为比如簇间差异,分得越细越好,但是k太细了没价值,所以要有取舍。
- 比如k可以选3和5,推荐选择5,能包含更多信息。
3、使用最佳簇值(k)进行聚类
# 提示用户选择k值
selected_k = 6
# 使用选择的k值进行KMeans聚类
kmeans = KMeans(n_clusters=selected_k, random_state=42)
kmeans_labels = kmeans.fit_predict(X_scaled)
X['Kmeans_Cluster'] = kmeans_labels
# 使用PCA降维到 2D 进行可视化
pca = PCA(n_components=2)
X_pca = pca.fit_transform(X_scaled)
# KMeans 聚类结果可视化
plt.figure(figsize=(6, 5))
sns.scatterplot(x=X_pca[:, 0], y=X_pca[:, 1], hue=kmeans_labels, palette='viridis')
plt.title(f'Kmeans Clustering with k={selected_k} (PCA Visualization)')
plt.xlabel('PCA Component 1')
plt.ylabel('PCA Component 2')
plt.show()
# 打印 KMeans 聚类标签前几行
print(f'Kmeans Cluster labels (k={selected_k} added to X:)')
print(X[['Kmeans_Cluster']].value_counts())
不同颜色的点代表聚类特征下的不同分类。
输出:
Kmeans Cluster labels (k=6 added to X:)
Kmeans_Cluster
0 5205
1 1220
2 903
3 128
4 34
5 10
Name: count, dtype: int64
通过KMeans方法聚类出的新特征 “ Kmeans_Cluster ” 下有 6 种分类,但是新特征代表的具体含义还不明确,需要进一步探索。
其它聚类方法还有 DB-SCAN 和 层次聚类。