文章导览
目标定义
数据获取
数据探索性分析
数据清洗
RFM模型处理
一、目标定义
这里有一个关于欧洲某商家2010年12月-2011年12月的销售数据截取的部分片段。目标是根据RF模型对顾客进行划分。
二、数据获取
RFM模型训练用 - Heywhale.comhttps://www.heywhale.com/mw/dataset/623f3a0b40f3c80018378be0/file数据集已挂在和鲸社区,链接如上
三、探索性分析
3,1 分析方向
查看数据类型、字段、大小、缺失情况,异常情况等
## 导入相关依赖
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
plt.rcParams["font.sans-serif"]=["SimHei"]
plt.rcParams['axes.unicode_minus'] = False # 解决中文显示的问题
from warnings import filterwarnings
filterwarnings('ignore') # 忽略警告的问题
import seaborn as sns
!pip install brewer2mpl # 解决画图库尴尬的配色问题
import brewer2mpl
# 导入数据
data = pd.read_csv("./data_sale.csv")
data.head(10)
# 查看基本信息
data.shape
data.isnull().sum()
data.info()
# 输出如下
(541909, 8)
InvoiceNo 0
StockCode 0
Description 1454
Quantity 0
InvoiceDate 0
UnitPrice 0
CustomerID 135080
Country 0
dtype: int64
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 541909 entries, 0 to 541908
Data columns (total 8 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 InvoiceNo 541909 non-null object
1 StockCode 541909 non-null object
2 Description 540455 non-null object
3 Quantity 541909 non-null int64
4 InvoiceDate 541909 non-null object
5 UnitPrice 541909 non-null float64
6 CustomerID 406829 non-null float64
7 Country 541909 non-null object
dtypes: float64(2), int64(1), object(5)
memory usage: 33.1+ MB
# 数据集共54万1999条数据,8个字段
# InvoiceNo 发票编号 字符串 无缺
# StockCode 走势 字符串 无缺
# Description 描述 字符串 有缺
# Quantity 数量 数字型 无缺
# InvoiceDate 发票日期 字符串 无缺
# UnitPrice 单价 小数型 无缺
# CustomerID 顾客编号 小数型 有缺
# Country 国家 字符型 无缺
观察到Customer_ID一列数据类型是浮点小数,需要转换成字符型
# 转换类型
data["InvoiceDate"] = pd.to_datetime(data["InvoiceDate"])
data["CustomerID"] = data["CustomerID"].astype("object",copy=False)
四、数据清洗
## 数据清洗
# 去重
data = data.drop_duplicates() # duplicates (n)副本 默认行数据比较取出完全相同的
data.info() # 去重后还剩53万+的数据
-------------------------------------
# 输出
<class 'pandas.core.frame.DataFrame'>
Int64Index: 536641 entries, 0 to 541908
Data columns (total 8 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 InvoiceNo 536641 non-null object
1 StockCode 536641 non-null object
2 Description 535187 non-null object
3 Quantity 536641 non-null int64
4 InvoiceDate 536641 non-null datetime64[ns]
5 UnitPrice 536641 non-null float64
6 CustomerID 401604 non-null object
7 Country 536641 non-null object
dtypes: datetime64[ns](1), float64(1), int64(1), object(5)
memory usage: 36.8+ MB
--------------------------------------
# 统计异常值
data.describe() # 负值明显是错误的
# 查看负值的情况有多少
data[data["UnitPrice"]<0]["UnitPrice"].count()
data[data["Quantity"]<0]["Quantity"].count()
--------------------------------------------
# 输出
Quantity UnitPrice
count 536641.000000 536641.000000
mean 9.620029 4.632656
std 219.130156 97.233118
min -80995.000000 -11062.060000
25% 1.000000 1.250000
50% 3.000000 2.080000
75% 10.000000 4.130000
max 80995.000000 38970.000000
2
10587
--------------------------------------------
# 删除不符合条件的行
data = data[data['UnitPrice']>=0]
data = data[data['Quantity']>=0]
# 缺失删除的是整行数据,删完后还剩52万6千+的数据
# 统计缺失值有多少
data.isnull().sum()
# 统计缺失值的占比
data.isnull().sum()/data.shape[0]*100 # 顾客ID一栏缺失的比较多
# 删除CustomerID为空的数据
data = data[~data["CustomerID"].isnull()] # 删除某些行时,可以用赋值语句。~代表取反
data = data.reset_index(drop=True) # 删除之后还剩39万+行数据
五、数据分析及RFMM用户价值分析
## 数据分析
# 添加一列sales
data["sales"] = data["Quantity"] * data["UnitPrice"]
data
# 计算购买频率(每个顾客购买次数)
frequency_data = data.groupby("CustomerID")["InvoiceNo"].count()
frequency_data = pd.DataFrame(frequency_data)
# 计算每个顾客的购买总金额
frequency_data["sales"] = data.groupby("CustomerID")["sales"].sum()
# 计算顾客的最近购买行为与最终日期的相隔天数
frequency_data["dateDiff"] = (pd.to_datetime('2012-01-01') - data.groupby("CustomerID")["InvoiceDate"].max()).dt.days
data_rfm = frequency_data
# 重命名列
data_rfm.columns = ["frequency","sales","datediff"]
data_rfm
## 数据可视化
# 查看数据分布
sns.pairplot(data_rfm) # pairplot函数查看数据两两之间的关系
# 绘制数据的直方图
plt.figure(1,figsize=(12,6))
n=0
for x in ['frequency','datediff','sales']:
n+=1
plt.subplot(1,3,n)
plt.subplots_adjust(hspace=0.5,wspace=0.5)
sns.distplot(data_rfm[x],bins=30)
plt.title('{} 直方图'.format(x))
plt.show()
## RFM模型
# 计算出划分用户的阈值,通过分布直方图可以发现该份数据不适合用中位数来分层,因此这里用均值做分层
r_mean = data_rfm["datediff"].mean()
f_mean = data_rfm["frequency"].mean()
m_mean = data_rfm["sales"].mean()
# 添加一列数据type
data_rfm["cus_type"] = np.nan
data_rfm
# 编写循环填充表格type
for i in range(len(data_rfm)):
if data_rfm.iloc[i,0] >= f_mean and data_rfm.iloc[i,1] >= m_mean and data_rfm.iloc[i,2] < r_mean:
data_rfm.iloc[i,3]="重要价值用户"
elif data_rfm.iloc[i,0] >= f_mean and data_rfm.iloc[i,1] >= m_mean and data_rfm.iloc[i,2] >= r_mean:
data_rfm.iloc[i,3]="重要保持用户"
elif data_rfm.iloc[i,0] < f_mean and data_rfm.iloc[i,1] >= m_mean and data_rfm.iloc[i,2] < r_mean:
data_rfm.iloc[i,3]="重要发展用户"
elif data_rfm.iloc[i,0] < f_mean and data_rfm.iloc[i,1] >= m_mean and data_rfm.iloc[i,2] >= r_mean:
data_rfm.iloc[i,3]="重要挽留用户"
elif data_rfm.iloc[i,0] >= f_mean and data_rfm.iloc[i,1] < m_mean and data_rfm.iloc[i,2] < r_mean:
data_rfm.iloc[i,3]="一般价值用户"
elif data_rfm.iloc[i,0] < f_mean and data_rfm.iloc[i,1] < m_mean and data_rfm.iloc[i,2] < r_mean:
data_rfm.iloc[i,3]="一般发展用户"
elif data_rfm.iloc[i,0] >= f_mean and data_rfm.iloc[i,1] < m_mean and data_rfm.iloc[i,2] >= r_mean:
data_rfm.iloc[i,3]="一般保持用户"
elif data_rfm.iloc[i,0] < f_mean and data_rfm.iloc[i,1] < m_mean and data_rfm.iloc[i,2] >= r_mean:
data_rfm.iloc[i,3]="一般挽留用户"
data_rfm
# 计算个类型用户数量并可视化
Cus_type = data_rfm.groupby("cus_type")["cus_type"].count().sort_values(ascending=False)
sns.barplot(Cus_type.index,Cus_type)
plt.xticks(rotation=30)
plt.show()
# 计算不同用户的消费总额,可视化其份额比例
cus_sales = data_rfm.groupby("cus_type")["sales"].sum().sort_values(ascending=False)
cus_sales
plt.figure(figsize=(10,10))
explode01 = np.array([0.05]*8)
colors01 = brewer2mpl.get_map('Set3', 'qualitative', 8).mpl_colors # 解决配色问题关键
plt.pie(cus_sales,labels=cus_sales.index,autopct='%.2f%%',textprops={'fontsize':14,'color':'k'},colors=colors01,explode=explode01)
plt.title('不同类型的客户销售份额',fontsize=15)
plt.show()