python Kmeans聚类之后如何给数据贴上聚类的标签?

用了二分Kmeans 来聚类 质心和聚类的簇都得到了 不知道如何给每一条数据贴上具体的标签,和怎么检验聚类的准确性
关注者
30
被浏览
95,154

12 个回答

k-均值是一种聚类算法。聚类算法根据数据在空间中的分布方式将其分组。聚类是一种非监督学习方法,它不需要任何形式的标签——这种算法的目的就是基于数据本身的结构推测出簇标签。一个简单的例子,假如从事相同体育项目的运动员是同一类运动员,他们在具体的身体特征上是相似的,表现为空间上比较接近,自然得将其归为一类。

”聚类一时爽,判断两行泪“——这是解决任何一个无监督问题时都会面临的苦恼,比如,无监督问题的项目——给一群无标签的结构化数据贴标签,首先开始查阅资料,然后把EDA(数据探索) 、特征工程、数据标准等三板斧悉数打出,最后输入'KMeans(n_clusters= num).fit(x)'

然而,在几分钟之后,映入眼帘的只是一个个'簇'的索引,真是一顿操作猛如虎,定睛一看都是'簇’,看着这一连串‘莫的感情’、没有任何意义的簇的序列 ,到底如何才能根据合适的理由判断哪一簇(类)是学生、哪一簇(类)是教师?

方法一:实际调研总结规律。由于学生群体过于庞大,可以从询问教师群体开始,因为教师授课的同时学生也在听课,所以二者具有一定的关联性,通过总结教师群体用户的行为特征,推测学生用户的行为特征,例如可以询问教师们最早从二月份的哪一日开始使用钉钉,通常在工作日和节假日的具体哪个时段在哪个教育类app比较活跃等等。

随即我便打消了这个念头,首先,找到目标群体——该地区教师用户耗费的成本过大;其次,虽然上课有统一的时间,但是上传课件、打卡等操作时间因人而异,最后可能只得到一个分布平均的数据,不便于总结教师用户行为特征;假如统计上课时间,虽然分布特征明显,但因为和学生用户是统一的,所以也难以区分是教师的行为特征还是学生的。

方法二:发放问卷。让不同的被调查者根据每一簇的行为特征决定该簇用户所属标签的最大可能性,然后根据每一簇的票数分布,判断该簇用户的所属群体。设定这一方法的前提是,聚类之后,簇与簇之间的行为特征差异明显,虽然也可以主观判断,但是说服力不足,让不同的调查者共同决定,可以推测不同群体的行为习惯,增强判断的可信度。

在本案例中,我们通过各类广告渠道90天内额日均UV,平均注册率、平均搜索率、访问深度、平均停留时长、订单转化率、投放时间、素材类型、广告类型、合作方式、广告尺寸和广告卖点等特征,将渠道分类,找出每类渠道的重点特征,为加下来的业务讨论和数据分析提供支持。

导入分析过程所需类库

import pandas as pd  
import matplotlib.pyplot as plt
import matplotlib,os
import numpy as np
import seaborn as sns
from sklearn.preprocessing import OneHotEncoder #文本向量化
from sklearn.preprocessing import MinMaxScaler #数据标准化
from sklearn.cluster import KMeans #聚类算法模型
from sklearn.metrics import silhouette_score  #轮廓系数
import warnings
warnings.filterwarnings("ignore")
matplotlib.rcParams['font.sans-serif'] = ['SimHei']
matplotlib.rcParams['axes.unicode_minus']=False
sns.set(font='SimHei')

导入原始数据

data = pd.read_csv('ad_performance.csv').iloc[:,1:]
data.head()

查看数据基本信息

print("\n"+"{:*^40}".format(' Data Info ')+"\n")
print(data.info())

print("\n"+"{:*^40}".format(' Null ')+"\n")
print(data.isnull().sum())

print("\n"+"{:*^40}".format(' Duplicated ')+"\n")
print(data.duplicated().value_counts())

print("\n"+"{:*^40}".format(' Shape ')+"\n")
print(data.shape)

out:

************** Data Info ***************

