一、背景
1.项目描述
- 你拥有一个超市(Supermarket Mall)。通过会员卡,你用有一些关于你的客户的基本数据,如客户ID,年龄,性别,年收入和消费分数。
- 消费分数是根据客户行为和购买数据等定义的参数分配给客户的。
- 问题陈述:你拥有这个商场。想要了解怎么样的顾客可以很容易地聚集在一起(目标顾客),以便可以给营销团队以灵感并相应地计划策略。
2.数据描述
二、相关模块
import numpy as np import pandas as pd
from pandas import plotting import matplotlib.pyplot as plt import seaborn as sns import plotly.graph_objs as go import plotly.offline as py
from sklearn.cluster import KMeans
import warnings warnings.filterwarnings('ignore')
三、数据可视化
1.数据读取
io = '.../Mall_Customers.csv' df = pd.DataFrame(pd.read_csv(io)) # 修改列名 df.rename(columns={'Annual Income (k$)': 'Annual Income', 'Spending Score (1-100)': 'Spending Score'}, inplace=True) print(df.head()) print(df.describe()) print(df.shape) print(df.count()) print(df.dtypes)
输出如下。
CustomerID Gender Age Annual Income Spending Score
0 1 Male 19 15 39
1 2 Male 21 15 81
2 3 Female 20 16 6
3 4 Female 23 16 77
4 5 Female 31 17 40
-----------------------------------------------------------------
CustomerID Age Annual Income Spending Score
count 200.000000 200.000000 200.000000 200.000000
mean 100.500000 38.850000 60.560000 50.200000
std 57.879185 13.969007 26.264721 25.823522
min 1.000000 18.000000 15.000000 1.000000
25% 50.750000 28.750000 41.500000 34.750000
50% 100.500000 36.000000 61.500000 50.000000
75% 150.250000 49.000000 78.000000 73.000000
max 200.000000 70.000000 137.000000 99.000000
-----------------------------------------------------------------
(200, 5)
CustomerID 200
Gender 200
Age 200
Annual Income 200
Spending Score 200
dtype: int64
-----------------------------------------------------------------
CustomerID int64
Gender object
Age int64
Annual Income int64
Spending Score int64
dtype: object
2.数据可视化
2.1 平行坐标图
- 平行坐标图(Parallel coordinates plot)用于多元数据的可视化,将高维数据的各个属性(变量)用一系列相互平行的坐标轴表示, 纵向是属性值,横向是属性类别。
- 若在某个属性上相同颜色折线较为集中,不同颜色有一定的间距,则说明该属性对于预标签类别判定有较大的帮助。
- 若某个属性上线条混乱,颜色混杂,则可能该属性对于标签类别判定没有价值。
plotting.parallel_coordinates(df.drop('CustomerID', axis=1), 'Gender') plt.title('平行坐标图', fontsize=12) plt.grid(linestyle='-.') plt.show()
2.2 年龄/年收入/消费分数的分布
这里用了直方图和核密度图。(注:核密度图看的是(x<X)的面积,而不是高度)
sns.set(palette="muted", color_codes=True) # seaborn样式 # 配置 plt.rcParams['axes.unicode_minus'] = False # 解决无法显示符号的问题 sns.set(font='SimHei', font_scale=0.8) # 解决Seaborn中文显示问题 # 绘图 plt.figure(1, figsize=(13, 6)) n = 0 for x in ['Age', 'Annual Income', 'Spending Score']: n += 1 plt.subplot(1, 3, n) plt.subplots_adjust(hspace=0.5, wspace=0.5) sns.distplot(df[x], bins=16, kde=True) # kde 密度曲线 plt.title('{}分布情况'.format(x)) plt.tight_layout() plt.show()
如下图。从左到右分别是年龄、年收入和消费能力的分布情况。发现:
- 年龄方面:[30,36]范围的客户是最多的另外,在[20,21]也不少,但是60岁以上的老年人是最不常来消费的。
- 年收入方面:大部分的客户集中在[53,83]范围里,在15以下和105以上的很少。
- 消费分数方面:消费分数在[40,55]的占了大多数,在[70,80]范围的次之。
2.3年龄/年收入/消费分数的柱状图
这里使用的是柱状图,和直方图不同的是:
plt.figure(1, figsize=(13, 6)) k = 0 for x in ['Age', 'Annual Income', 'Spending Score']: k += 1 plt.subplot(3, 1, k) plt.subplots_adjust(hspace=0.5, wspace=0.5) sns.countplot(df[x], palette='rainbow', alpha=0.8) plt.title('{}分布情况'.format(x)) plt.tight_layout() plt.show()
如下图。从上到下分别是年龄、年收入和消费能力的柱状图。发现:
- 年龄方面:[27,40]范围的客户居多。其中,32岁的客户是商城的常客,55,、56、64、69岁的用户却很少。总的来说,年龄较大的人群较少,年龄较少的人群较多。
- 年收入方面:年收入在54和78的频数是最多的。其他在各个收入的客户频数看起来相差不太大。
- 消费分数方面:消费分数在42的客户数是最多的,56次之。有的客户的分数甚至达到了99,而分数为1的客户也存在,没有分数为0的客户。
2.4不同性别用户占比
df_gender_c = df['Gender'].value_counts() p_lables = ['Female', 'Male'] p_color = ['lightcoral', 'lightskyblue'] p_explode = [0, 0.05] # 绘图 plt.pie(df_gender_c, labels=p_lables, colors=p_color, explode=p_explode, shadow=True, autopct='%.2f%%') plt.axis('off') plt.legend() plt.show()
如下饼图。女性以56%的份额居于领先地位,而男性则占整体的44%。特别是当男性人口相对高于女性时,这是一个比较大的差距。
2.5 两两特征之间的关系
# df_a_a_s = df.drop(['CustomerID'], axis=1) sns.pairplot(df, vars=['Age', 'Annual Income', 'Spending Score'], hue='Gender', aspect=1.5, kind='reg') plt.show()
pairplot主要展现的是属性(变量)两两之间的关系(线性或非线性,有无较为明显的相关关系)。注意,我对男、女性的数据点进行了区分(但是感觉数据在性别上的差异不大呀?)。如下组图所示:
- 对角线上的图是各个属性的核密度分布图。
- 非对角线的图是两个不同属性之间的相关图。看得出年收入和消费能力之间有较为明显的相关关系。
- 将
kind
参数设置为reg
会为非对角线上的散点图拟合出一条回归直线,更直观地显示变量之间的关系。
2.6 两两特征之间的分布
# 根据分类变量分组绘制一个纵向的增强箱型图 plt.rcParams['axes.unicode_minus'] = False # 解决无法显示符号的问题 sns.set(font='SimHei', font_scale=0.8) # 解决Seaborn中文显示问题 sns.boxenplot(df['Gender'], df['Spending Score'], palette='Blues') # x:设置分组统计字段,y:数据分布统计字段 sns.swarmplot(x=df['Gender'], y=df['Spending Score'], data=df, palette='dark', alpha=0.5, size=6) plt.title('男女性的消费能力比较', fontsize=12) plt.show()
- 如下图使用了增强箱图,可以通过绘制更多的分位数来提供数据分布的信息,适用于大数据。
- 男性的消费得分集中在[25,70],而女性的消费得分集中在[35,75],一定程度上说明了女性在购物方面表现得比男性好。
# 根据分类变量分组绘制一个纵向的增强箱型图 plt.rcParams['axes.unicode_minus'] = False # 解决无法显示符号的问题 sns.set(font='SimHei', font_scale=0.8) # 解决Seaborn中文显示问题 sns.boxenplot(df['Gender'], df['Spending Score'], palette='Blues') # x:设置分组统计字段,y:数据分布统计字段 sns.swarmplot(x=df['Gender'], y=df['Spending Score'], data=df, palette='dark', alpha=0.5, size=6) plt.title('男女性的消费能力比较', fontsize=12) plt.show()
其实,下面这一部分也包含了上面的信息。
- 年龄方面:男性分布较为均匀,20多岁的比较多;女性的年龄大部分集中在20+~30+这个范围,整体上较为年轻?
- 收入方面:男性略胜一筹
四、K-means聚类分析
0.手肘法简介
核心指标
误差平方和(sum of the squared errors,SSE)是所有样本的聚类误差反映了聚类效果的好坏,公式如下:
核心思想
- 随着聚类数k 的增大,样本划分会更加精细,每个簇的聚合程度会逐渐提高,那么SSE会逐渐变小。
- 当k 小于真实聚类数时,由于k 的增大会大幅增加每个簇的聚合程度,故SSE的下降幅度会很大。
- 当k到达真实聚类数时,再增加k所得到的聚合程度回报会迅速变小,所以SSE的下降幅度会骤减。然后随着k值的继续增大而趋于平缓,也就是说SSE和k的关系图是一个手肘的形状,而这个肘部对应的k值就是数据的真实聚类数。
1.基于年龄和消费分数的聚类
所需要的数据有‘Age'和‘Spending Score'。
df_a_sc = df[['Age', 'Spending Score']].values # 存放每次聚类结果的误差平方和 inertia1 = []
使用手肘法确定最合适的
for n in range(1, 11): # 构造聚类器 km1 = (KMeans(n_clusters=n, # 要分成的簇数,int类型,默认值为8 init='k-means++', # 初始化质心,k-means++是一种生成初始质心的算法 n_init=10, # 设置选择质心种子次数,默认为10次。返回质心最好的一次结果(好是指计算时长短) max_iter=300, # 每次迭代的最大次数 tol=0.0001, # 容忍的最小误差,当误差小于tol就会退出迭代 random_state=111, # 随机生成器的种子 ,和初始化中心有关 algorithm='elkan')) # 'full'是传统的K-Means算法,'elkan'是采用elkan K-Means算法 # 用训练数据拟合聚类器模型 km1.fit(df_a_sc) # 获取聚类标签 inertia1.append(km1.inertia_)
绘图确定
plt.figure(1, figsize=(15, 6)) plt.plot(np.arange(1, 11), inertia1, 'o') plt.plot(np.arange(1, 11), inertia1, '-', alpha=0.7) plt.title('手肘法图', fontsize=12) plt.xlabel('聚类数'), plt.ylabel('SSE') plt.grid(linestyle='-.') plt.show()
通过如下图,确定
确定
km1_result = (KMeans(n_clusters=4, init='k-means++', n_init=10, max_iter=300, tol=0.0001, random_state=111, algorithm='elkan')) # 先fit()再predict(),一次性得到聚类预测之后的标签 y1_means = km1_result.fit_predict(df_a_sc) # 绘制结果图 plt.scatter(df_a_sc[y1_means == 0][:, 0], df_a_sc[y1_means == 0][:, 1], s=70, c='blue', label='1', alpha=0.6) plt.scatter(df_a_sc[y1_means == 1][:, 0], df_a_sc[y1_means == 1][:, 1], s=70, c='orange', label='2', alpha=0.6) plt.scatter(df_a_sc[y1_means == 2][:, 0], df_a_sc[y1_means == 2][:, 1], s=70, c='pink', label='3', alpha=0.6) plt.scatter(df_a_sc[y1_means == 3][:, 0], df_a_sc[y1_means == 3][:, 1], s=70, c='purple', label='4', alpha=0.6) plt.scatter(km1_result.cluster_centers_[:, 0], km1_result.cluster_centers_[:, 1], s=260, c='gold', label='质心') plt.title('聚类图(K=4)', fontsize=12) plt.xlabel('年收入(k$)') plt.ylabel('消费分数(1-100)') plt.legend() plt.grid(linestyle='-.') plt.show()
效果如下,基于年龄和消费能力这两个参数,可以将用户划分成4类。
2.基于年收入和消费分数的聚类
所需要的数据
df_ai_sc = df[['Annual Income', 'Spending Score']].values # 存放每次聚类结果的误差平方和 inertia2 = []
同理,使用手肘法确定合适的
for n in range(1, 11): # 构造聚类器 km2 = (KMeans(n_clusters=n, init='k-means++', n_init=10, max_iter=300, tol=0.0001, random_state=111, algorithm='elkan')) # 用训练数据拟合聚类器模型 km2.fit(df_ai_sc) # 获取聚类标签 inertia2.append(km2.inertia_) # 绘制手肘图确定K值 plt.figure(1, figsize=(15, 6)) plt.plot(np.arange(1, 11), inertia1, 'o') plt.plot(np.arange(1, 11), inertia1, '-', alpha=0.7) plt.title('手肘法图', fontsize=12) plt.xlabel('聚类数'), plt.ylabel('SSE') plt.grid(linestyle='-.') plt.show()
通过如下图,确定
确定
km2_result = (KMeans(n_clusters=5, init='k-means++', n_init=10, max_iter=300, tol=0.0001, random_state=111, algorithm='elkan')) # 先fit()再predict(),一次性得到聚类预测之后的标签 y2_means = km2_result.fit_predict(df_ai_sc) # 绘制结果图 plt.scatter(df_ai_sc[y2_means == 0][:, 0], df_ai_sc[y2_means == 0][:, 1], s=70, c='blue', label='1', alpha=0.6) plt.scatter(df_ai_sc[y2_means == 1][:, 0], df_ai_sc[y2_means == 1][:, 1], s=70, c='orange', label='2', alpha=0.6) plt.scatter(df_ai_sc[y2_means == 2][:, 0], df_ai_sc[y2_means == 2][:, 1], s=70, c='pink', label='3', alpha=0.6) plt.scatter(df_ai_sc[y2_means == 3][:, 0], df_ai_sc[y2_means == 3][:, 1], s=70, c='purple', label='4', alpha=0.6) plt.scatter(df_ai_sc[y2_means == 4][:, 0], df_ai_sc[y2_means == 4][:, 1], s=70, c='green', label='5', alpha=0.6) plt.scatter(km2_result.cluster_centers_[:, 0], km2_result.cluster_centers_[:, 1], s=260, c='gold', label='质心') plt.title('聚类图(K=5)', fontsize=12) plt.xlabel('年收入(k$)') plt.ylabel('消费分数(1-100)') plt.legend() plt.grid(linestyle='-.') plt.show()
效果如下,基于年收入和消费能力这两个参数,可以将用户划分成如下5类:
- 群体1
"application/x-tex">\Rightarrow"application/x-tex">\Rightarrow"application/x-tex">\Rightarrow"application/x-tex">\Rightarrow"application/x-tex">\Rightarrow"text-align: center"> 3.基于年龄、收入和消费分数的聚类所需要的数据
df_a_ai_sc = df[['Age', 'Annual Income', 'Spending Score']].values
聚类,
kkk=5。 km3 = KMeans(n_clusters=5, init='k-means++', max_iter=300, n_init=10, random_state=0) km3.fit(df_a_ai_sc)
绘图。
df['labels'] = km3.labels_ # 绘制3D图 trace1 = go.Scatter3d( x=df['Age'], y=df['Spending Score'], z=df['Annual Income'], mode='markers', marker=dict( color=df['labels'], size=10, line=dict( color=df['labels'], width=12 ), opacity=0.8 ) ) df_3dfid = [trace1] layout = go.Layout( margin=dict( l=0, r=0, b=0, t=0 ), scene=dict( xaxis=dict(title='年龄'), yaxis=dict(title='消费分数(1-100)'), zaxis=dict(title='年收入(k$)') ) ) fig = go.Figure(data=df_3dfid, layout=layout) py.offline.plot(fig)
效果如下。
五、小结
- 主要是为了记录下K-means学习过程,而且之前也参与了一个项目用到了K-means算法。
- 如何进行特征旋是一个需要考虑的问题,我这里尝试了三种不同的方案。然后,确定k 值是另一个重要的问题。我这个用了“手肘法”,但是可以配合“轮廓系数”综合判断。
- 还有许多地方不够详细。另外,如果有考虑不严谨的地方,欢迎批评指正!
《魔兽世界》大逃杀!60人新游玩模式《强袭风暴》3月21日上线
暴雪近日发布了《魔兽世界》10.2.6 更新内容,新游玩模式《强袭风暴》即将于3月21 日在亚服上线,届时玩家将前往阿拉希高地展开一场 60 人大逃杀对战。
艾泽拉斯的冒险者已经征服了艾泽拉斯的大地及遥远的彼岸。他们在对抗世界上最致命的敌人时展现出过人的手腕,并且成功阻止终结宇宙等级的威胁。当他们在为即将于《魔兽世界》资料片《地心之战》中来袭的萨拉塔斯势力做战斗准备时,他们还需要在熟悉的阿拉希高地面对一个全新的敌人──那就是彼此。在《巨龙崛起》10.2.6 更新的《强袭风暴》中,玩家将会进入一个全新的海盗主题大逃杀式限时活动,其中包含极高的风险和史诗级的奖励。
《强袭风暴》不是普通的战场,作为一个独立于主游戏之外的活动,玩家可以用大逃杀的风格来体验《魔兽世界》,不分职业、不分装备(除了你在赛局中捡到的),光是技巧和战略的强弱之分就能决定出谁才是能坚持到最后的赢家。本次活动将会开放单人和双人模式,玩家在加入海盗主题的预赛大厅区域前,可以从强袭风暴角色画面新增好友。游玩游戏将可以累计名望轨迹,《巨龙崛起》和《魔兽世界:巫妖王之怒 经典版》的玩家都可以获得奖励。
更新日志
- 【雨果唱片】中国管弦乐《鹿回头》WAV
- APM亚流新世代《一起冒险》[FLAC/分轨][106.77MB]
- 崔健《飞狗》律冻文化[WAV+CUE][1.1G]
- 罗志祥《舞状元 (Explicit)》[320K/MP3][66.77MB]
- 尤雅.1997-幽雅精粹2CD【南方】【WAV+CUE】
- 张惠妹.2007-STAR(引进版)【EMI百代】【WAV+CUE】
- 群星.2008-LOVE情歌集VOL.8【正东】【WAV+CUE】
- 罗志祥《舞状元 (Explicit)》[FLAC/分轨][360.76MB]
- Tank《我不伟大,至少我能改变我。》[320K/MP3][160.41MB]
- Tank《我不伟大,至少我能改变我。》[FLAC/分轨][236.89MB]
- CD圣经推荐-夏韶声《谙2》SACD-ISO
- 钟镇涛-《百分百钟镇涛》首批限量版SACD-ISO
- 群星《继续微笑致敬许冠杰》[低速原抓WAV+CUE]
- 潘秀琼.2003-国语难忘金曲珍藏集【皇星全音】【WAV+CUE】
- 林东松.1997-2039玫瑰事件【宝丽金】【WAV+CUE】