1 项目背景
(1)数据
Kaggle数据源。英国零售商的实际交易数据,包含了2010年12月1日至2011年12月9日在英国注册的非实体网上零售发生的所有交易。公司主要销售独特的全天候礼品。该公司的许多客户都是批发商。本篇基于Python进行分析,还有一篇是基于SQL语言进行的分析。
(2)分析目的
采用RFM模型,从用户、地区和时间等维度分析用户行为并提出优化建议。
2 理解数据
InvoiceNo --> 订单号码: 6位字符串
StockCode --> 产品代码: 6位字符串
Description --> 产品描述
Quantity --> 产品数量:交易产品数量
InvoiceDate --> 订单日期:订单发生的日期和时间
UnitPrice --> 单价:浮点数值
CustomerID --> 顾客ID:5位字符串
Country --> 国家:客户所在地
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import warnings
warnings.filterwarnings('ignore')
import plotly as py
import plotly.graph_objs as go
pyplot = py.offline.iplot
py.offline.init_notebook_mode()
df = pd.read_csv(r'E:\jupyter_lab\leetcode\data\UK retailer-kaggle\data.csv',encoding='ISO-8859-1',dtype={'CustomerID':str})
df.head()
360截图176106119511198.png
df.info()
360截图1872011583114132.png
3 数据清洗
(1) 空值 :空值是什么?没有影响的删掉,有影响的填充。
(2) 去重操作(看情况分析)。
(3) 数据格式:时间格式(日期表示)、字符格式、类别格式(编码问题)。
(4) 异常值分析(describe:负数,极值等)。此处,负数代表退货。
(5) 计算一些分析用到的统计变量。
(1)空值
df.apply(lambda x : sum(x.isnull())/len(x)) #缺失率
df.isnull().sum() #缺失值个数
(2)重复值、无用字段 和 空值填充
df=df.drop_duplicates()
df.drop(['Description'],axis=1,inplace=True) #按列操作,默认是删除行的
df.CustomerID = df.CustomerID .fillna('U') #U表示customer_id缺失
(3)数据格式转化
df['date'] = [x.split(' ')[0] for x in df.InvoiceDate ] #月-日-年
df['time'] = [x.split(' ')[1] for x in df.InvoiceDate ]
df.drop(['InvoiceDate'],axis=1,inplace=True)
df['year'] = [x.split('/')[2] for x in df.date]
df['month'] = [x.split('/')[0] for x in df.date]
df['day'] = [x.split('/')[1] for x in df.date]
df['date'] = pd.to_datetime(df.date)
df['year']=pd.to_numeric(df['year'])
df['month']=pd.to_numeric(df['month'])
df['day']=pd.to_numeric(df['day'])
#是数字的一定变成数字,不然排序的时候不灵光
(4)异常值分析
df.describe() #数据探索,异常值分析
360截图16720402111152114.png
#禁用科学计数法
pd.set_option('display.float_format',lambda x : '%.2f' % x)
pd.options.display.float_format = '{:,.2f}'.format
df2 = df[df['UnitPrice']<=0]
df2.shape[0]/df.shape[0] #单价为负值的比例
0.00468
#分类统计方法一
df2.UnitPrice.groupby(df2.UnitPrice).count()
#分类统计方法二
from collections import Counter
Counter(df.UnitPrice)
360截图1672033173103102.png
(5)统计一些分析用到的变量
df['amount'] = df.Quantity * df.UnitPrice #消费总金额
4 数据分析
该是数值的地方一定在数据处理的时候改成数值类型,不然在排序的时候打死你也不知道为什么不能排序
(1)退货率=退货金额/销售金额
df1 = df[df.Quantity<=0]
sales_return = pd.pivot_table(df1,index=['year'],columns=['month'],values=['amount'],aggfunc=np.sum,fill_value=0)
df2 = df[(df.Quantity>0) & (df.UnitPrice>0)]
sales = pd.pivot_table(df2,index=['year'],columns=['month'],values=['amount'],aggfunc=np.sum,fill_value=0.001)
return_goods_ratio=np.abs(sales_return/sales)
return_goods_ratio.sort_index(level=1,axis=1,inplace=True)
360截图18290329726667.png
这涉及多重索引,下面是数据提取的一些方法。
return_goods_ratio.iloc[0,:].index.levels[1]
return_goods_ratio.iloc[1,:]
按照month 排序,然后把amount绘制出来
data_2011 = pd.DataFrame({'return_ratio':return_goods_ratio.iloc[1,:]})
data_2011 = data_2011.iloc[:,-1].reset_index().iloc[:,1:]
%matplotlib inline
plt.plot(data_2011.month,data_2011.return_ratio)
360截图18720122103705.png
return_goods_ratio.iloc[1,:].mean()
#2011年退货的平均数 0.092
(2)RFM模型
R_value = df2.groupby('CustomerID').date.max()
R_value = (df2.date.max()-R_value).dt.days
#每位客户最近一次购买时间到参考日期的时间间隔(此值越小越好)
F_value = df2.groupby('CustomerID').InvoiceNo.nunique() #唯一值的个数
#每位客户的购买频率(此值越大越好
M_value = df2.groupby('CustomerID').amount.sum()
#每位用户的消费总金额(此值越大越好)
(2.1)探索RFM数据
R_value.describe()
360截图187201248212296.png
可视化一:
sns.distplot(R_value,bins=30)
360截图17650105739789.png
可视化二:
plt.hist(R_value,bins=30)
plt.show()
360截图168004118911176.png
同理于消费频次与购买金额的分析。在此略过。
(2.2) RFM分级
指定分割段
R_bins = [0,30,90,180,360,720]
通过分位数指定分割段
F_value.quantile([0.1,0.2,0.3,0.4,0.5,0.9,1])
F_bins = [0,2,10,100,1000,2000]
M_value.quantile([0.1,0.2,0.3,0.4,0.5,0.9,1])
M_bins = [100,200,500,5000,100000,2000000]
依据分割段分割
R = pd.cut(R_value,R_bins,labels=[5,4,3,2,1],right=False)
#不包含上线 越小越好
F = pd.cut(F_value,F_bins,labels=[1,2,3,4,5],right=False)
#不包含上线
M = pd.cut(M_value,M_bins,labels=[1,2,3,4,5],right=False)
#不包含上线
rfm = pd.concat([R,F,M],axis=1)
#按列操作,扩展
rfm.rename(columns={'date':'R_score','InvoiceNo':'F_score','amount':'M_score'},inplace=True)
rfm.head()
360截图17411027432772.png
看一下rfm的数据类型
rfm.info()
360截图16720401180641.png
(2.3) 数据类型的转化,便于计算
for i in rfm.columns:
rfm[i] = rfm[i].astype(float)
依据平均值划分客户类型
rfm['R'] = np.where(rfm['R_score']>rfm['R_score'].mean(),'高','低')
rfm['F'] = np.where(rfm['F_score']>rfm['F_score'].mean(),'高','低')
rfm['M'] = np.where(rfm['M_score']>rfm['M_score'].mean(),'高','低')
rfm['values'] = rfm.R.str[:]+rfm.F.str[:]+rfm.M.str[:]
rfm['values']=rfm['values'].str.strip()
定义客户类型的函数
def class_func(x):
if x == '高高高':
return '重要价值客户'
elif x == '高低高':
return '重要发展客户'
elif x == '低高高':
return '重要保持客户'
elif x == '低低高':
return '重要挽留客户'
elif x == '高高低':
return '一般价值客户'
elif x == '高低低':
return '一般发展客户'
elif x == '低高低':
return '一般保持客户'
else:
return '一般挽留客户'
将客户类型映射到函数上,得到客户等级
rfm['用户等级'] = rfm['values'].apply(class_func)
rfm.head()
360截图16780702407079.png
存一下数据
rfm.to_csv(r'E:\jupyter_lab\leetcode\data\UK retailer-kaggle\rfm.csv')
(2.4) RFM可视化
条形图
trace_base = [go.Bar(x=rfm['用户等级'].value_counts().index,
y=rfm['用户等级'].value_counts().values
, marker=dict(color='orange'),opacity=0.5
)]
layout = go.Layout(title='用户等级分布',xaxis=dict(title='用户等级'))
figure_base = go.Figure(data=trace_base,layout=layout)
pyplot(figure_base, auto_open=True)
newplot.png
饼图
trace_pie = [go.Pie(labels=rfm['用户等级'].value_counts().index,
values=rfm['用户等级'].value_counts().values
, textfont=dict(color='orange',size=12))]
layout = go.Layout(title='用户等级比例',xaxis=dict(title='用户等级'))
figure_base = go.Figure(data=trace_pie,layout=layout)
pyplot(figure_base, auto_open=True)
pie.png
5 结论与对策
(1)1月与12月的退货率比较高,我们要对比往年同时间段的退货率,如果与往年不同,此时我们要继续分析退货率增加的原因。
(2)由RFM模型可知,客户类型中占比最多的是“重要价值客户”(最近购买了,购买频率还挺高,购买金额也挺大)此时,应向该部分人群继续推送公司主营业务,通过宣传推广等手段,让产品信息送达客户手中。其次,占比较多的是“一般挽留客户”(很长时间没买了,购买的频率比较低,购买的金额也比较少),公司应该面向该部分人群推出促销活动,拉动消费的积极性。
网友评论