数据挖掘一:K-Means

475 阅读8分钟

K-Means源码

from numpy import *
import numpy as np
# K-均值聚类支持函数
def loadDataSet(fileName):
    dataMat = []
    fr = open(fileName)
    for line in fr.readlines():
        curLine = line.strip().split('\t')
        fltLine = map(float, curLine) # 将curLine浮点化
        dataMat.append(fltLine)
    return dataMat

def distEclud(vecA, vecB): # 计算欧氏距离
    return sqrt(sum(power(vecA - vecB, 2)))

def randCent(dataSet, k): # 构建一个包含K个随机质心的集合
    n = shape(dataSet)[1]
    centroids = mat(zeros((k, n)))
    for j in range(n):
        minJ = min(dataSet[:, j])
        rangeJ = float(max(dataSet[:, j]) - minJ)
        centroids[:, j] = minJ + rangeJ * random.rand(k,1)
    return centroids
# K-Means聚类算法
def kMeans(dataSet, k, distMeas=distEclud, createCent=randCent):
    m = shape(dataSet)[0]
    clusterAssment = mat(zeros((m,2)))#create mat to assign data points 
                                      #to a centroid, also holds SE of each point
    centroids = createCent(dataSet, k)
    clusterChanged = True
    while clusterChanged:
        clusterChanged = False
        for i in range(m):#for each data point assign it to the closest centroid
            minDist = inf; minIndex = -1
            for j in range(k):
                distJI = distMeas(centroids[j,:],dataSet[i,:])
                if distJI < minDist:
                    minDist = distJI; minIndex = j
            if clusterAssment[i,0] != minIndex: clusterChanged = True
            clusterAssment[i,:] = minIndex,minDist**2
        print(centroids)
        for cent in range(k):#recalculate centroids
            ptsInClust = dataSet[nonzero(clusterAssment[:,0].A==cent)[0]]#get all the point in this cluster
            centroids[cent,:] = mean(ptsInClust, axis=0) #assign centroid to mean 
    return centroids, clusterAssment
# 二分K均值聚类算法(bisecting K-means)
def biKmeans(dataSet, k, distMeas=distEclud):
    m = shape(dataSet)[0]
    clusterAssment = mat(zeros((m,2)))
    centroid0 = mean(dataSet, axis=0).tolist()[0]
    centList =[centroid0]  # create a list with one centroid
    for j in range(m):    # calc initial Error
        clusterAssment[j,1] = distMeas(mat(centroid0), dataSet[j,:])**2
    while (len(centList) < k):
        lowestSSE = inf
        for i in range(len(centList)):
            ptsInCurrCluster = dataSet[nonzero(clusterAssment[:,0].A==i)[0],:]#get the data points currently in cluster i
            centroidMat, splitClustAss = kMeans(ptsInCurrCluster, 2, distMeas)
            sseSplit = sum(splitClustAss[:,1])#compare the SSE to the currrent minimum
            sseNotSplit = sum(clusterAssment[nonzero(clusterAssment[:,0].A!=i)[0],1])
            print("sseSplit, and notSplit: ",sseSplit,sseNotSplit)
            if (sseSplit + sseNotSplit) < lowestSSE:
                bestCentToSplit = i
                bestNewCents = centroidMat
                bestClustAss = splitClustAss.copy()
                lowestSSE = sseSplit + sseNotSplit
        bestClustAss[nonzero(bestClustAss[:,0].A == 1)[0],0] = len(centList) #change 1 to 3,4, or whatever
        bestClustAss[nonzero(bestClustAss[:,0].A == 0)[0],0] = bestCentToSplit
        print('the bestCentToSplit is: ',bestCentToSplit)
        print('the len of bestClustAss is: ', len(bestClustAss))
        centList[bestCentToSplit] = bestNewCents[0,:].tolist()[0]#replace a centroid with two best centroids 
        centList.append(bestNewCents[1,:].tolist()[0])
        clusterAssment[nonzero(clusterAssment[:,0].A == bestCentToSplit)[0],:]= bestClustAss#reassign new clusters, and SSE
    return mat(centList), clusterAssment

demo Yahoo map

import urllib
import json
def geoGrab(stAddress, city):
    apiStem = 'http://where.yahooapis.com/geocode?'  #create a dict and constants for the goecoder
    params = {}
    params['flags'] = 'J'#JSON return type
    params['appid'] = 'aaa0VN6k'
    params['location'] = '%s %s' % (stAddress, city)
    url_params = urllib.urlencode(params)
    yahooApi = apiStem + url_params      #print url_params
    print(yahooApi)
    c=urllib.urlopen(yahooApi)
    return json.loads(c.read())
