顾客购物数据清洗、可视化和聚类分析.
可视化图片和ipyb文件,详见! [Github] (https://github.com/xin5835/customer_shopping_analysis)
导入库
import numpy as np
import pandas as pd
import datetime
from matplotlib import pyplot as plt
import seaborn as sns
plt.style.use(['fivethirtyeight'])
from sklearn.preprocessing import LabelEncoder
from sklearn.preprocessing import StandardScaler
from sklearn import metrics
from sklearn.decomposition import PCA
from sklearn.cluster import KMeans
from sklearn.cluster import AgglomerativeClustering
from yellowbrick.cluster import KElbowVisualizer
import warnings
warnings.filterwarnings('ignore')
加载数据
data = pd.read_csv('/Users/robert/python/marketing_campaign.csv', sep= '\t')
data.info()
data.head().T
数据准备
- 数据维度说明
| 分类 | 列名 | 说明 |
|---|---|---|
| 用户属性 | ID | 用户的唯一标识符 |
| 用户属性 | Year_Birth | 用户的出生年份 |
| 用户属性 | Education | 教育水平 |
| 用户属性 | Marital_Status | 婚姻状况 |
| 用户属性 | Income | 年家庭收入 |
| 用户属性 | Kidhome | 儿童数量 |
| 用户属性 | Teenhome | 青少年人数 |
| 用户健康度 | Dt_Customer | 顾客注册的日期 |
| 用户健康度 | Recency | 自用户上次购买以来的天数 |
| 用户健康度 | NumWebVisitsMonth | 上个月访问公司网站的次数 |
| 用户健康度 | Complain | 如果用户在过去 2 年内投诉,则为 1,否则为 0 |
| 交易信息 | MntWines | 过去两年在葡萄酒上的消费金额 |
| 交易信息 | MntFruits | 过去两年在水果上的花费 |
| 交易信息 | MntMeatProducts | 过去2年肉类消费量 |
| 交易信息 | MntFishProducts | 过去2年在鱼上花费的金额 |
| 交易信息 | MntSweetProducts | 过去2年在糖果上花费的金额 |
| 交易信息 | MntGoldProds | 过去2年在黄金上花费的金额 |
| 交易来源 | NumWebPurchases | 通过公司网站进行的购买数量 |
| 交易来源 | NumCatalogPurchases | 使用目录进行的购买数量 |
| 交易来源 | NumStorePurchases | 直接在商店购买的数量 |
| 运营转化信息 | NumDealsPurchases | 打折的购买次数 |
| 运营转化信息 | AcceptedCmp1 | 如果用户在第一个活动中接受了报价,则为 1,否则为 0 |
| 运营转化信息 | AcceptedCmp2 | 如果用户在第二个活动中接受了报价,则为 1,否则为 0 |
| 运营转化信息 | AcceptedCmp3 | 如果用户在第三个活动中接受了报价,则为 1,否则为 0 |
| 运营转化信息 | AcceptedCmp4 | 如果用户在第 4 个活动中接受了报价,则为 1,否则为 0 |
| 运营转化信息 | AcceptedCmp5 | 如果用户在第 5 个活动中接受了报价,则为 1,否则为 0 |
| 运营转化信息 | Response | 如果用户在上次活动中接受了报价,则为 1,否则为 0 |
| Z_CostContact | Z_成本联系方式 | |
| Z_Revenue | Z_收入 |
## 检查缺失值
print("缺失值:\n", data.isnull().sum()) ## 收入数据有缺失值,缺失了24条数据
## 检查是否有重复数据
data.duplicated().sum()
分析思路:
- 目标:挖掘数据的价值,助力业务成长和用户增长
- 用户信息—-做用户画像
- 用户健康度—-做用户价值评级
- 交易信息–做推荐算法
- 交易来源—-做增长分析
- 运营转化—-评估运营活动的效果
数据清洗
- 收入数据有24条缺失值,使用均值对其进行填充
- 顾客注册时间需要转换为日期格式
- 教育水平和婚姻状态两个变量需要对其进行重新编码
## 采用均值对收入列的24条缺失数据进行填充
data = data.fillna(data['Income'].mean())
## 将注册日期数据格式调整为日期格式
data['Dt_Customer'] = pd.to_datetime(data['Dt_Customer'])
data['Dt_Customer'].apply(['min', 'max'])
print("婚姻状况:\n", data['Marital_Status'].value_counts(), '\n')
print("教育情况:\n", data['Education'].value_counts())
## 不同婚姻状态的顾客所占比例
plt.figure(figsize=(9, 9))
x = data['Marital_Status'].value_counts().sort_values()
plt.pie(x=x, labels=['YoLo', 'Absurd','Alone', 'Window', 'Divorced', 'Single', 'Together','Married'],
autopct = '%1.1f%%', )
plt.axis('equal')
plt.title('Propotion of Marital_Status')
## 不同受教育水平的顾客所占比例
x = data['Education'].value_counts().sort_values()
plt.figure(figsize=(9,9))
plt.pie(x=x, labels=['2n Cycle', 'Basic', 'Master', 'PhD', 'Graduation'], autopct = '%1.1f%%')
plt.title('Propotion of Education')
数据预处理
创建新的数据特征:
- 利用出生日期创建Age(年龄)列
- 创建Spent(总消费)列
- 创建Children(小孩)列
- 创建TotalAcceptedCmp(接受报价次数合计)列
- 创建NumTotalPurchases(总购买数量)列
data['Age'] = data.apply(lambda data: 2015- data['Year_Birth'], axis=1)
data['Spent'] = data.apply(lambda data: data['MntFishProducts'] + data['MntFruits'] + data['MntGoldProds'] +
data['MntMeatProducts'] + data['MntSweetProducts'] + data['MntWines'], axis=1)
data['Children'] = data.apply(lambda data: data['Kidhome'] + data['Teenhome'], axis=1)
data['TotalAcceptedCmp'] = data.apply(lambda data: data['AcceptedCmp1'] + data['AcceptedCmp2'] +
data['AcceptedCmp3'] + data['AcceptedCmp4'] +
data['AcceptedCmp5'], axis=1)
data['NumTotalPurchases'] = data.apply(lambda data: data['NumWebPurchases'] + data['NumCatalogPurchases'] +
data['NumStorePurchases'] + data['NumDealsPurchases'], axis=1)
## 删除'Year_Birth', 'Z_CostContact', 'Z_Revenue', 'Kidhome', 'Teenhome'列
data = data.drop(['Year_Birth', 'Z_CostContact', 'Z_Revenue', 'Kidhome', 'Teenhome'], axis=1)
## 删除数据中收入和年龄的异常值部分
data = data.drop(data.loc[data['Income'] > 600000].index)
data = data.drop(data.loc[data['Age'] > 100].index)
数据可视化
## 创建新的特征”Have_Children“,有小孩值为1,没有小孩值为0
data['Have_Children'] = np.where(data.Children > 0, 1, 0)
data.head()
## 以是否有小孩为分组对数据进行分组绘制散点图矩阵
pairplot = data.loc[:, ['Income', 'Age', 'Spent', 'Recency', 'Have_Children']]
sns.pairplot(pairplot, hue='Have_Children', palette='Set1')
## 对连续型变量Age进行面元划分
bins = [0, 20, 40, 60, 80,100]
aged = pd.cut(data['Age'], bins=bins)
print('number of age_cut:\n', aged.value_counts())
## 不同年龄段顾客分类汇总
plt.figure(figsize=(12,6))
sns.countplot(y=aged, palette='Set2')
plt.title('countplot of Age')
## 不同年龄区间在购物花费上的区别
plt.figure(figsize=(12,6))
sns.boxplot(x=aged, y=data['Spent'], palette='Set2')
## 受教育水平和购物花费间的关系
## 不同婚姻状态下顾客的花费比较
to_boxplot = ['Education', 'Marital_Status']
fig, axes = plt.subplots(2, 1, figsize=(12, 12))
axes = axes.flatten()
for col, ax in zip(to_boxplot, axes):
ax = sns.boxplot(data=data, x=col, y='Spent', ax=ax, palette='Set2')
ax.set_title(f'boxplot of spent by {col}')
## 以教育水平的不同对不同婚姻状态的顾客进行分组,并对比其在收入和购物花费上的差异对比
fig, axes = plt.subplots(2, 1, figsize=(14,10))
sns.barplot(x= 'Marital_Status',y='Income',hue='Education',data=data, ci=0,palette='Set2', ax=axes[0])
sns.barplot(x= 'Marital_Status',y='Spent',hue='Education',data=data, ci=0, palette='Set2', ax=axes[1])
## 收入和支出呈线性增长关系
fig, axes = plt.subplots(1, 2, figsize=(14,6))
sns.scatterplot(y=data['Spent'], x=data['Income'], ax=axes[0])
sns.regplot(y='Spent', x='Income', data=data, ax=axes[1])
## 网站顾客整体花费直方图
plt.figure(figsize=(12,6))
sns.histplot(data['Spent'], bins=50, kde=True)
plt.title('histogram of Spent')
## 自用户上次购买以来的天数的频数分布
plt.figure(figsize=(12,6))
sns.histplot(data['Recency'], bins=60)
## 网站用户月度活跃频数统计
plt.figure(figsize=(12,6))
sns.histplot(data['NumWebVisitsMonth'], bins=50, kde=True)
plt.title('Monthly activities (5-7)/month')
## 网站投诉情况
x = data['Complain'].value_counts().sort_values()
plt.figure(figsize=(8,8))
plt.pie(x=x, labels=['YES', 'NO'], autopct = '%1.1f%%')
plt.title('Complain of customer')
## 酒、鱼、水果、黄金、肉、糖果各自销量频数统计
to_histplot = ['MntWines', 'MntFishProducts', 'MntFruits', 'MntGoldProds',
'MntMeatProducts', 'MntSweetProducts']
fig, axes = plt.subplots(3, 2, sharex=True, sharey=True, figsize=(14, 10))
axes = axes.flatten()
for col, ax in zip(to_histplot, axes):
ax = sns.histplot(data=data, x=col, ax=ax)
ax.set_title(f'histogram of {col}')
## 通过目录、商店和网站三个渠道购买的频数统计
to_histplot = ['NumCatalogPurchases', 'NumStorePurchases', 'NumWebPurchases']
fig, axes = plt.subplots(1, 3, sharey=True, figsize=(14, 9))
axes = axes.flatten()
for col, ax in zip(to_histplot, axes):
ax = sns.histplot(data=data, x=col, ax=ax)
plt.subplots_adjust(hspace = 0.5, wspace = 0.3)
## 创建总活动接受报价次数指标
acceptedConcat = data[['AcceptedCmp1', 'AcceptedCmp2', 'AcceptedCmp3', 'AcceptedCmp4', 'AcceptedCmp5']]
acceptedConcat = acceptedConcat.apply(pd.DataFrame.sum)
print('acceptedConcat:\n', acceptedConcat)
## 每次接受活动报价的用户的频数统计
plt.figure(figsize=(12,6))
plt.title('accepted the campaings in every attempt')
sns.barplot(x=acceptedConcat.index, y=acceptedConcat, palette='Set2')
## 用户上次购买以来的天数和在上次活动是否接受报价间的关系
plt.figure(figsize=(12,6))
plt.title('Recency Vs Acceptance of an offer')
sns.lineplot(x='Recency', y='Response', data=data)
## 网站每月活跃用户折线图
groupedDate = data.set_index('Dt_Customer')
groupedDate = groupedDate.resample('M').count()
plt.figure(figsize=(14,4))
plt.title('Customer NumWebVisitsMonth by mounths')
groupedDate.NumWebVisitsMonth.plot(kind='line')
## 每月总体消费金额折线图
groupedDate = data.set_index('Dt_Customer')
groupedDate = groupedDate.resample('M').count()
plt.figure(figsize=(14,4))
plt.title('Customer Spent by mounths')
groupedDate.Spent.plot(kind='line')
## 每月通过网站购买的用户折线图
groupedDate = data.set_index('Dt_Customer')
groupedDate = groupedDate.resample('M').count()
plt.figure(figsize=(14,4))
plt.title('Customer NumWebPurchases by mounths')
groupedDate.NumWebPurchases.plot()
## 每月通过目录购买的用户折线图
groupedDate = data.set_index('Dt_Customer')
groupedDate = groupedDate.resample('M').count()
plt.figure(figsize=(14,4))
plt.title('Customer NumCatalogPurchases by mounths')
groupedDate.NumCatalogPurchases.plot(kind='line')
##每月通过商店购买的用户折现图
groupedDate = data.set_index('Dt_Customer')
groupedDate = groupedDate.resample('M').count()
plt.figure(figsize=(14,4))
plt.title('Customer NumStorePurchases by mounths')
groupedDate.NumStorePurchases.plot(kind='line')
自2014年6月份以来网站的网站访问次数和交易总量出现了大幅的下滑。通过网站、目录、商店直接购买的三个渠道都出现了相同轨迹的下滑。
数据预处理
s = (data.dtypes == 'object')
object_cols = list(s[s].index)
print("Categorical variables in the dataset:", object_cols)
LE=LabelEncoder()
for i in object_cols:
data[i]=data[[i]].apply(LE.fit_transform)
## 相关性热力图
plt.figure( figsize = (14, 14))
data1 = data.iloc[:, 1:-1]
sns.heatmap(data1.corr(), annot=True, cmap='Set2')
ds = data.copy()
cols_del = ['ID', 'AcceptedCmp3', 'AcceptedCmp4', 'AcceptedCmp5', 'AcceptedCmp1','AcceptedCmp2',
'Complain', 'Response', 'Have_Children', 'Dt_Customer', 'Children']
ds = ds.drop(cols_del, axis=1)
## 数据标准化
scaler = StandardScaler()
scaler.fit(ds)
scaled_ds = pd.DataFrame(scaler.transform(ds),columns= ds.columns )
PCA降维
pca = PCA(n_components=3)
pca.fit(scaled_ds)
PCA_ds = pd.DataFrame(pca.transform(scaled_ds), columns=(["col1","col2", "col3"]))
PCA_ds.describe().T
x =PCA_ds["col1"]
y =PCA_ds["col2"]
z =PCA_ds["col3"]
## 降维后数据可视化
fig = plt.figure(figsize=(10,8))
ax = fig.add_subplot(111, projection="3d")
ax.scatter(x,y,z, marker="o" )
ax.set_title("Data Visualization In The Reduced Dimension")
plt.show()
聚类
Elbow_M = KElbowVisualizer(KMeans(), k=10)
Elbow_M.fit(PCA_ds)
Elbow_M.show()
AC = AgglomerativeClustering(n_clusters=4)
## 拟合模型并预测聚类
yhat_AC = AC.fit_predict(PCA_ds)
PCA_ds["Clusters"] = yhat_AC
## 将聚类特征添加到原始数据
data["Clusters"]= yhat_AC
## 聚类结果可视化
fig = plt.figure(figsize=(10,8))
ax = plt.subplot(111, projection='3d', label="bla")
ax.scatter(x, y, z, s=50, c=PCA_ds["Clusters"], marker='o', cmap = 'Set2' )
ax.set_title("The Plot Of The Clusters")
plt.show()
评估模型
## 聚类结果分布图
plt.figure(figsize=(12, 6))
pl = sns.countplot(x=data["Clusters"], palette= 'Set2')
pl.set_title("Distribution Of The Clusters")
plt.show()
## 按照收入和支出对聚类结果进行可视化
plt.figure(figsize=(12, 6))
pl = sns.scatterplot(data = data,x=data["Spent"], y=data["Income"],hue=data["Clusters"], palette= 'Set2')
pl.set_title("Cluster's Profile Based On Income And Spent")
plt.legend()
plt.show()
收入与支出图显示集群模式
- 第 0 组:高支出和平均收入
- 第 1 组:高消费和高收入
- 第 2 组:低支出和低收入
- 第 3 组:高支出和低收入
plt.figure(figsize=(12,6))
sns.swarmplot(x=data["Clusters"], y=data["Spent"], alpha=0.9, palette= 'Set2' )
## 按照聚类结果对活动总接受情况进行可视化
plt.figure(figsize=(12,6))
pl = sns.countplot(x=data["TotalAcceptedCmp"],hue=data["Clusters"], palette= 'Set2')
pl.set_title("Count Of Promotion Accepted")
pl.set_xlabel("Number Of Total Accepted Promotions")
plt.show()