秀,用NBA球员数据学透K-Means聚类
這次我們用 NBA 球員賽季表現(xiàn)聚類來探討下 K-Means 算法,K-Means 是一個清晰明白的無監(jiān)督學(xué)習(xí)方法,和 KNN 有很多相似點(diǎn),例如都有超參數(shù) K,前者是 K 個類別,后者是 K 個鄰居。
聚類算法是不需要標(biāo)簽的,直接從數(shù)據(jù)的內(nèi)在性之中學(xué)習(xí)最優(yōu)的分類結(jié)果,或者說確定離散標(biāo)簽類型。K-Means 聚類算法是其中最簡單、最容易理解的。
簡單即高效。我們的目標(biāo)是學(xué)習(xí)一個東西,然后把它的思想應(yīng)用到我們想要探索的場景,以加深對算法的理解。
最優(yōu)的聚類結(jié)果需要滿足兩個假設(shè):
“簇中心點(diǎn)”(cluster center)是屬于該簇的所有的數(shù)據(jù)點(diǎn)坐標(biāo)的算術(shù)平均值
一個簇的每個點(diǎn)到該簇中心點(diǎn)的距離,比到其他簇中心點(diǎn)的距離短
于是 K-Means 聚類的工作流為
隨機(jī)猜測一些簇中心點(diǎn)
將樣本分配至離其最近的簇中心點(diǎn)上去
將簇中心點(diǎn)設(shè)置為所有點(diǎn)坐標(biāo)的平均值
重復(fù) 2 和 3 直至收斂
我們會編碼實(shí)現(xiàn) K-Means 算法,并用于 NBA 控衛(wèi) 2020-21 常規(guī)賽季的表現(xiàn)分析中。
我們知道過去剛剛結(jié)束的這個賽季得分王是萌神庫里,我愛他。我們這里只關(guān)注控衛(wèi),一是因?yàn)榭匦l(wèi)線上星光熠熠,二是數(shù)據(jù)分析要細(xì)分,這本身是一個原則。
我們開始吧!
01
學(xué)習(xí)原理方法
找到數(shù)據(jù)
我們可以從https://www.basketball-reference.com找到我們想要的數(shù)據(jù),那就是NBA球員在過去一個賽季的統(tǒng)計(jì)數(shù)據(jù)。數(shù)據(jù)獲取流程如下
看看數(shù)據(jù)
#?三劍客來一遍 import?pandas?as?pd import?numpy?as?np import?matplotlib.pyplot?as?pltnba?=?pd.read_csv("nba_2020.csv") nba.head()我們發(fā)現(xiàn)姓名列有個\,后面跟著不知道是什么的簡稱,可以處理掉。另外字段名全是簡寫,我一個球迷有很多都看不懂,尷尬。整理了下字段含義,我們看一遍,這樣就大概了解了這個數(shù)據(jù)集有什么了。
Age:年齡(別告訴我你知不知道)
TM(team):球隊(duì)
Lg(league):聯(lián)盟
Pos(position):位置
PG(Point Guard):組織后衛(wèi)
GS(Games Started):首發(fā)出場次數(shù)
MP(Minutes Played Per Game):場均上場時間
FG(Field Goals Per Game):場均命中數(shù)
FGA(Field Goal Attempts Per Game):場均投籃數(shù)
FG%(Field Goal Percentage):命中率
3P(3-Point Field Goals Per Game):三分球命中率
3PA (3-Point Field Goal Attempts Per Game):場均三分球投籃數(shù)
3P% (3-Point Field Goal Percentage):三分球命中率
2P (2-Point Field Goals Per Game):兩分球命中數(shù)
2PA (2-Point Field Goal Attempts Per Game):場均兩分投籃數(shù)
2P% (2-Point Field Goal Percentage):兩分球命中率
eFG% (Effective Field Goal Percentage):有效命中率
FT (Free Throws Per Game):場均罰球命中數(shù)
FTA (Free Throw Attempts Per Game):場均罰球數(shù)
FT% (Free Throw Percentage):罰球命中率
ORB (Offensive Rebounds Per Game):場均進(jìn)攻籃板數(shù)
DRB (Defensive Rebounds Per Game):場均防守籃板數(shù)
TRB (Total Rebounds Per Game):總籃板數(shù)
AST (Assists Per Game):場均助攻
STL (Steals Per Game):場均搶斷
BLK (Blocks Per Game):場均蓋帽
TOV (Turnovers Per Game):場均失誤
PF (Personal Fouls Per Game):場均個人犯規(guī)
PTS (Points Per Game):場均得分
處理數(shù)據(jù)
#?球員名字取\前面的字符 nba['Player']?=?nba['Player'].apply(lambda?x:?x.split('\\')[0]) #?取控衛(wèi)PG的樣本進(jìn)行分析 point_guards?=?nba[nba['Pos']?==?'PG'] #?剔除0失誤的控衛(wèi)數(shù)據(jù),0tov意味著場次太少,且0不能被除 point_guards?=?point_guards[point_guards['TOV']?!=?0] #?定義ATR為助攻失誤比,并計(jì)算出來 point_guards['ATR']?=?point_guards['AST']?/?point_guards['TOV']處理后的數(shù)據(jù)變成了下面的樣子,我們剩下了 124 個控衛(wèi)數(shù)據(jù)。
判斷一個控衛(wèi)優(yōu)不優(yōu)秀,最重要的兩個指標(biāo)是場均得分和場均助攻失誤比,也就是PTS和ATR。下面我們就用這兩個關(guān)鍵特征對球員聚類。
探索數(shù)據(jù)
看下這些球員的場次得分與助攻失誤比散點(diǎn)圖,這往往是數(shù)據(jù)分析和建模的第一步。
#?改善下繪圖風(fēng)格 import?seaborn?as?sns sns.set()# nba聯(lián)盟控衛(wèi)場次得分與助攻失誤比散點(diǎn)圖 plt.scatter(point_guards['PTS'],?point_guards['ATR'],?c='y') plt.title("Point?Guards") plt.xlabel('Points?Per?Game',?fontsize=13) plt.ylabel('Assist?Turnover?Ratio',?fontsize=13) plt.show()最右邊得分超過 30 的你不用猜,就是這個男人。
確認(rèn)下數(shù)據(jù),場均得分大于 25 的過濾出來看下,果然是庫里,東契奇、歐文、利拉德等人赫然在列。這些超巨得分是高,但看起來助攻失誤比差不多都在平均線上下。這是合理的。其實(shí)有點(diǎn)干的越多錯的越多的意思,工作上亦是如此。
看了下助攻失誤比超高或超低的,我都不認(rèn)識就不說了。
我們聚類吧
從散點(diǎn)圖來看,其實(shí)并沒有明顯的簇,但我們可以人為定義任意個簇。我們還是分成 5 類。于是我們開始做聚類的第一步。
STEP1:隨機(jī)認(rèn)定 5 個樣本點(diǎn)作為簇的中心點(diǎn)
num_clusters?=?5 #?從樣本里隨機(jī)選5個出來作為5個簇的起始中心點(diǎn) random_initial_points?=?np.random.choice(point_guards.index,?size=num_clusters) centroids?=?point_guards.loc[random_initial_points] #?畫出散點(diǎn)圖,包括5個隨機(jī)選取的聚類中心點(diǎn) plt.scatter(point_guards['PTS'],?point_guards['ATR'],?c='y') plt.scatter(centroids['PTS'],?centroids['ATR'],?c='red') plt.title("Centroids") plt.xlabel('Points?Per?Game',?fontsize=13) plt.ylabel('Assist?Turnover?Ratio',?fontsize=13) plt.show()隨后就是不斷迭代優(yōu)化簇中心點(diǎn)的過程,為了方便,我們將中心點(diǎn)的坐標(biāo)存在一個字典里。
def?centroids_to_dict(centroids):dictionary?=?dict()#?iterating?counter?we?use?to?generate?a?cluster_idcounter?=?0#?iterate?a?pandas?data?frame?row-wise?using?.iterrows()for?index,?row?in?centroids.iterrows():coordinates?=?[row['PTS'],?row['ATR']]dictionary[counter]?=?coordinatescounter?+=?1return?dictionarycentroids_dict?=?centroids_to_dict(centroids)上圖我們看到,隨機(jī)選出來的centroids,我們把它存在了一個centroids_dict里面。
STEP2:將樣本分配至離其最近的簇中心點(diǎn)上去
這里涉及兩個計(jì)算,一個是距離的度量,一個是最小元素的查找。前者我們采用歐拉距離,后者是選擇排序的精髓。
先定義好這兩個計(jì)算函數(shù),如下。
import?mathdef?calculate_distance(centroid,?player_values):root_distance?=?0for?x?in?range(0,?len(centroid)):difference?=?centroid[x]?-?player_values[x]squared_difference?=?difference**2root_distance?+=?squared_differenceeuclid_distance?=?math.sqrt(root_distance)return?euclid_distancedef?assign_to_cluster(row):lowest_distance?=?-1closest_cluster?=?-1for?cluster_id,?centroid?in?centroids_dict.items():df_row?=?[row['PTS'],?row['ATR']]euclidean_distance?=?calculate_distance(centroid,?df_row)if?lowest_distance?==?-1:lowest_distance?=?euclidean_distanceclosest_cluster?=?cluster_idelif?euclidean_distance?<?lowest_distance:lowest_distance?=?euclidean_distanceclosest_cluster?=?cluster_idreturn?closest_cluster執(zhí)行這兩個函數(shù),我們就完成了 STEP2,我們可視化看下完成后的結(jié)果。
point_guards['cluster']?=?point_guards.apply(lambda?row:?assign_to_cluster(row),?axis=1)def?visualize_clusters(df,?num_clusters):colors?=?['b',?'g',?'r',?'c',?'m',?'y',?'k']for?n?in?range(num_clusters):clustered_df?=?df[df['cluster']?==?n]plt.scatter(clustered_df['PTS'],?clustered_df['ATR'],?c=colors[n-1])plt.xlabel('Points?Per?Game',?fontsize=13)plt.ylabel('Assist?Turnover?Ratio',?fontsize=13)plt.show()visualize_clusters(point_guards,?5)以上,5 個類別分別以不同的顏色標(biāo)識出來了,但顯然這是隨機(jī)簇的結(jié)果,劃分的 5 類結(jié)果并沒有太好。我們還需要 STEP3。
STEP3:將簇中心點(diǎn)設(shè)置為所有點(diǎn)坐標(biāo)的平均值
def?recalculate_centroids(df):new_centroids_dict?=?dict()for?cluster_id?in?range(0,?num_clusters):values_in_cluster?=?df[df['cluster']?==?cluster_id]#?Calculate?new?centroid?using?mean?of?values?in?the?clusternew_centroid?=?[np.average(values_in_cluster['PTS']),?np.average(values_in_cluster['ATR'])]new_centroids_dict[cluster_id]?=?new_centroidreturn?new_centroids_dictSTEP4:重復(fù) 2 和 3
#?多次迭代,試試100輪吧 for?_?in?range(0,100):centroids_dict?=?recalculate_centroids(point_guards)point_guards['cluster']?=?point_guards.apply(lambda?row:?assign_to_cluster(row),?axis=1)visualize_clusters(point_guards,?num_clusters)最終的結(jié)果如上,我們看到效果已經(jīng)得到了很好的優(yōu)化。高得分的在一個簇,高助攻失誤比的在一個簇。
以上,我們寫了很多代碼。
手寫算法是為了學(xué)習(xí)和理解。工程上,我們要充分利用工具和資源。
sklearn 庫就包含了我們常用的機(jī)器學(xué)習(xí)算法實(shí)現(xiàn),可以直接用來建模。
from?sklearn.cluster?import?KMeanskmeans?=?KMeans(n_clusters=num_clusters) kmeans.fit(point_guards[['PTS',?'ATR']]) point_guards['cluster']?=?kmeans.labels_visualize_clusters(point_guards,?num_clusters)短短五行代碼就完成了我們從零開始寫的百來行代碼,效果看起來也很合理。值得說明的是,聚類受起始點(diǎn)的影響,可能不會達(dá)到全局最優(yōu)結(jié)果。細(xì)心的朋友一定看出來了,上面兩個最終聚類結(jié)果是有差異的。
02
風(fēng)控中的應(yīng)用
如果學(xué)習(xí)了一項(xiàng)技能,但是不知道怎么用,那就毫無意義。
想想風(fēng)控中聚類可以用來干什么呢?風(fēng)控中我們有什么數(shù)據(jù),關(guān)注什么結(jié)果呢?
一個有意思的課題是,用戶手機(jī)安裝的 app 能不能區(qū)分用戶的風(fēng)險。答案顯然是肯定的,
除了必要的社交、支付、生活和工具類 app 外,那些差異化的 app 偏好顯然刻畫不同類型的用戶。安裝了很多小貸平臺的用戶,就很可能是一個多頭客戶,在想著辦法擼口子。
于是我們可以用 app 的安裝情況來給用戶聚類,假設(shè)采集了用戶在 100 個 app 的安裝情況,就可以對這 100 個 0、1 變量聚類。
聚成兩類后,我們可以采用其中有標(biāo)簽的用戶進(jìn)行驗(yàn)證,如果好壞用戶絕大部分都正確地被劃分開了,那么有理由相信那些沒有標(biāo)簽的用戶大概率就等于簇的標(biāo)簽。
我沒有對應(yīng)的數(shù)據(jù)可以舉例,但有類似的數(shù)據(jù)來說明問題。下面是美國國會的數(shù)據(jù),party 代表了議員的黨派,R 是共和黨 Republican,D 是民主黨 Democratic,I 是中間黨派。其余數(shù)據(jù)均為 0、1 變量。這里的 party 就類似風(fēng)控中的好壞用戶。
import?pandas?as?pd votes?=?pd.read_csv("114_congress.csv") votes.head()于是我們開始無監(jiān)督聚類。注意以下并未用到 party 這個標(biāo)簽。
#?用k-means?clustering方法 from?sklearn.cluster?import?KMeanskmeans_model?=?KMeans(n_clusters=2,?random_state=1) senator_distances?=?kmeans_model.fit_transform(votes.iloc[:,?3:])labels?=?kmeans_model.labels_我們需要用標(biāo)簽去對聚類結(jié)果做驗(yàn)證。
可以看到,分成了 0 和 1 兩個簇中,0 簇中 43 個議員有 41 個都是民主黨,2 是無黨派人士;1 簇中 57 個議員有 54 個都是共和黨,3 個是民主黨。數(shù)據(jù)總有異常,或者用戶總有一部分比較奇怪,我們不追求 100%準(zhǔn)確。
不完美才是人生的最完美。
推薦閱讀
Pandas處理數(shù)據(jù)太慢,來試試Polars吧!
懶人必備!只需一行代碼,就能導(dǎo)入所有的Python庫
絕!關(guān)于pip的15個使用小技巧
介紹10個常用的Python內(nèi)置函數(shù),99.99%的人都在用!
可能是全網(wǎng)最完整的 Python 操作 Excel庫總結(jié)!
與50位技術(shù)專家面對面20年技術(shù)見證,附贈技術(shù)全景圖總結(jié)
以上是生活随笔為你收集整理的秀,用NBA球员数据学透K-Means聚类的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 硬核图解,再填猛将!
- 下一篇: 再见,Visio!