from time import sleep
def massPlaceFind(fileName):
    fw = open(r'E:\资料\Python学习资料(、重要、)\005python机器学习\机器学习实战源码\machinelearninginaction\Ch10\places.txt', 'w')
    for line in open(fileName).readlines():
        line = line.strip()
        lineArr = line.split('\t')
        retDict = geoGrab(lineArr[1], lineArr[2])
        if retDict['ResultSet']['Error'] == 0:
            lat = float(retDict['ResultSet']['Results'][0]['latitude'])
            lng = float(retDict['ResultSet']['Results'][0]['longitude'])
            print("%s\t%f\t%f" % (lineArr[0], lat, lng))
            fw.write('%s\t%f\t%f\n' % (line, lat, lng))
        else: print("error fetching")
        sleep(1)
    fw.close()
    
def distSLC(vecA, vecB):#Spherical Law of Cosines
    a = sin(vecA[0,1]*pi/180) * sin(vecB[0,1]*pi/180)
    b = cos(vecA[0,1]*pi/180) * cos(vecB[0,1]*pi/180) * \
                      cos(pi * (vecB[0,0]-vecA[0,0]) /180)
    return arccos(a + b)*6371.0 #pi is imported with numpy
import matplotlib
import matplotlib.pyplot as plt
def clusterClubs(numClust=5):
    datList = []
    for line in open(r'E:\资料\Python学习资料(、重要、)\005python机器学习\机器学习实战源码\machinelearninginaction\Ch10\places.txt').readlines():
        lineArr = line.split('\t')
        datList.append([float(lineArr[4]), float(lineArr[3])])
    datMat = mat(datList)
    myCentroids, clustAssing = biKmeans(datMat, numClust, distMeas=distSLC)
    fig = plt.figure()
    rect=[0.1,0.1,0.8,0.8]
    scatterMarkers=['s', 'o', '^', '8', 'p', \
                    'd', 'v', 'h', '>', '<']
    axprops = dict(xticks=[], yticks=[])
    ax0=fig.add_axes(rect, label='ax0', **axprops)
    imgP = plt.imread(r'E:\资料\Python学习资料(、重要、)\005python机器学习\机器学习实战源码\machinelearninginaction\Ch10\Portland.png')
    ax0.imshow(imgP)
    ax1=fig.add_axes(rect, label='ax1', frameon=False)
    for i in range(numClust):
        ptsInCurrCluster = datMat[nonzero(clustAssing[:,0].A==i)[0],:]
        markerStyle = scatterMarkers[i % len(scatterMarkers)]
        ax1.scatter(ptsInCurrCluster[:,0].flatten().A[0], ptsInCurrCluster[:,1].flatten().A[0], marker=markerStyle, s=90)
    ax1.scatter(myCentroids[:,0].flatten().A[0], myCentroids[:,1].flatten().A[0], marker='+', s=300)
    plt.show()
clusterClubs(5)
[[-122.6985422    45.49225191]
 [-122.58377287   45.42378894]]
[[-122.67224529   45.52035345]
 [-122.53228195   45.49259215]]
[[-122.68313993   45.51691943]
 [-122.54110004   45.50418828]]
[[-122.69753113   45.51014305]
 [-122.55095081   45.5149589 ]]
[[-122.69551477   45.50729503]
 [-122.54868607   45.51882187]]
sseSplit, and notSplit:  3043.2633161055337 0.0
the bestCentToSplit is:  0
the len of bestClustAss is:  69
[[-122.84068545   45.59620636]
 [-122.74412946   45.41285132]]
[[-122.72836525   45.57987825]
 [-122.68703723   45.48856387]]
[[-122.72072414   45.59011757]
 [-122.69000022   45.48917759]]
sseSplit, and notSplit:  1435.437848696069 851.4388885817106
[[-122.60810742   45.56120752]
 [-122.42855662   45.52283237]]
[[-122.57664775   45.52983775]
 [-122.4927627    45.4967901 ]]
[[-122.57768158   45.53263337]
 [-122.49860291   45.49496564]]
sseSplit, and notSplit:  464.7205983452951 2191.824427523823
the bestCentToSplit is:  0
the len of bestClustAss is:  39
[[-122.83800807   45.60560043]
 [-122.75618313   45.56452542]]
[[-122.842918     45.646831  ]
 [-122.7003585    45.58066533]]
