本文轉自https://www.freeaihub.com/article/ad-cluster-with-kmean-in-python.html,該頁可在線運行
本案例中的業務場景為,通過各類廣告渠道90天內額日均UV,平均注冊率、平均搜索率、訪問深度、平均停留時長、訂單轉化率、投放時間、素材類型、廣告類型、合作方式、廣告尺寸和廣告賣點等特征,將渠道分類,找出每類渠道的重點特征,為接下來的業務討論和數據分析提供支持。
假如你們公司投放廣告的渠道很多,每個渠道的客戶性質也可能不同,比如在優酷視頻投廣告和今日頭條投放廣告,效果可能會有差異。現在需要對廣告效果分析實現有針對性的廣告效果測量和優化工作。
K-Means聚類算法介紹
聚類算法:屬於無監督機器學習算法,通過計算樣本項之間的相似度(也稱為樣本間的距離),按照數據內部存在的數據特征將數據集划分為多個不同的類別,使類別內的數據比較相似,類別之間的數據相似度比較小。
閔可夫斯基距離(Minkowski):
$$dist(X,Y)=\sqrt{\sum_{i=1}^{n}|X_i - Y_i|^p}$$
算法思想(步驟):
a、選擇初始化的k個類別中心a1,a2,...ak;
b、計算每個樣本Xi到類別中心aj的距離,設定最近的類別j
c、將每個類別的中心點aj,替換為隸屬該類別的所有樣本的均值,作為新的質心。
d、重復上面兩步操作,直到達到某個中止條件
中止條件為:組內最小平方誤差MSE最小,或者達到迭代次數,或者簇中心點不再變化。
分析流程
- 1、應用技術介紹
- 2、數據介紹
- 3、導入庫,加載數據
- 4、數據審查
- 5、數據處理
- 6、建立模型
- 7、聚類結果特征分析與展示
- 8、數據結論
數據預處理
a、數據標准化
數據無量綱化,去除數據的單位限制,將其轉化為無量綱的純數值,便於不同單位或者量級的指標能夠進行和加權. 比如身高與體重,房子數量與收入等。
b、獨熱編碼OneHotEncoder
對於字符串類別類型的變量,不能直接帶入模型,需要轉化為數值型。
數據介紹
01 數據維度概況
除了渠道唯一標識,共12個維度,889行,有缺失值,有異常值。
02 數據13個維度介紹
1、渠道代號:渠道唯一標識
2、日均UV:每天的獨立訪問量
3、平均注冊率=日均注冊用戶數/平均每日訪問量
4、平均搜索量:每個訪問的搜索量
5、訪問深度:總頁面瀏覽量/平均每天的訪問量
6、平均停留時長=總停留時長/平均每天的訪問量
7、訂單轉化率=總訂單數量/平均每天的訪客量
8、投放時間:每個廣告在外投放的天數
9、素材類型:'jpg' 'swf' 'gif' 'sp'
10、廣告類型:banner、tips、不確定、橫幅、暫停
11、合作方式:'roi' 'cpc' 'cpm' 'cpd'
12、廣告尺寸:'14040' '308388' '450300' '60090' '480360' '960126' '900120'
'390270'
13、廣告賣點:打折、滿減、滿贈、秒殺、直降、滿返
導入庫,加載數據
import pandas as pd
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
from sklearn.preprocessing import MinMaxScaler,OneHotEncoder
from sklearn.metrics import silhouette_score # 導入輪廓系數指標
from sklearn.cluster import KMeans # KMeans模塊
%matplotlib inline
## 設置屬性防止中文亂碼
mpl.rcParams['font.sans-serif'] = [u'SimHei']
mpl.rcParams['axes.unicode_minus'] = False
以上是加載庫的國際慣例,OneHotEncoder是獨熱編碼,如果一個類別特征有n個類別,將該變量按照類別分裂成N維新變量,包含則標記為1,否則為0,用N維特征表示原來的特征。
讀取數據:
raw_data = pd.read_table('/share/datasets/ad_performance.txt', delimiter='\t')
raw_data.head()
渠道代號是唯一標識,日均UV到投放總時間是數值型(float和int)變量,后面是字符型變量。
數據審查
# 查看基本狀態
print('{:*^60}'.format('數據前兩行:'))
print(raw_data.head(2)) # 打印輸出前2條數據
print('{:*^60}'.format('數據類型:'))
print(pd.DataFrame(raw_data.dtypes).T) # 打印數據類型分布
print('{:*^60}'.format('數據統計描述:'))
print(raw_data.describe().round(2).T) # 打印原始數據基本描述性信息
上面代碼,分別展示前兩條數據、所有特征的數據類型、以及數值型特征的五值分布
查看缺失值情況:
# 缺失值審查
na_cols = raw_data.isnull().any(axis=0) # 查看每一列是否具有缺失值
print('{:*^60}'.format('含有缺失值的列:'))
print(na_cols[na_cols==True]) # 查看具有缺失值的列
print('總共有多少數據缺失: {0}'.format(raw_data.isnull().any(axis=1).sum())) # 查看具有缺失值的行總記錄數
變量之間的相關性分析:
# 相關性分析
print('{:*^60}'.format('Correlation analysis:'))
print(raw_data.corr().round(2).T) # 打印原始數據相關性信息
皮爾遜相關用熱力圖形象展示
# 相關性可視化展示
import seaborn as sns
corr=raw_data.corr().round(2)
sns.heatmap(corr,cmap="Reds",annot=True)
可以看到,“訪問深度”和“平均停留時間”相關性比較高,相關性高說明兩個變量在建立模型的時候,作用是一樣或者效果是一樣的,可以考慮組合或者刪除其一。
數據處理
數據了解的差不多了,我們開始時處理數據,把常規數據通過清洗、轉換、規約、聚合、抽樣等方式變成機器學習可以識別或者提升准確度的數據。
# 1 刪除平均平均停留時間列
raw_data2 = raw_data.drop(['平均停留時間'], axis=1)
類別變量的獨熱編碼:
# 類別變量取值
cols=["素材類型","廣告類型","合作方式","廣告尺寸","廣告賣點"]
for x in cols:
data=raw_data2[x].unique()
print("變量【{0}】的取值有:\n{1}".format(x,data))
print("-·"*20)
# 字符串分類獨熱編碼處理
cols = ['素材類型','廣告類型','合作方式','廣告尺寸','廣告賣點']
model_ohe = OneHotEncoder(sparse=False) # 建立OneHotEncode對象
ohe_matrix = model_ohe.fit_transform(raw_data2[cols]) # 直接轉換
print(ohe_matrix[:2])
# 用pandas的方法
ohe_matrix1=pd.get_dummies(raw_data2[cols])
ohe_matrix1.head(5)
數據標准化:
數據標准化的意義開篇技術介紹有聊過,統一量綱。
# 數據標准化
sacle_matrix = raw_data2.iloc[:, 1:7] # 獲得要轉換的矩陣
model_scaler = MinMaxScaler() # 建立MinMaxScaler模型對象
data_scaled = model_scaler.fit_transform(sacle_matrix) # MinMaxScaler標准化處理
print(data_scaled.round(2))
數據處理完,我們將獨熱編碼的數據和標准化轉化后的數據合並:
# 合並所有維度
X = np.hstack((data_scaled, ohe_matrix))
數據處理完,就可以帶入模型進行訓練了。
建立模型
# 通過平均輪廓系數檢驗得到最佳KMeans聚類模型
score_list = list() # 用來存儲每個K下模型的平局輪廓系數
silhouette_int = -1 # 初始化的平均輪廓系數閥值
for n_clusters in range(2, 8): # 遍歷從2到5幾個有限組
model_kmeans = KMeans(n_clusters=n_clusters) # 建立聚類模型對象
labels_tmp = model_kmeans.fit_predict(X) # 訓練聚類模型
silhouette_tmp = silhouette_score(X, labels_tmp) # 得到每個K下的平均輪廓系數
if silhouette_tmp > silhouette_int: # 如果平均輪廓系數更高
best_k = n_clusters # 保存K將最好的K存儲下來
silhouette_int = silhouette_tmp # 保存平均輪廓得分
best_kmeans = model_kmeans # 保存模型實例對象
cluster_labels_k = labels_tmp # 保存聚類標簽
score_list.append([n_clusters, silhouette_tmp]) # 將每次K及其得分追加到列表
print('{:*^60}'.format('K值對應的輪廓系數:'))
print(np.array(score_list)) # 打印輸出所有K下的詳細得分
print('最優的K值是:{0} \n對應的輪廓系數是:{1}'.format(best_k, silhouette_int))
輪廓系數確定最佳的K值,確定數據分為幾類較為合理,開篇技術介紹有輪廓系數的介紹了,聚類效果的評價,之前文章有介紹過聚類數和簇內均方誤差繪圖出現拐點的時候可以確定為幾類。
總體思想(評價指標)還是怎么聚才能使得簇內距離足夠小,簇與簇之間平均距離足夠大來評判。
當然,有興趣的同學,可以查資料總結一下聚類的評價指標,怎么確定K值,K-Means有什么優缺點,怎么提升?··· ···
聚類結果特征分析與展示
通過上面模型,我們其實給每個觀測(樣本)打了個標簽clusters,即他屬於4類中的哪一類:
# 將原始數據與聚類標簽整合
cluster_labels = pd.DataFrame(cluster_labels_k, columns=['clusters']) # 獲得訓練集下的標簽信息
merge_data = pd.concat((raw_data2, cluster_labels), axis=1) # 將原始處理過的數據跟聚類標簽整合
merge_data.head()
然后看看,每個類別下的樣本數量和占比情況:
# 計算每個聚類類別下的樣本量和樣本占比
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)
接下來,我們看看每個類別內部最顯著的特征:
# 計算各個聚類類別內部最顯著特征值
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 # 將列表轉化為矩陣
print('{:*^60}'.format('每個類別主要的特征:'))
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 = model_scaler.fit_transform(num_sets) # 獲得標准化后的數據
# 畫圖
fig = plt.figure(figsize=(6,6)) # 建立畫布
ax = fig.add_subplot(111, polar=True) # 增加子網格,注意polar參數
labels = np.array(merge_data1.index) # 設置要展示的數據標簽
cor_list = ['g', 'r', 'y', 'b'] # 定義不同類別的顏色
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=2.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)) # 設置圖例位置
數據結論
從案例結果來看,所有的渠道被分為4各類別,每個類別的樣本量分別為:154、313、349 、73,對應占比分別為:17%、35%、39%、8%。
通過雷達圖可以清楚的知道:
類別1(索引為2類的渠道)
這類廣告媒體除了訪問深度和投放時間較高,其他屬性較低,因此這類廣告媒體效果質量較差,並且占到39%,因此這類是主題渠道之一。業務部門要考慮他的實際投放價值。
類別2(索引為1類的渠道)
這類廣告媒體除了訪問深度略差,在平均搜索量、日均UV、訂單轉化率等廣告效果指標上表現良好,是一類綜合效果較好的渠道。
但是日均UV是短板,較低。無法給企業帶來大量的流量以及新用戶,這類廣告的特質適合用戶轉化,尤其是有關訂單的轉化提升。
類別3(索引為0類的渠道)
這類廣告媒體的顯著特征是日均UV和注冊率較高,其“引流”和“拉新”效果好,可以在廣告媒體中定位為引流角色。
符合“廣而告之”的訴求,適合“拉新”使用。
類別4(索引為3類的渠道)
這類渠道各方面特征都不明顯,各個流量質量和流量數量的指標均處於“中等”層次。不突出但是均衡,考慮在各場景下可以考慮在這個渠道投放廣告。