K-Means 算法是聚類算法的一種,所以先了解聚類算法。
聚類分析是在沒有給定劃分類別情況下,根據數據相似度進行樣本分組的一種方法。是一種無監督的學習算法。劃分依據主要是自身的距離或相似度將他們劃分為若干組,劃分原則是組內樣本最小化而組間(外部)距離最大化。聚類多數場景下用在「數據探索」環節,也就是用來了解數據。它無法提供明確的行動指向,更多是為後期挖掘和分析提供參考,無法回答「為什麼」和「怎麼辦」的問題。
聚類分析和分類區別:分類是從特定的數據中挖掘模式,作出分類判斷;聚類是根據數據本身特點,按照不同的模型來判斷數據之間的相似性、相似性高的一組數據聚成一簇。
K-Means 算法是基於距離的聚類方法,在最小誤差函數的基礎上將數據劃分為預定的類數 K,採用距離作為相似性的評價指標,認為兩個對象的距離越近,其相似度越高。適用於連續型數據。使用場景可以是:用戶分群分析。
算法過程
從 n 個樣本數據中隨機選取 K 個對象作為初始的聚類中心(在一開始確定 K 值上,憑業務經驗劃分,所以K值的選定不一定合理);
分別計算每個樣本到各個聚類中心的距離,將對象分配到距離最近的聚類中;
所有對象分配完成後,重新計算 K 個聚類的中心(類似重新計算虛擬中心);
與前一次計算得到的 K 個聚類中心比較,如果聚類中心發生變化,轉至步驟 2,否則轉至步驟 5;
度量距離:度量樣本之間的相似性最常用的是歐幾裡得距離、曼哈頓距離和閔可夫斯基距離(Python 中目前支持歐氏距離)。
偽代碼如圖所示
初始質心如何確定比較好?
所以可以看到,在 K-Means 中有一個重要的環節,那就是如何放置初始質心,初始質心放置的位置不同,聚類的結果很可能不一樣。一個好的初始質心可以避免更多的計算,讓算法收斂穩定且更快。算法的第一步是在 n 個樣本中隨機抽取 K 個對象作為初始聚類中心,在初始的時候可能會生成在一起,導致算法運算慢且不收斂,對於此類問題可以採用 K-means++ 作為優化,其核心就是選擇離已選中心點最遠的點,初始質心相互間離得儘可能遠。
如何確定K值?
在 K-Means 中,K 值是人為確定的,如果有業務經驗,可以根據業務經驗來判斷,如果此類數據沒有先驗知識,也不知道如何聚類,劃分的依據就依賴於組間差異最大化,組內差異最小化的評估指標。對於這個問題可以採用輪廓係數來判定,輪廓係數的取值為(-1,1),輪廓係數越接近於 1 越好,負數則表示聚類效果非常差。單個樣本的輪廓係數計算公式如下:
a:樣本與其自身所在的簇中的其他樣本的相似度,等於樣本與同一簇中所有其他點之間的平均距離;
b:樣本與其他簇中的樣本的相似度,等於樣本與下一個最近的簇中的所有點之間的平均距離。
值越接近 1 表示樣本與自己所在的簇中的樣本很相似,並且與其他簇中的樣本不相似;越接近 -1 說明樣本點與簇外的樣本更相似,與簇內樣本不相似;當輪廓係數接近 0 時,則代表簇類差異和簇外差異的樣本相似度一致,無明顯分界。
K-Means 案例演示數據集:航空公司客戶特徵欄位。
分析流程為:
import pandas as pd
描述性統計分析查看整體數據描述性分析
import numpy as np
import os
import seaborn as sns
import matplotlib.pyplot as plt
%matplotlib inline
plt.rcParams['font.family'] = ['Arial Unicode MS']
pd.set_option('display.max_columns', None)
# sns.set_style("darkgrid",{"font.sans-serif":['simhei','Droid Sans Fallback']})
os.chdir('/data')
df = pd.read_csv('air_data.csv')
df.head()explore = df.describe(percentiles=[], include='all').T
explore查看單個主體變量會員逐年增長情況
explore['null'] = len(df) - explore['count']
df_check = explore[['null', 'max', 'min']]
df_check.columns = [['空值記錄數', '最大值', '最小值']]
df_checkdf['FFP_DATE_year'] = pd.to_datetime(df['FFP_DATE']).dt.year
# 獲取每個年份的合計數量。方式一
count = df['FFP_DATE_year'].value_counts(sort=False).reset_index()
# 獲取每個年份的合計數量。方式二
# count = df.groupby('FFP_DATE')['FFP_DATE'].count()
count.columns = ['入會時間', '入會人數']
count.plot(kind='bar', x='入會時間', y='入會人數', figsize=(12,8))
查看單個分類型變量比例變化情況(直方堆積圖)# 使用數據交叉表,計算每個年度時間節點,出現男女分類的數量情況
cross_table = pd.crosstab(index=df['FFP_DATE_year'],columns=df['GENDER'])
cross_table# 通過div函數,讓分組每行合計等於1,用來看每個分組佔比
cross_table = cross_table.div(cross_table.sum(1), axis=0)
cross_tablecross_table.plot(kind='bar', stacked=True)
查看單個分類型變量佔比情況(直方圖&餅圖)# 男女分別數量合計佔比
sns.countplot(x='GENDER',data=df)# 男女會員分別所有數量。方式一
查看單個分類型變量頻數情況(直方圖)
# male_count = df.groupby('GENDER')['GENDER'].count()[0]
# female_count = df.groupby('GENDER')['GENDER'].count()[1]
# 方式二
male_count = pd.value_counts(df['GENDER'])['男']
female_count = pd.value_counts(df['GENDER'])['女']
plt.pie([male_count, female_count],
labels=['男', '女'],
colors=['lightskyblue', 'lightcoral'],
autopct='%1.1f%%',
)
plt.show# 方式一: 直接使用sns.countplot 查看單個變量的分類情況
# sns.countplot(x='FFP_TIER',data=df)
# 方式二:
FFP_TIER_Level4 = pd.value_counts(df['FFP_TIER'])[4]
FFP_TIER_Level5 = pd.value_counts(df['FFP_TIER'])[5]
FFP_TIER_Level6 = pd.value_counts(df['FFP_TIER'])[6]
plt.bar(x=range(3),
height=[FFP_TIER_Level4, FFP_TIER_Level5, FFP_TIER_Level6],
width=0.4,
alpha=0.8,
color='skyblue')
plt.xticks([index for index in range(3)], [4, 5, 6])
plt.show()
查看單個數值型變量分布情況(箱型圖)# 會員年齡分布箱型圖
相關性分析
plt.figaspect
plt.boxplot(df['AGE'].dropna(),
patch_artist=True,
vert=False,
boxprops = {'facecolor': 'lightblue'},
labels=['會員年齡'])
plt.grid(linestyle=":", color="r")
plt.title('會員年齡分布箱型圖')
plt.show()# 'FFP_TIER','FLIGHT_COUNT','LAST_TO_END','SEG_KM_SUM','EXCHANGE_COUNT','Points_Sum','FFP_DATE'的相關係數分析
df_corr = df.loc[:, ['FFP_TIER','FLIGHT_COUNT','LAST_TO_END','SEG_KM_SUM','EXCHANGE_COUNT','Points_Sum','FFP_DATE']]
age1 = df['AGE'].fillna(0)
df_corr['AGE'] = age1.astype('int64')
# df的相關係數
df_corr = df_corr.corr()
df_corr
# 設置圖形大小
plt.subplots(figsize=(10,10))
# 特徵數據熱力圖
ax = sns.heatmap(df_corr, annot=True, cmap='Blues')
ax.set_ylim([8, 0])
ax
數據預處理數據清洗# 去除票價為空的行
airline_notnull = df.loc[df['SUM_YR_1'].notnull() & df['SUM_YR_2'].notnull(), :]
# 保留票價非零數據,或者平均折扣率不為零且總飛行數大於0的記錄;AGE去除大於100的記錄
index1 = airline_notnull['SUM_YR_1'] != 0
index2 = airline_notnull['SUM_YR_2'] != 0
index3 = (airline_notnull['SEG_KM_SUM'] > 0) & (airline_notnull['avg_discount'] != 0)
index4 = airline_notnull['AGE'] > 100
airline = airline_notnull[(index1 | index2) & index3 & ~index4]
airline.head()部分數據欄位
屬性歸約airline = airline[['FFP_DATE', 'LOAD_TIME', 'LAST_TO_END','FLIGHT_COUNT','SEG_KM_SUM','avg_discount' ]]
airline['FFP_DATE'], airline['LOAD_TIME'] = pd.to_datetime(airline['FFP_DATE']), pd.to_datetime(airline['LOAD_TIME'])
airline['L'] = (airline['LOAD_TIME'] - airline['FFP_DATE']).dt.days
airline = airline.drop(['FFP_DATE', 'LOAD_TIME'], axis=1)
airline.columns = ['R', 'F', 'M', 'C', 'L']
airline.head()
數據標準化from sklearn.preprocessing import StandardScaler
data = StandardScaler().fit_transform(airline)
data[:5, :]array([[-0.94493902, 14.03402401, 26.76115699, 1.29554188, 1.43579256],
[-0.91188564, 9.07321595, 13.12686436, 2.86817777, 1.30723219],
[-0.88985006, 8.71887252, 12.65348144, 2.88095186, 1.32846234],
[-0.41608504, 0.78157962, 12.54062193, 1.99471546, 0.65853304],
[-0.92290343, 9.92364019, 13.89873597, 1.34433641, 0.3860794 ]])
聚類分析from sklearn.cluster import KMeans
# 構建模型,隨機種子設為123
kmeans_model = KMeans(n_clusters=5, n_jobs=4, random_state=123)
# 模型訓練
fit_kmeans = kmeans_model.fit(data)
# 查看聚類結果
kmeans_cc = kmeans_model.cluster_centers_
kmeans_cc
# 樣本的類別標籤
kmeans_lable = kmeans_model.labels_
# 統計不同類別樣本數目
pd.Series(kmeans_model.labels_).value_counts()
cluster_center = pd.DataFrame(kmeans_model.cluster_centers_, columns=['ZR','ZF','ZM','ZC','ZL'])
# cluster_center.index = pd.DataFrame(kmeans_model.labels_).drop_duplicates().iloc[:,0]
cluster_center
聚類分析可視化(雷達圖)#標籤
labels = cluster_center.columns
#數據個數
k = 5
plot_data = kmeans_model.cluster_centers_
#指定顏色
color = ['b', 'g', 'r', 'c', 'y']
angles = np.linspace(0, 2*np.pi, k, endpoint=False)
# 閉合
plot_data = np.concatenate((plot_data, plot_data[:,[0]]), axis=1)
# 閉合
angles = np.concatenate((angles, [angles[0]]))
fig = plt.figure(figsize=(8,6))
#polar參數
ax = fig.add_subplot(111, polar=True)
for i in range(len(plot_data)):
ax.plot(angles, plot_data[i], 'o-', color = color[i], label = u'客戶群'+str(i), linewidth=2)# 畫線
ax.set_rgrids(np.arange(0.01, 3.5, 0.5), np.arange(-1, 2.5, 0.5), fontproperties="SimHei")
ax.set_thetagrids(angles * 180/np.pi, labels, fontproperties="SimHei")
plt.legend(loc = 4)
plt.show()