k-means是针对聚类所得簇划分的最小化平方误差
采用的是贪心的策略(最小化式不容易解决,属于NP难问题),主要分三步进行
1.初始化,随机分配簇的中心
2.反复迭代计算簇中心
3.等到为簇中心分配的数据点保持不变之后,得到簇中心
以下通过一个简单的算法实例来进行说明(原本例子来源于中国大学MOOC-北京理工大学-Python机器学习应用,其中样本集由本人经百度文库下载得到,对于MOOC中的代码也有略微改动)
本实例是通过城镇居民消费性支出数据对于中国大陆省份进行聚类分析,主要通过sk-learn库实现
import numpy as np
from sklearn.cluster import KMeans
def loadData(filePath):
fr = open(filePath,'r',encoding='UTF-8')
lines = fr.readlines()
retData = []
retCityName = []
for line in lines:
line=line.encode('utf-8').decode('utf-8-sig')#去掉\ufeff非法字符, 参见https://www.jb51.net/article/116820.htm
items = line.strip().split(" ")
retCityName.append(items[0])
retData.append([float(items[i]) for i in range(1,len(items))])
for i in range(1,len(items)):
return retData,retCityName
if __name__ == '__main__':
data,cityName = loadData('city.txt')
km = KMeans(n_clusters=3)
label = km.fit_predict(data)
expenses = np.sum(km.cluster_centers_,axis=1)
#print(expenses)
CityCluster = [[],[],[]]
for i in range(len(cityName)):
CityCluster[label[i]].append(cityName[i])
for i in range(len(CityCluster)):
print("Expenses:%.2f" % expenses[i])
print(CityCluster[i])
数据集(将百度文库的数据进行了删减和预处理,保存为city.txt,存放在代码的同一目录下)
北京 19934.48 6392.9 409.02 54.33 77.39 136.85 909.54 120.22 250.95 530.94 108.97
天津 16561.77 5940.44 385.73 51.34 59.61 130.85 865.7 158.77 443.79 521.49 98.08
河北 10318.32 3335.23 326.16 37.79 50.84 119.09 578.26 103.51 141.81 403.14 55.43
山西 9792.65 3052.57 369.5 64.52 64.93 88.12 432.23 89.86 57.45 347.9 47.78
内蒙古 13994.62 4211.48 401.6 37.49 39.2 94.12 761.02 66.43 102.05 353.14 54.51
辽宁 13280.04 4658 387.35 52.27 65.31 113.27 738.95 104.74 364.23 499.4 75.88
吉林 11679.04 3767.85 391.04 47.39 68.69 106 657.07 89.84 173.7 462.56 66.91
黑龙江 10683.92 3784.72 429.72 64.97 66.13 109.58 692.99 94.9 185.57 420.29 65.21
上海 23200.4 7776.98 614.73 58.23 98.45 113.73 1136.23 117.29 817.64 622.01 79.76
江苏 14357.49 5243.14 373.38 47.99 82.77 108.09 1027.57 106.86 421.94 568.46 61.18
浙江 17858.2 6118.46 364.15 40.8 82.66 117.84 845.32 85.88 713.79 543.17 54.28
安徽 11512.55 4369.63 337.74 22.52 68.55 113.86 742.07 125.39 211.35 472.44 42.23
福建 14750.01 5790.72 469.71 44.02 72.96 120.13 1152.92 112.4 999.62 543.68 67.26
江西 10618.69 4195.38 353.11 23.81 86.22 164.49 916.4 89.11 254.69 581.89 51.35
山东 13118.24 4205.88 343.91 36.97 53.77 107.28 682.72 132.17 321.62 401.49 55.6
河南 10838.49 3575.75 349.36 48.11 60.95 105.41 611.01 110.61 78.49 373.02 55.32
湖北 11450.97 4429.3 516.4 29.69 83.47 152.31 853.27 96.35 267.47 615.32 68.05
湖南 11825.33 4322.09 329.92 24.13 80.32 164.97 918.32 75.51 219.42 512.8 50.63
广东 18489.53 6746.62 460.12 35.6 59.75 142.28 1640.6 83.75 657.73 615.84 58.8
广西 11490.08 4372.75 321.35 27.78 62.27 93.84 1280.94 69.24 293.42 428.87 38.57
海南 10926.71 4895.96 307.56 22.12 28.57 117.67 1285.53 51.22 669.83 528.01 45.62
重庆 13335.02 5012.56 311.81 39.19 62.47 199.45 1142.96 101.66 190.51 559.89 97.5
四川 12105.09 4779.6 336.37 53.77 61.95 166.62 1177.98 93.47 144.56 580.03 98.87
贵州 10058.29 4013.67 320 19.45 49.79 137.06 880.8 65.76 81.01 457.11 58.7
云南 11074.08 4593.49 335.31 31.99 49.7 81.44 882.9 72.53 122.38 562.9 46.78
西藏 9685.54 4847.58 432.38 33.54 5.21 163.4 1084.98 53.89 51.08 566.63 45.92
陕西 11821.88 4381.4 390.19 53.95 69.59 110.86 533.01 79.56 86.7 443.78 64.86
甘肃 9895.35 3702.18 352.83 40.73 46.27 134.3 529.16 70.19 72.29 437.07 62.17
青海 9613.79 3784.81 452.34 37.6 34.37 107.83 708.9 66.87 83.4 405.4 50.02
宁夏 11334.43 3768.09 343.1 49.36 48.16 111.58 626.04 56.92 70.21 370.71 47.67
新疆 10197.09 3694.81 362.74 41.08 36.93 131.07 815.84 68.38 87.82 363.33 41.76
下面针对代码的具体内容进行分析
首先,加载数据
data,cityName = loadData('city.txt')
def loadData(filePath):
fr = open(filePath,'r',encoding='UTF-8')
lines = fr.readlines()
retData = []
retCityName = []
for line in lines:
line=line.encode('utf-8').decode('utf-8-sig')#去掉\ufeff非法字符, 参见https://www.jb51.net/article/116820.htm
items = line.strip().split(" ")
retCityName.append(items[0])
retData.append([float(items[i]) for i in range(1,len(items))])
for i in range(1,len(items)):
return retData,retCityName
loadpath函数加载city.txt文件,注意编码方式为utf-8,建议直接使用sublime或者建立记事本后另存为使用utf-8编码的文件,否则中文会出现乱码(记事本默认的格式是ascii,会出现乱码情况)
readlines直接将文件读完,并且按行读取,最终返回为一个列表形式(拓展:read()也是直接将文件一次性读完,但存放到一个字符串中,readline()按行读取,但不将文件一次性读完)
接下来是切片,生成列表,最后分别遍历一次返回给主函数中的列表
再然后调用KMeans方法
km = KMeans(n_clusters=3)
label = km.fit_predict(data)
n_clusters是聚类中心的数目,默认参数为8,此处修改为3
fit_predict()是计算簇中心并为簇中心分配序号, 参数和返回值参见sk-learn的API官方文档,需要传入数据,然后传出编号的列表
CityCluster = [[],[],[]]
for i in range(len(cityName)):
CityCluster[label[i]].append(cityName[i])
for i in range(len(CityCluster)):
print("Expenses:%.2f" % expenses[i])
print(CityCluster[i])
通过每个城市对应的标签序号,将城市名归入不同行的二维列表,最后遍历输出
最终结果如下
Expenses:17097.17
['河北', '山西', '吉林', '黑龙江', '安徽', '江西', '河南', '湖北', '湖南', '广西', '海南', '四川', '贵州', '云南', '西藏', '陕西', '甘肃', '青海', '宁夏', '新疆']
Expenses:29844.05
['北京', '上海', '浙江', '广东']
Expenses:21815.38
['天津', '内蒙古', '辽宁', '江苏', '福建', '山东', '重庆']
按照常识就能够对于聚类效果进行检验,京沪粤以及江浙一带为经济最为发达的地区,大致归为了一类,这个聚类还是比较合理的
另外在知网上也有对于上述数据的统计学分析数据,可以另外作为测试的指标
还有一个小缺憾,更改n_clusters为大于3的数的时候会出现错误,可能由于lable或cityName列表为空导致,debug没有解决这个问题,今后有时间再说吧