import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
from sklearn.cluster import KMeans
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import silhouette_score
from sklearn.metrics import calinski_harabasz_score
from sklearn.model_selection import GridSearchCV
from mpl_toolkits.mplot3d import Axes3D
%matplotlib inline
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus']=False
# 读取数据
data = pd.read_csv(r'D:\ws\机器学习\data\customers.csv')
data.head()
plt.figure(figsize=(20,8),dpi=80)
p = data.boxplot(return_type= 'dict')
x = p['fliers'][0].get_xdata() # 'flies'即为异常值的标签
y = p['fliers'][0].get_ydata()
y.sort() # 从小到大排序,该方法直接改变原对象
'''
用annotate添加注释
其中有些相近的点,注释会出现重叠,难以看清,需要一些技巧来控制
以下参数都是经过调试的,需要具体问题具体调试
'''
for i in range(len(x)):
if i>0:
plt.annotate(y[i], xy=(x[i],y[i]), xytext=(x[i]+0.05 -0.8/(y[i]-y[i-1]), y[i]))
else:
plt.annotate(y[i], xy=(x[i],y[i]), xytext=(x[i]+0.08,y[i]))
plt.show() # 展示箱型图
箱盒图的构成,从下到上分别为最小观察值(下边缘),25%分位数(Q1),中位数,75%分位数(Q3),最大观察值(上边缘),在上下边缘以外的都是异常值。
# 确定箱型图的上、下边界
Q1 = data.iloc[:,2:].describe().iloc[4]
Q3 = data.iloc[:,2:].describe().iloc[6]
IQR = Q3 - Q1
max_up = Q3 + 1.5*IQR
max_lower = Q1 - 1.5*IQR
# 剔除大于上边界的异常值,保留上、下边界之间的数据
df = data.iloc[:,2:]
columns = df.columns.tolist()
for i in range(len(columns)):
df = df[df[columns[i]] <= max_up[i]]
前两个字段的数据是多分类变量,应转换为哑变量;其余的数据进行标准化。
# 将‘Region’和‘Channel’用get_dummies转换为one-hot
df_new = pd.get_dummies(data.Region, prefix='Region')
df_new = df_new.join(pd.get_dummies(data.Channel ,prefix='Channel'))
# 对df进标准化
sc = StandardScaler()
df = pd.DataFrame(sc.fit_transform(df), columns = columns)
# 根据索引进行拼接
data_new = df_new.join(df,how = 'right',lsuffix='_left',rsuffix='_right')
# 恢复索引
for i in data_new:
data_new.index = range(data_new.shape[0])
data_new
# k-means建模
K_means = KMeans()
K_means.fit(data_new)
kmeans_labels = K_means.labels_
kmeans_labels
没有标签,所以这里不做外部评估,仅做内部评估
# 内部评估之轮廓系数,[-1,1],簇间距离
silhouette_score(data_new, kmeans_labels)
# out>>> 0.16282317970738752
# 簇内距离
calinski_harabasz_score(data_new, kmeans_labels)
# out>>> 67.01402119227596
CH越大代表着类自身越紧密,类与类之间越分散,即更优的聚类结;,轮廓系数的值越接近于1,聚类越合理。现在轮廓系数为0.16282317970738752,比较小,说明模型仍有调整空间。
# 构造自定义函数,用于绘制不同k值和对应轮廓系数的折线图
def k_silhouette(X, clusters):
K = range(2,clusters+1)
# 构建空列表,用于存储个中簇数下的轮廓系数
S = []
for k in K:
kmeans = KMeans(n_clusters=k)
kmeans.fit(X)
labels = kmeans.labels_
# 计算轮廓系数
S.append(silhouette_score(X, labels))
# 设置绘图风格
plt.style.use('ggplot')
# 绘制K的个数与轮廓系数的关系
plt.plot(K, S, 'b*-')
plt.xlabel('簇的个数')
plt.ylabel('轮廓系数')
# 显示图形
plt.show()
# 自定义函数的调用(指定原始数据和选取范围)
k_silhouette(data_new, 10)
# 重新建模
K_means_1 = KMeans(n_clusters = 2)
K_means_1.fit(data_new)
data_new['type'] = K_means_1.labels_
print("轮廓系数",silhouette_score(data_new, data_new['type']))
print("CH值",calinski_harabasz_score(data_new, data_new['type']))
# %matplotlib notebook
fig = plt.figure()
ax = Axes3D(fig)
d=df[data_new['type'] == 0].values
print(d.shape)
ax.scatter(d[:,0],d[:,1],d[:,2], c='r')
d=df[data_new['type'] == 1].values
print(d.shape)
ax.scatter(d[:,0],d[:,1], d[:,2],c='g')
plt.show()
读取数据后,使用箱型图剔除异常值,异常值为不在箱型图的上限于下限之间的数据,仅保留在这之间的数据。经过观察发现原数据集中前两列为多分类变量应转化为哑变量用于训练模型;而其余变量间数据差异大,应使用 StandardScaler对其进标准化后在用于训练模型。使用k-means算法训练的得到的模型经过调参后,将原数据分为了两个类别,所得到的轮廓系数为 0.32465655576864333,CH值为154.75138523338046。轮廓系数较接近于1且CH值大,模型分类效果良好。