<class 'pandas.core.frame.DataFrame'>
RangeIndex: 889 entries, 0 to 888
Data columns (total 13 columns):
渠道代号      889 non-null object
日均UV      889 non-null float64
平均注册率     889 non-null float64
平均搜索量     889 non-null float64
访问深度      889 non-null float64
平均停留时间    887 non-null float64
订单转化率     889 non-null float64
投放总时间     889 non-null int64
素材类型      889 non-null object
广告类型      889 non-null object
合作方式      889 non-null object
广告尺寸      889 non-null object
广告卖点      889 non-null object
dtypes: float64(6), int64(1), object(6)
memory usage: 90.4+ KB
None

***************** Null *****************

渠道代号      0
日均UV      0
平均注册率     0
平均搜索量     0
访问深度      0
平均停留时间    2
订单转化率     0
投放总时间     0
素材类型      0
广告类型      0
合作方式      0
广告尺寸      0
广告卖点      0
dtype: int64

************** Duplicated **************

False    889
dtype: int64

**************** Shape *****************

(889, 13)

查看数据描述统计

data.describe().T.round(2)

查看数据相关系数

data.corr().round(2)

相关系数热力图

sns.heatmap(data.corr().round(2), cmap="Reds", annot=True)


接下来是数据处理部分:

删除包含缺失项内容

raw_data = data.drop(['平均停留时间'], axis=1)

查看类别变量取值范围

cols=["素材类型","广告类型","合作方式","广告尺寸","广告卖点"]
for i in cols:
    df = raw_data[i].unique()
print("变量【{0}】的取值有:\n{1}".format(i, df)+"\n")
print("-."*20+"\n")

out:

变量【素材类型】的取值有:
['jpg' 'swf' 'gif' 'sp']

-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.

变量【广告类型】的取值有:
['banner' 'tips' '不确定' '横幅' '暂停']

-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.

变量【合作方式】的取值有:
['roi' 'cpc' 'cpm' 'cpd']

-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.

变量【广告尺寸】的取值有:
['140*40' '308*388' '450*300' '600*90' '480*360' '960*126' '900*120'
'390*270']

-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.

变量【广告卖点】的取值有:
['打折' '满减' '满赠' '秒杀' '直降' '满返']

-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.

字符串分类热编码处理

model_one = OneHotEncoder(sparse=False)
one_matrix = model_one.fit_transform(raw_data[cols])
print(one_matrix[:2])

out:
[[0. 1. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 1. 1. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0.
  0. 0. 0.]
 [0. 1. 0. 0. 1. 0. 0. 0. 0. 1. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0.
  0. 0. 0.]]

标准化处理

scaler = MinMaxScaler()
raw_data_scaler = scaler.fit_transform(raw_data.iloc[:,1:7])
print(raw_data_scaler[:2])

out:

[[1.43508267e-04 1.81585678e-01 2.06364513e-02 1.33404913e-02
  1.19168591e-01 6.55172414e-01]
 [7.06234624e-03 1.02301790e-01 3.12439730e-02 1.07052569e-02
  1.38568129e-02 6.20689655e-01]]

合并训练数据

X = np.hstack((raw_data_scaler, one_matrix))

建立与训练模型

通过平均轮廓系数检验得到最佳KMeans聚类模型

score_list = []
silhouette_int = -1
for n_clusters in range(2,8):
    model_kmeans = KMeans(n_clusters=n_clusters)
    labels_tmp = model_kmeans.fit_predict(X)
    silhouette_tmp = silhouette_score(X, labels_tmp)
if silhouette_tmp > silhouette_int:
        best_k = n_clusters
        silhouette_int = silhouette_tmp
        best_kmeans = model_kmeans
        cluster_labels_k = labels_tmp
    score_list.append([n_clusters, silhouette_tmp])