sseSplit, and notSplit:  48.18024019262078 2086.9191905495936
[[-122.46519092   45.46665924]
 [-122.59126547   45.53564291]]
[[-122.48812733   45.49597933]
 [-122.57463981   45.52861152]]
[[-122.49860291   45.49496564]
 [-122.57768158   45.53263337]]
sseSplit, and notSplit:  464.7205983452951 1435.437848696069
[[-122.63804603   45.48254302]
 [-122.64830388   45.40343501]]
[[-122.68629342   45.50489985]
 [-122.706063     45.42104783]]
sseSplit, and notSplit:  800.1013176074429 1051.3964353098966
the bestCentToSplit is:  2
the len of bestClustAss is:  32
[[-122.83699638   45.60631313]
 [-122.80120807   45.54937379]]
[[-122.842918     45.646831  ]
 [-122.7003585    45.58066533]]
sseSplit, and notSplit:  48.18024019262078 1651.5402061891537
[[-122.40178468   45.50910095]
 [-122.60557443   45.47146157]]
[[-122.4568086    45.4961344 ]
 [-122.56706156   45.52335936]]
sseSplit, and notSplit:  505.61960820164956 1000.0588643356288
[[-122.76298741   45.53997861]
 [-122.72960595   45.53693961]]
[[-122.78288433   45.4942305 ]
 [-122.65731615   45.50810065]]
sseSplit, and notSplit:  185.43955654824634 1213.0484488286252
[[-122.74169415   45.43685817]
 [-122.63427231   45.38378579]]
[[-122.7458315    45.42503625]
 [-122.626526     45.413071  ]]
sseSplit, and notSplit:  43.66877281157821 1689.845739398611
the bestCentToSplit is:  2
the len of bestClustAss is:  26

K-means 分析NBA球员数据

读取数据

# 加载第三包
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from sklearn.cluster import KMeans

合并数据

# 读取网页中的数据表
table = []
for i in range(1,7):
    table.append(pd.read_html('https://nba.hupu.com/stats/players/pts/%d' %i)[0][1:])
# 所有数据纵向合并为数据框    
players = pd.concat(table)
#players.head()
players.head(20)
0 1 2 3 4 5 6 7 8 9 10 11
1 1 詹姆斯-哈登 火箭 34.40 9.90-22.70 43.5% 4.40-12.60 35.2% 10.10-11.80 86.1% 61 36.70
2 2 布拉德利-比尔 奇才 30.60 10.40-22.90 45.5% 3.00-8.40 35.3% 6.80-8.00 84.3% 57 36.00
3 3 扬尼斯-阿德托昆博 雄鹿 29.60 10.90-20.00 54.7% 1.50-4.80 30.6% 6.30-10.00 63.3% 57 30.90
4 3 特雷-杨 老鹰 29.60 9.10-20.80 43.7% 3.40-9.50 36.1% 8.00-9.30 86% 60 35.30
5 5 达米安-利拉德 开拓者 28.90 9.20-20.00 45.7% 3.90-9.90 39.4% 6.70-7.60 88.8% 58 36.90
6 6 卢卡-东契奇 独行侠 28.70 9.50-20.60 46.1% 2.90-9.10 31.8% 6.80-9.10 75.2% 54 33.30
7 7 拉塞尔-威斯布鲁克 火箭 27.50 10.70-22.60 47.4% 1.00-3.80 25.4% 5.10-6.50 77.7% 53 35.90
8 8 科怀-伦纳德 快船 26.90 9.30-19.90 46.9% 2.10-5.70 36.6% 6.10-6.90 88.9% 51 32.20
9 9 安东尼-戴维斯 湖人 26.70 9.20-18.10 51.1% 1.20-3.50 33.5% 7.00-8.30 84.5% 55 34.30
10 10 德文-布克 太阳 26.10 8.80-18.00 48.7% 2.00-5.70 36% 6.50-7.10 91.6% 62 36.10
11 11 勒布朗-詹姆斯 湖人 25.70 9.80-19.60 49.8% 2.20-6.30 34.9% 4.00-5.70 69.7% 60 34.90
12 12 扎克-拉文 公牛 25.50 9.00-20.00 45% 3.10-8.10 38% 4.50-5.60 80.2% 60 34.80
13 13 布兰登-英格拉姆 鹈鹕 24.30 8.40-18.00 46.6% 2.50-6.30 38.7% 5.10-5.90 85.8% 56 34.30
14 14 多诺万-米切尔 爵士 24.20 8.90-19.60 45.3% 2.50-6.80 36.4% 4.00-4.60 85.9% 63 34.40
15 15 帕斯卡尔-西亚卡姆 猛龙 23.60 8.70-18.90 45.9% 2.20-6.00 35.9% 4.20-5.20 80% 53 35.50
16 15 杰森-塔特姆 凯尔特人 23.60 8.50-18.90 44.8% 2.80-7.10 39.8% 3.80-4.70 80.6% 59 34.60
17 17 CJ-麦科勒姆 开拓者 22.50 8.80-19.50 45.3% 2.80-7.40 38% 2.00-2.70 75% 62 36.00
18 18 德马尔-德罗赞 马刺 22.20 8.20-15.70 52.6% 0.10-0.50 26.7% 5.50-6.60 84.3% 61 34.30
19 19 肯巴-沃克 凯尔特人 21.20 7.00-16.60 42.1% 3.30-8.80 37.7% 3.90-4.50 86.7% 50 31.80
20 20 克里斯-米德尔顿 雄鹿 21.10 7.70-15.40 49.9% 2.40-5.80 41.8% 3.20-3.60 90.8% 55 30.10

