分享kaggle上面经典数据集customer personality analysis实战情况
数据集基本情况:
目录
一、确定研究问题
研究问题:什么样的客户喜欢什么样的商品。旨在更精准地把握客户需求,优化营销策略
二、清洗数据
1. 准备工作
导入基本库:
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
为了程序更高效地运行,需要做两个准备工作:屏蔽非错误警告和更改工作路径:
import warnings
import sys
if not sys.warnoptions:
warnings.simplefilter("ignore")
###
import os
os.chdir(r'现在的工作路径')
os.getcwd()
2. 处理缺失值
通过df.info()查看基本信息,isnull().sum()查看缺失值
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 2240 entries, 0 to 2239
Data columns (total 29 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 ID 2240 non-null int64
1 Year_Birth 2240 non-null int64
2 Education 2240 non-null object
3 Marital_Status 2240 non-null object
4 Income 2216 non-null float64
5 Kidhome 2240 non-null int64
6 Teenhome 2240 non-null int64
仅有24个缺失值,直接dropna()
3. 调整数据形式
(1)时点变时期
出生年→年龄
注册日期→注册天数
技巧:生成副本列,避免对数据集造成不可撤销的更改
#年龄
df['age']= 2021-df['Year_Birth']
#注册天数
df['Dt_Customer_']=pd.to_datetime(df['Dt_Customer'],dayfirst=True)
#需要提示‘天’在第一位
df['Dt_Customer_']=max(df.Dt_Customer_)-df['Dt_Customer_']
df['days']=df['Dt_Customer_'].dt.days
del df['Dt_Customer_']
(2)非数值数据编码
对教育水平和婚姻状况先了解填入内容:
print(df.Education.value_counts())
print(df.Marital_Status.value_counts())
结果:
Education
Graduation 1116
PhD 481
Master 365
2n Cycle 200
Basic 54
Name: count, dtype: int64
Marital_Status
Married 857
Together 573
Single 471
Divorced 232
Widow 76
Alone 3
Absurd 2
YOLO 2
Name: count, dtype: int64```
先合并同类项:本科以下,本科,本科以上;独居,同居;在进行编码
注意:为了体现教育水平的高低,应当采取顺序编码(OrdinalEncoder)是标签一一对应。
#教育水平
df['edu']=df['Education'].apply(lambda x: 'post' if x in ['PhD','Mastr']
else 'graduate'if x== 'Graduation'
else 'under')
from sklearn.preprocessing import OrdinalEncoder
ordinal=OrdinalEncoder(categories=[['under','graduate','post']])
df['edu']=ordinal.fit_transform(df[['edu']])
#婚姻状况
from sklearn.preprocessing import LabelEncoder
leb=LabelEncoder()
df['together']=df['Marital_Status'].apply(lambda x:'together' if x in ['Married','Together']
else 'alone')
df['together']=leb.fit_transform(df['together'])
(3)数值型合并
#孩子
df['children']=df.Kidhome+df.Teenhome #不可以sum
#总消费
df['amount_sum']=df.MntFishProducts+df.MntFruits+df.MntGoldProds+df.MntMeatProducts+df.MntSweetProducts+df.MntWines
最后去掉已经被处理过的数据:
to_drop=['Year_Birth','Education','Marital_Status','Kidhome','Teenhome','Dt_Customer']
df_drop=df.drop(df[to_drop],axis=1)
4.处理异常值
.describe()初步观察后
uni=['Income','Recency','age','days','amount_sum']
sns.pairplot(df_drop[uni],diag_kind='kde')
df_clean=df_drop[(df_drop['age']<90) & (df_drop['Income']<300000)]
sns.pairplot(df_clean[uni],diag_kind='kde')
此外,还应当通过相关性查看有无十分高度相关一般来说0.9以上。
三、PCA主成分分析
注意:只要是能体现客户特征的变量都应当加入,不能局限于attribute,排除售后和非性格因素即可
1.归一化
#归一化std
#对方法的讨论:MMX要求标准差小,且不好控制平滑项;Robust缩小力度较小;还是std最好
#PCA也需要中心化,一举两得
from sklearn.preprocessing import StandardScaler
stdsc = StandardScaler()
std_sp=['Income', 'Recency', 'MntWines', 'MntFruits', 'MntMeatProducts',
'MntFishProducts', 'MntSweetProducts', 'MntGoldProds',
'NumWebPurchases', 'NumCatalogPurchases','edu','together',
'NumStorePurchases', 'NumWebVisitsMonth', 'age', 'days',
'children', 'amount_sum']
#删除表示售后反应,以及promotion因素
std_fit=stdsc.fit(df_clean[std_sp])
std_num=stdsc.fit_transform(df_clean[std_sp])
std_yes=pd.DataFrame(std_num,columns=std_sp)
customer=std_yes
对要不要加入总消费的思考:虽然总消费与部分消费有线性性,但有其独特之处,体现了总量特征,故不删除
2.PCA
一般来说提取2-3个,我选择分成三类,3类误差更小,我们希望尽量保留特征,在不引起维度灾难的情况下。
from sklearn.decomposition import PCA
dim_=3
pca_model=PCA(n_components=dim_)
pca_fit= pca_model.fit_transform(customer)
pca_results= pd.DataFrame(pca_fit,columns=['atr1','atr2','atr3'])
技巧:单独设置维度变量,方便后续修改测试
四、分类(KMeans vs AGNES)
1. 函数准备
#KMeans肘部法
from sklearn.cluster import KMeans
#AGENS
from sklearn.cluster import AgglomerativeClustering
#轮廓法
from sklearn.metrics import silhouette_score
#DB指数
from sklearn.metrics import davies_bouldin_score
2.代码实现
kmeans
wcss_list=[]
cishu=range(2,11)
lunkuo=[]
dbi=[]
for i in cishu:
kms_model=KMeans(n_clusters=i,random_state=222)#默认贪心算法
kms_fits=kms_model.fit(pca_results)
wcss_list.append(kms_model.inertia_) #肘部
score=silhouette_score(pca_results,kms_fits.labels_) #轮廓
lunkuo.append(score)
db_score=davies_bouldin_score(pca_results,kms_fits.labels_)
dbi.append(db_score)
fig,(ax1,ax2,ax3)=plt.subplots(3,1,figsize=(10,12))
ax1.plot(cishu,wcss_list)
ax1.set(title='elbow')
ax2.plot(cishu,lunkuo)
ax2.set(title='lunkuo')
ax3.plot(cishu,dbi)
ax3.set(title='DBI')
plt.savefig('kmeans.png')
AGNES
ac_lun=[]
ac_dbi=[]
ac_cishu=range(2,10)
for i in ac_cishu:
ac_model=AgglomerativeClustering(n_clusters=i)
ac_fit=ac_model.fit(pca_results)
ac_score=silhouette_score(pca_results,ac_fit.labels_)
ac_lun.append(ac_score)
ac_dbs=davies_bouldin_score(pca_results,ac_fit.labels_)
ac_dbi.append(ac_dbs)
fig,(ax2,ax3)=plt.subplots(2,1,figsize=(10,8))
ax2.plot(ac_cishu,ac_lun)
ax2.set(title='lunkuo')
ax3.plot(ac_cishu,ac_dbi)
ax3.set(title='DBI')
plt.savefig('ac.png')
3.性能评估(选择)
两种方式大差不差,Kmeans稍忧。一个比较奇怪的点是K显示3方差和在缩小,性能却在下降。
考虑到实际情况, 我们更希望客户群能够细分,故而选择分为三类, 兼顾性能与实际,最终选择K均值法
4. 分类
cluster=3
km=KMeans(n_clusters=cluster)
pred=km.fit_predict(pca_results)
df_clean['cluster']=pred
五、画像与分析
这一部分的图表需要面向大众展示,故而使用Pyecharts,其优势在于:默认字体支持中文,数据项默认显示
from pyecharts.render import make_snapshot
from snapshot_pyppeteer import snapshot
from pyecharts import options as opts
from pyecharts.globals import ThemeType
from pyecharts.commons.utils import JsCode
1.客户结构
from pyecharts.charts import Bar
#客户分布
counts_cluster=df_clean.cluster.value_counts().sort_index()
#改变柱条颜色
color_function = """
function (params) {
if (params.dataIndex==0 ) {
return 'gold';
} else if (params.dataIndex == 1) {
return 'dodgerblue';
}
return 'plum';
}
"""
#https://matplotlib.org/stable/gallery/color/named_colors.html
bar_cluster = ( Bar(init_opts=opts.InitOpts(animation_opts=opts.AnimationOpts(animation=False),
bg_color='rgb(255,255,255)')) #关闭动画
.add_xaxis(counts_cluster.index.tolist())
.add_yaxis('',counts_cluster.values.tolist(),bar_width='40%',
itemstyle_opts=opts.ItemStyleOpts(color=JsCode(color_function)))
.set_global_opts(title_opts=opts.TitleOpts(title='客户分布',pos_left='center',
title_textstyle_opts=opts.TextStyleOpts(font_size=25)),#标题大小
xaxis_opts=opts.AxisOpts(splitline_opts=opts.SplitLineOpts(is_show=False)))# 关闭x轴网格线
.set_series_opts(label_opts=opts.LabelOpts(position='top'),font_size=14))
# bar_render=bar_cluster.render('customer.html')
# make_snapshot(snapshot,bar_render,'customer.png',pixel_ratio=3,notebook=True)
2. 客户画像
通过pairplot大致排除特征区别不大的量
plt.figure(figsize=(15,15),dpi=150)
relation=['Income', 'Recency','age','days',
'edu', 'together', 'children', 'amount_sum','cluster']
sns.pairplot(df_clean[relation],hue='cluster',corner=True)
plt.savefig('relation.png')
对比总体平均值和组间平均值
hua=['Income','age','children','amount_sum','days','edu']#
huaxiang= df_clean.groupby('cluster')[hua].mean().round(2)
raw=df_clean[hua].describe().round(2)
得出画像:
group0:
1108
低收入,低消费
50岁以下
基本上都有孩子,有30%为二孩家庭
受教育水平较低
group1:
571
较高收入,较高消费
最‘老’客户
基本上都有孩子,多为一孩
高教育水平
group2:
533
高收入,高消费
新客户
50岁左右
没有孩子
高教育水平
3.消费偏好
shangpin=['MntWines', 'MntFruits', 'MntMeatProducts',
'MntFishProducts', 'MntSweetProducts', 'MntGoldProds']
jiegou_dict=jiegou.to_dict(orient ='records')
jiegou_list = [[(k, v) for k, v in d.items()] for d in jiegou_dict]
from pyecharts.charts import Pie
pie_jiegou = (Pie(init_opts=opts.InitOpts(animation_opts=opts.AnimationOpts(animation=False),
bg_color='rgb(255,255,255)')) #关闭动画
.add('group0',jiegou_list[0],center=['25%','25%'],radius=[0,'35%'])
.add('group1',jiegou_list[1],center=['75%','25%'],radius=[0,'35%'])
.add('group2',jiegou_list[2],center=['50%','75%'],radius=[0,"35%"])
.set_global_opts(title_opts=opts.TitleOpts(title='细分客户消费偏好',pos_left='center',
title_textstyle_opts=opts.TextStyleOpts(font_size=25)),
legend_opts=opts.LegendOpts(pos_left='80%',pos_top="50%",orient='vertical'))
.set_series_opts(label_opts={'formatter':"{d}%",'fontsize':18})
)
4.渠道及其他
qudao = ['NumWebPurchases', 'NumCatalogPurchases',
'NumStorePurchases']
fangshi=df_clean.groupby('cluster')[qudao].sum();fangshi
fangshi_dict=fangshi.to_dict(orient ='records')
fangshi_list = [[(k, v) for k, v in d.items()] for d in fangshi_dict]
pie_fangshi = (Pie(init_opts=opts.InitOpts(animation_opts=opts.AnimationOpts(animation=False),
bg_color='rgb(255,255,255)')) #关闭动画
.add('group0',fangshi_list[0],center=['25%','25%'],radius=[0,'35%'])
.add('group1',fangshi_list[1],center=['75%','25%'],radius=[0,'35%'])
.add('group2',fangshi_list[2],center=['50%','75%'],radius=[0,"35%"])
.set_global_opts(title_opts=opts.TitleOpts(title='细分客户渠道偏好',pos_left='center',
title_textstyle_opts=opts.TextStyleOpts(font_size=25)),
legend_opts=opts.LegendOpts(pos_left='80%',pos_top="50%",orient='vertical'))
.set_series_opts(label_opts={'formatter':"{d}%",'fontsize':18})
)
5.其他
同理,可以用groupby+的方式讨论其他方面,由于代码结构大致相同,不一一赘述
六、结果报告
见下一集