print("\n"+"{:*^60}".format(" K值对应轮廓系数: ")+"\n")
print(np.array(score_list))
print('最优的K值是:{0} \n对应的轮廓系数是:{1}'.format(best_k, silhouette_


out:

************************ K值对应轮廓系数: *************************

[[2.         0.38655493]
 [3.         0.45757883]
 [4.         0.50209812]
 [5.         0.4800359 ]
 [6.         0.47761127]
 [7.         0.50016363]]
最优的K值是:4
对应的轮廓系数是:0.5020981194788054

建立聚类标签,合并数据

cluster_labels = pd.DataFrame(cluster_labels_k, columns=['clusters']) 
merge_data = pd.concat((raw_data,cluster_labels),axis=1)

查看各聚类标签信息

clustering_count = pd.DataFrame(merge_data['渠道代号'].groupby(merge_data['clusters']).count()).T.rename({'渠道代号': 'counts'})  # 计算每个聚类类别的样本量
clustering_ratio = (clustering_count / len(merge_data)).round(2).rename({'counts': 'percentage'})  # 计算每个聚类类别的样本量占比
print(clustering_count)
print("#"*30)
print(clustering_ratio)

out:

clusters    0    1    2   3
counts    349  313  154  73
##############################
clusters       0     1     2     3
percentage  0.39  0.35  0.17  0.08

各聚类中心主要特征提取:

cluster_features = []  # 空列表,用于存储最终合并后的所有特征信息
for line in range(best_k):  # 读取每个类索引
    label_data = merge_data[merge_data['clusters'] == line]  # 获得特定类的数据

    part1_data = label_data.iloc[:, 1:7]  # 获得数值型数据特征
    part1_desc = part1_data.describe().round(3)  # 得到数值型特征的描述性统计信息
    merge_data1 = part1_desc.iloc[2, :]  # 得到数值型特征的均值

    part2_data = label_data.iloc[:, 7:-1]  # 获得字符串型数据特征
    part2_desc = part2_data.describe(include='all')  # 获得字符串型数据特征的描述性统计信息
    merge_data2 = part2_desc.iloc[2, :]  # 获得字符串型数据特征的最频繁值

    merge_line = pd.concat((merge_data1, merge_data2), axis=0)  # 将数值型和字符串型典型特征沿行合并
    cluster_features.append(merge_line)  # 将每个类别下的数据特征追加到列表
cluster_pd = pd.DataFrame(cluster_features).T  # 将列表转化为矩阵
all_cluster_set = pd.concat((clustering_count, clustering_ratio, cluster_pd),axis=0)  # 将每个聚类类别的所有信息合并
all_cluster_set

图形化输出

数据预处理

num_sets = cluster_pd.iloc[:6, :].T.astype(np.float64)  # 获取要展示的数据
num_sets_max_min = scaler.fit_transform(num_sets)  # 获得标准化后的数据
print(num_sets)
print('-'*60)
print(num_sets_max_min)

out:
       日均UV  平均注册率  平均搜索量   访问深度  订单转化率  投放总时间
0   933.015  0.003  0.064  5.916  0.006  8.770
1  1390.013  0.003  0.152  1.168  0.017  8.199
2  2717.419  0.005  0.051  0.947  0.007  8.529
3  1904.371  0.003  0.106  0.943  0.009  8.217
------------------------------------------------------------
[[0.00000000e+00 0.00000000e+00 1.28712871e-01 1.00000000e+00
  0.00000000e+00 1.00000000e+00]
 [2.56106801e-01 0.00000000e+00 1.00000000e+00 4.52443193e-02
  1.00000000e+00 0.00000000e+00]
 [1.00000000e+00 1.00000000e+00 0.00000000e+00 8.04343455e-04
  9.09090909e-02 5.77933450e-01]
 [5.44358789e-01 0.00000000e+00 5.44554455e-01 0.00000000e+00
  2.72727273e-01 3.15236427e-02]]

雷达图展示

fig = plt.figure(figsize=(6,6))  # 建立画布
ax = fig.add_subplot(111, polar=True)
labels = np.array(merge_data1.index)  
cor_list = ['#FF0000', '#FF7400', '#009999', '#00CC00']
angles = np.linspace(0, 2 * np.pi, len(labels), endpoint=False)
angles = np.concatenate((angles, [angles[0]])) 
for i in range(len(num_sets)):  # 循环每个类别
    data_tmp = num_sets_max_min[i, :]  # 获得对应类数据
    data = np.concatenate((data_tmp, [data_tmp[0]]))  # 建立相同首尾字段以便于闭合
    ax.plot(angles, data, 'o-', c=cor_list[i], label="第%d类渠道"%(i))  # 画线
    ax.fill(angles, data,alpha=0.5)
ax.set_thetagrids(angles * 180 / np.pi, labels, fontproperties="SimHei")  # 设置极坐标轴
ax.set_title("各聚类类别显著特征对比", fontproperties="SimHei")  # 设置标题放置
ax.set_rlim(-0.2, 1.2)  # 设置坐标轴尺度范围
plt.legend(loc="upper right" ,bbox_to_anchor=(1.2,1.0))  # 设置图例位置

以上就是本次分析所有代码内容,那么通过以上分析能得出怎样的结论呢?欢迎在评论区踊跃发言。