数据清洗

#字段重命名
columns=['排名','球员','球队','得分','命中-出手','命中率','命中-三分','三分命中率','命中-罚球','罚球命中率','场次','上场时间']
players.columns=columns
players.to_excel('dummy.xlsx')
players.info()
<class 'pandas.core.frame.DataFrame'>
Int64Index: 237 entries, 1 to 37
Data columns (total 12 columns):
排名       236 non-null object
球员       236 non-null object
球队       235 non-null object
得分       236 non-null object
命中-出手    237 non-null object
命中率      237 non-null object
命中-三分    237 non-null object
三分命中率    237 non-null object
命中-罚球    237 non-null object
罚球命中率    237 non-null object
场次       236 non-null object
上场时间     236 non-null object
dtypes: object(12)
memory usage: 24.1+ KB
players[players.isnull().T.any()]
排名 球员 球队 得分 命中-出手 命中率 命中-三分 三分命中率 命中-罚球 罚球命中率 场次 上场时间
22 NaN NaN NaN NaN - 0% - 0% - 0% NaN NaN
17 216 文斯-卡特 NaN 5.00 1.80-5.10 35.2% 1.00-3.40 30.2% 0.40-0.50 79.3% 60 14.60
players=players.reset_index(drop=True)
players[players.isnull().T.any()]

排名 球员 球队 得分 命中-出手 命中率 命中-三分 三分命中率 命中-罚球 罚球命中率 场次 上场时间
171 NaN NaN NaN NaN - 0% - 0% - 0% NaN NaN
216 216 文斯-卡特 NaN 5.00 1.80-5.10 35.2% 1.00-3.40 30.2% 0.40-0.50 79.3% 60 14.60
players.drop(171,inplace=True)
players[players.isnull().T.any()]
排名 球员 球队 得分 命中-出手 命中率 命中-三分 三分命中率 命中-罚球 罚球命中率 场次 上场时间
216 216 文斯-卡特 NaN 5.00 1.80-5.10 35.2% 1.00-3.40 30.2% 0.40-0.50 79.3% 60 14.60
players.info()
<class 'pandas.core.frame.DataFrame'>
Int64Index: 236 entries, 0 to 236
Data columns (total 12 columns):
排名       236 non-null object
球员       236 non-null object
球队       235 non-null object
得分       236 non-null object
命中-出手    236 non-null object
命中率      236 non-null object
命中-三分    236 non-null object
三分命中率    236 non-null object
命中-罚球    236 non-null object
罚球命中率    236 non-null object
场次       236 non-null object
上场时间     236 non-null object
dtypes: object(12)
memory usage: 24.0+ KB
# 数据类型转换
players.得分 = players.得分.astype('float')
players.命中率 = players.命中率.str[:-1].astype('float')/100
players.三分命中率 = players.三分命中率.str[:-1].astype('float')/100
players.罚球命中率 = players.罚球命中率.str[:-1].astype('float')/100
players.场次 = players.场次.astype('int')
players.上场时间 = players.上场时间.astype('float')

players.head()


排名 球员 球队 得分 命中-出手 命中率 命中-三分 三分命中率 命中-罚球 罚球命中率 场次 上场时间
0 1 詹姆斯-哈登 火箭 34.4 9.90-22.70 0.435 4.40-12.60 0.352 10.10-11.80 0.861 61 36.7
1 2 布拉德利-比尔 奇才 30.6 10.40-22.90 0.455 3.00-8.40 0.353 6.80-8.00 0.843 57 36.0
2 3 扬尼斯-阿德托昆博 雄鹿 29.6 10.90-20.00 0.547 1.50-4.80 0.306 6.30-10.00 0.633 57 30.9
3 3 特雷-杨 老鹰 29.6 9.10-20.80 0.437 3.40-9.50 0.361 8.00-9.30 0.860 60 35.3
4 5 达米安-利拉德 开拓者 28.9 9.20-20.00 0.457 3.90-9.90 0.394 6.70-7.60 0.888 58 36.9

数据可视化

# 中文和负号的正常显示
plt.rcParams['font.sans-serif'] = ['Microsoft YaHei']
plt.rcParams['axes.unicode_minus'] = False
# 设置绘图风格
plt.style.use('ggplot')

# 绘制得分与命中率的散点图
players.plot(x='罚球命中率',y='命中率',kind='scatter')
plt.show()


K-means 聚类尝试

X = players[['罚球命中率','命中率']]
num_clusters = 2
kmeans = KMeans(n_clusters=num_clusters, random_state=1)
kmeans.fit(X)

# 聚类结果标签
players['cluster'] = kmeans.labels_

# 聚类中心
centers = kmeans.cluster_centers_

# 绘制散点图
plt.scatter(x = X.iloc[:,0], y = X.iloc[:,1], c = players['cluster'], s=50, cmap='rainbow')
plt.scatter(centers[:,0], centers[:,1], c='k', marker = '*', s = 180)
plt.xlabel('罚球命中率')
plt.ylabel('命中率')
# 图形显示
plt.show()

players.head()
排名 球员 球队 得分 命中-出手 命中率 命中-三分 三分命中率 命中-罚球 罚球命中率 场次 上场时间 cluster
0 1 詹姆斯-哈登 火箭 34.4 9.90-22.70 0.435 4.40-12.60 0.352 10.10-11.80 0.861 61 36.7 1
1 2 布拉德利-比尔 奇才 30.6 10.40-22.90 0.455 3.00-8.40 0.353 6.80-8.00 0.843 57 36.0 1
2 3 扬尼斯-阿德托昆博 雄鹿 29.6 10.90-20.00 0.547 1.50-4.80 0.306 6.30-10.00 0.633 57 30.9 0
3 3 特雷-杨 老鹰 29.6 9.10-20.80 0.437 3.40-9.50 0.361 8.00-9.30 0.860 60 35.3 1
4 5 达米安-利拉德 开拓者 28.9 9.20-20.00 0.457 3.90-9.90 0.394 6.70-7.60 0.888 58 36.9 1

寻找最优K值

from sklearn import metrics
X = players[['罚球命中率','命中率']]
K = range(2,int(np.sqrt(players.shape[0])))
GSSE = []
Gsilhouette=[]
for k in K:
    SSE = []
    kmeans = KMeans(n_clusters=k, random_state=10)
    kmeans.fit(X)
    labels = kmeans.labels_
    centers = kmeans.cluster_centers_
    for label in set(labels):
        SSE.append(np.sum(np.sum((players[['罚球命中率','命中率']].loc[labels == label,]-centers[label,:])**2)))
    GSSE.append(np.sum(SSE))
    
    silhouette = metrics.silhouette_score(X,kmeans.predict(X),metric='euclidean')
    Gsilhouette.append(silhouette)
    
# 绘制K的个数与GSSE的关系
plt.plot(K, GSSE, 'b*-')
plt.plot(K,Gsilhouette,'r*-')
plt.legend(['SSE','轮廓系数'])
plt.xlabel('聚类个数')
plt.ylabel('簇内离差平方和')
plt.title('选择最优的聚类个数')
plt.show()


选择最优K值,输出聚类结果

#调用sklearn的库函数
num_clusters = 6
kmeans = KMeans(n_clusters=num_clusters, random_state=1)
kmeans.fit(X)

# 聚类结果标签
players['cluster'] = kmeans.labels_
# 聚类中心
centers = kmeans.cluster_centers_

# 绘制散点图
plt.scatter(x = X.iloc[:,0], y = X.iloc[:,1], c = players['cluster'], s=50, cmap='rainbow')
plt.scatter(centers[:,0], centers[:,1], c='k', marker = '*', s = 180)
plt.xlabel('罚球命中率')
plt.ylabel('命中率')
# 图形显示
plt.show()