电商零售交易数据分析

news/2024/10/18 8:23:58/

1、项目背景

以英国的在线电子零售公司的跨国交易数据集作为分析样本,通过对该公司的运营指标统计分析以及构建RMF模型、K-Means机器学习算法从大量的电子零售交易数据中分析并找出价值用户,针对价值用户提供以消费者为中心的智能商业模式。

2、数据详情

2.1 数据来源

数据集来自一个在英国注册的在线电子零售公司,其中包含2010年12月1日到2011年12月9日期间发生的网络交易数据。

2.2 数据量

数据集总共有541909条数据,8个字段

2.3 数据理解

字段名含义
InvoiceNo发票编号。为每笔订单唯一分配的6位整数。若以字母’C’开头,则表示该订单 被取消。
StockCode产品代码。为每个产品唯一分配的编码。
Description产品描述。
Quantity数量。每笔订单中各产品分别的数量。
InvoiceDate发票日期和时间。每笔订单发生的日期和时间。
UnitPrice单价。单位产品价格,单位为英镑。
CustomerID客户编号。为每个客户唯一分配的5位整数。
Country国家。客户所在国家/地区的名称

3、数据清洗

3.1 准备工作

# 导入库
import pandas as pd
import numpy as np
import datetime as dt# 数据可视化库
import matplotlib.pyplot as plt%matplotlib inline
#更改设计风格
plt.style.use('seaborn')
plt.rcParams['font.sans-serif'] = ['SimHei']#(显示中文)
plt.rcParams['axes.unicode_minus'] = False#(显示负数)
import seaborn as sns# 机器学习算法库
from sklearn.preprocessing import StandardScaler
from sklearn.cluster import KMeansimport  warnings
warnings.filterwarnings("ignore")
#导入数据
salesOR = pd.read_csv('Online Retail.csv')
#查看数据
salesOR.head()
InvoiceNoStockCodeDescriptionQuantityInvoiceDateUnitPriceCustomerIDCountry
053636585123AWHITE HANGING HEART T-LIGHT HOLDER62010/12/1 8:262.5517850.0United Kingdom
153636571053WHITE METAL LANTERN62010/12/1 8:263.3917850.0United Kingdom
253636584406BCREAM CUPID HEARTS COAT HANGER82010/12/1 8:262.7517850.0United Kingdom
353636584029GKNITTED UNION FLAG HOT WATER BOTTLE62010/12/1 8:263.3917850.0United Kingdom
453636584029ERED WOOLLY HOTTIE WHITE HEART.62010/12/1 8:263.3917850.0United Kingdom

查看数据信息

salesOR.shape
(541909, 8)
salesOR.info()
<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  float646   CustomerID   406829 non-null  float647   Country      541909 non-null  object 
dtypes: float64(2), int64(1), object(5)
memory usage: 33.1+ MB

3.2 删除重复值

salesOR_NoDup=salesOR.drop_duplicates(subset=['InvoiceNo','StockCode','Description','Quantity','InvoiceDate','UnitPrice','CustomerID','Country'])
dup=salesOR.shape[0]-salesOR_NoDup.shape[0]
print('duplicates =',dup)
duplicates = 5268

共删除了5268条重复数据

3.3 缺失值处理

查看数据缺失情况

tab_info=pd.DataFrame(salesOR.dtypes).T.rename(index={0:'特征类型'})
tab_info=tab_info.append(pd.DataFrame(salesOR.isnull().sum()).T.rename(index={0:'缺失值数量'}))
tab_info=tab_info.append(pd.DataFrame(salesOR.isnull().sum()/salesOR.shape[0]).T.rename(index={0:'缺失值占比'}))
display(tab_info)
InvoiceNoStockCodeDescriptionQuantityInvoiceDateUnitPriceCustomerIDCountry
特征类型objectobjectobjectint64objectfloat64float64object
缺失值数量0014540001350800
缺失值占比0.00.00.0026830.00.00.00.2492670.0

从上表可以看出,有近25%的条目未分配给特定客户。在现有的数据下,不可能为用户估算出数值,因此这些条目对目前的工作没有用处。所以应当从数据框中删除它们。

#删除InvoiceNo和CustomerID字段中的缺失数据
salesOR_NoDupNA=salesOR_NoDup.dropna(subset=['InvoiceNo','CustomerID'],how='any')
missing=salesOR_NoDup.shape[0]-salesOR_NoDupNA.shape[0]
print('missings =',missing)
missings = 135037

共删除InvoiceNo 和 CustomerID字段缺失的数据135037条

3.4 数据一致化处理

将字符串类型数据转化为数值类型

salesOR_NoDupNA['Quantity']=salesOR_NoDupNA['Quantity'].astype('float')
salesOR_NoDupNA['UnitPrice']=salesOR_NoDupNA['UnitPrice'].astype('float')
salesOR_NoDupNA['CustomerID']=salesOR_NoDupNA['CustomerID'].astype('object')
print('Data type after changing:\n',salesOR_NoDupNA.dtypes)
Data type after changing:InvoiceNo       object
StockCode       object
Description     object
Quantity       float64
InvoiceDate     object
UnitPrice      float64
CustomerID      object
Country         object
dtype: object

将字符串类型数据转化为日期类型

#将InvoiceDate数据转换成日期的数据类型,如果不符合日期的格式,转换后的值为空值
salesOR_NoDupNA['InvoiceDate']=pd.to_datetime(salesOR_NoDupNA['InvoiceDate'], errors='coerce')
#分离出年、月、日、星期几方便之后分析
#这样得到的数据依旧是一个整形数据
salesOR_NoDupNA['Year'] = salesOR_NoDupNA['InvoiceDate'].dt.year
salesOR_NoDupNA['Month'] = salesOR_NoDupNA['InvoiceDate'].dt.month 
salesOR_NoDupNA['Date'] = salesOR_NoDupNA['InvoiceDate'].dt.date
salesOR_NoDupNA['weekday'] = salesOR_NoDupNA['InvoiceDate'].dt.weekday
salesOR_NoDupNA.head()
InvoiceNoStockCodeDescriptionQuantityInvoiceDateUnitPriceCustomerIDCountryYearMonthDateweekday
053636585123AWHITE HANGING HEART T-LIGHT HOLDER6.02010-12-01 08:26:002.5517850.0United Kingdom2010122010-12-012
153636571053WHITE METAL LANTERN6.02010-12-01 08:26:003.3917850.0United Kingdom2010122010-12-012
253636584406BCREAM CUPID HEARTS COAT HANGER8.02010-12-01 08:26:002.7517850.0United Kingdom2010122010-12-012
353636584029GKNITTED UNION FLAG HOT WATER BOTTLE6.02010-12-01 08:26:003.3917850.0United Kingdom2010122010-12-012
453636584029ERED WOOLLY HOTTIE WHITE HEART.6.02010-12-01 08:26:003.3917850.0United Kingdom2010122010-12-012

数据转换过程中不符合日期格式的会转换为空值,需要再进行一次删除缺失值处理。

#转换日期过程中不符合日期格式的数值会被转换为空值,
#这里删除InvoiceDate,InvoiceTime,CustomerID,InvoiceNo中为空的行
salesOR_NoDupNA=salesOR_NoDupNA.dropna(subset=['InvoiceNo','CustomerID','InvoiceDate'],how='any')

3.5 异常值处理

#利用描述性指标(describe)查看是否有异常值。
salesOR_NoDupNA[['Quantity','UnitPrice']].describe()
QuantityUnitPrice
count401604.000000401604.000000
mean12.1832733.474064
std250.28303769.764035
min-80995.0000000.000000
25%2.0000001.250000
50%5.0000001.950000
75%12.0000003.750000
max80995.00000038970.000000

描述指标中购买产品的数量最小值为-8095,单品单价为0,这两个不符合实际情况确认为异常值。

querySer=salesOR_NoDupNA.loc[:,'Quantity']>0
salesOR_NoDupNA=salesOR_NoDupNA.loc[querySer,:]
querySer1=salesOR_NoDupNA.loc[:,'UnitPrice']>0
salesOR_NoDupNA=salesOR_NoDupNA.loc[querySer1,:]
print('after delete outlier:',salesOR_NoDupNA.shape)
after delete outlier: (392692, 12)

经过两次条件判断之后数据集大小为(392692,10),为了检查处理后的结果,再次检查描述指标:

salesOR_NoDupNA[['Quantity','UnitPrice']].describe()
QuantityUnitPrice
count392692.000000392692.000000
mean13.1197023.125914
std180.49283222.241836
min1.0000000.001000
25%2.0000001.250000
50%6.0000001.950000
75%12.0000003.750000
max80995.0000008142.750000

原数据集的时期是2010-12-1到2011-12-09,2011年12月数据不满一个月,为了方便分析和讨论本次分析选择数据时间为:2010-12-01到2011-11-30,12个月。

querySer2=salesOR_NoDupNA.loc[:,'InvoiceDate']<'2011-12-01'
salesOR_NoDupNA=salesOR_NoDupNA.loc[querySer2,:]
print('删除2011-11-30之后的数据:',salesOR_NoDupNA.shape)
删除2011-11-30之后的数据: (375666, 12)

4、运营指标统计分析

4.1 月追踪:月销售量、月销售额、月均销售额

4.11 月销售量

#因为每月交易 = 总唯一交易数,又因为相同的InvoiceNo包含不同的产品,所以需要计算唯一的InvoiceNo
transUni=salesOR_NoDupNA.drop_duplicates(subset=['InvoiceNo'])
monthly_trans=transUni.groupby(['Year','Month'])['InvoiceNo'].count()
print('月销售量 :\n',monthly_trans)
月销售量 :Year  Month
2010  12       1400
2011  1         9872         9973        13214        11495        15556        13937        13318        12809        175510       192911       2657
Name: InvoiceNo, dtype: int64
#绘制月销售量条形图
fig,ax = plt.subplots(figsize=(8,3))
monthly_trans.plot(kind='bar')
plt.xlabel('月份')
plt.ylabel('销售量')
plt.title('月销售量')
plt.show()

请添加图片描述

计算结果显示2011年9月、10月、11月的销售单数稳步增长,2011年11月达到最高值2657单,怀疑平台年末促销或者换季用户大量购买冬季产品导致,可以通过查询销售额占比最高的产品确认。

4.12 月销售额

#新增列Amount = Quantity * UnitPrice
salesOR_NoDupNA['Amount']=salesOR_NoDupNA.Quantity*salesOR_NoDupNA.UnitPrice
monthly_sales=salesOR_NoDupNA.groupby(['Year','Month'])['Amount'].sum()
print('月销售额:\n',monthly_sales)
月销售额:Year  Month
2010  12        570422.730
2011  1         568101.3102         446084.9203         594081.7604         468374.3315         677355.1506         660046.0507         598962.9018         644051.0409         950690.20210       1035642.45011       1156205.610
Name: Amount, dtype: float64
fig,ax = plt.subplots(figsize=(8,4))
monthly_sales.plot(kind='bar')
plt.xlabel('月份')
plt.ylabel('销售额')
plt.title('月销售额')
plt.show()

请添加图片描述

在销售额方面,2011年9月、10月、11月的销售额明显高于其他月份,虽然11月的总销售单数比10月要高出近(2657-1929)/1929100%≈38%但是11月的总销售额却只比10月高出(1156205.610-1035642.450)/1035642.450100%≈11.6%,初步评价2011年10月的销售情况最佳,可以重点看看10月份的销售活动。

4.13 月均销售额

monthSales=salesOR_NoDupNA.groupby(['Year','Month'])['Amount'].sum()
avg_monthSales=round(monthSales.sum()/12,2)
print('月均销售额:',avg_monthSales)
月均销售额: 697501.54

计算得出月均销售额为697501.54英镑,2011年9、10、11月远超平均值。

4.2 周追踪:周销售量、周销售额、周均销售额

4.21 周销售量

transUni=salesOR_NoDupNA.drop_duplicates(subset=['InvoiceNo'])
weekly_trans=transUni.groupby(['weekday'])['InvoiceNo'].count()
print('周销售量:\n',weekly_trans)
周销售量:weekday
0    2747
1    3074
2    3351
3    3801
4    2674
6    2107
Name: InvoiceNo, dtype: int64

发现没有数字5,查找源数据集里是否不包含5的星期数。 真的没有5,查看原始数据以及对应的星期数,5代表星期六。

fig,ax = plt.subplots(figsize=(8,3))
x = ['周一', '周二','周三','周四','周五','周日']
y = weekly_trans
plt.plot(x,y)
plt.xlabel('星期')
plt.ylabel('周销售量')
plt.show()

请添加图片描述

此处计算的是在一年观察时间段内,每个星期天数的累计订单数。星期四的订单数是最多的,怀疑有公司(周末不上班)在该电商平台购买用品和礼品。这里要特别提醒上面的结果是一年里面每一个星期的累计单数,数据集没有星期六的销售订单,需要跟电商公司确认。

4.22 周销售额

weeklySales=salesOR_NoDupNA.groupby(['weekday'])['Amount'].sum()
print('周销售额:\n',weeklySales)
周销售额:weekday
0    1305523.311
1    1651744.141
2    1515053.230
3    1878214.400
4    1254225.051
6     765258.321
Name: Amount, dtype: float64
fig,ax = plt.subplots(figsize=(8,4))
x = ['周一', '周二','周三','周四','周五','周日']
y = weeklySales
plt.plot(x,y)
plt.xlabel('星期')
plt.ylabel('周销售额')
plt.show()

请添加图片描述

跟销售量相吻合,累计周销售量在星期四达到峰值,临近周末回落,高度怀疑该电商平台出售的产品不是针对普通家庭消费者,因为周末是消费的低谷跟实际情况不符。

4.23 周均销售额

avg_weeklySales=round(weeklySales.sum()/52,2)
print('周均销售额:',avg_weeklySales)
周均销售额: 160961.89

计算结果为160961.89欧元,这是每周平均销售额,给运营者提供一个参考,可以初步评价每一星期的销售情况,是高于还是低于平均值。

4.3 效率指标:客单价、件单价、连带率、退货金额

4.31 客单价

total_Sales=salesOR_NoDupNA.Amount.sum()
total_customer=salesOR_NoDupNA.drop_duplicates(subset=['CustomerID']).shape[0]
#Sales per customer
sales_perCustomer=round(total_Sales/total_customer,2)
print('客单价=',sales_perCustomer)
客单价= 1947.87

客单价=总的销售额/总的客户数,计算结果客单价为1947.87欧元,也即一年里面每个消费者平均贡献了1947.87欧元。

4.32 件单价

total_product=salesOR_NoDupNA.Quantity.sum()
#Sales per item
sale_perItem=round(total_Sales/total_product,2)
print('件单价=',sale_perItem)
件单价= 1.72

件单价=销售总额/售出产品总数量,计算结果为每件产品的平均价格为1.72欧元。

4.33 连带率

total_transUni=transUni.shape[0]
joint_Rate=round(total_product/total_transUni,2)
print('连带率=',joint_Rate)
连带率= 274.04

连带率=售出的产品总数/总的交易单数,计算结果连带率=274.04,在分析的一年时间段里,每一笔交易售出的平均产品总数为274件,这里怀疑有大量采购的用户。

4.34 每月新客占比

grouped_user =salesOR_NoDupNA.groupby(['CustomerID'])
user_life = grouped_user['InvoiceDate'].agg(['min','max'])
user_life.head(10)
minmax
CustomerID
12346.02011-01-18 10:01:002011-01-18 10:01:00
12347.02010-12-07 14:57:002011-10-31 12:25:00
12348.02010-12-16 19:09:002011-09-25 13:13:00
12349.02011-11-21 09:51:002011-11-21 09:51:00
12350.02011-02-02 16:01:002011-02-02 16:01:00
12352.02011-02-16 12:33:002011-11-03 14:37:00
12353.02011-05-19 17:47:002011-05-19 17:47:00
12354.02011-04-21 13:11:002011-04-21 13:11:00
12355.02011-05-09 13:49:002011-05-09 13:49:00
12356.02011-01-18 09:50:002011-11-17 08:40:00
(user_life['min'] == user_life['max']).value_counts()
False    2757
True     1540
dtype: int64

可以算出2757/(2757+1540)≈64%的顾客都在此购买了不止一次

salesOR_NoDupNA['month'] = salesOR_NoDupNA.InvoiceDate.values.astype('datetime64[M]')
#按月份和用户ID分组
grouped_month_user = salesOR_NoDupNA.groupby(['month','CustomerID'])#将 当月用户订单日期最小值 与 用户订单日期最小值 联结
tmp = grouped_month_user.InvoiceDate.agg(['min']).join(grouped_user.InvoiceDate.min())# 判断用户当月订单日期最小值是否与用户订单日期最小值相等,新建字段new,new代表新客户
tmp['new'] = (tmp['min'] == tmp.InvoiceDate)# 重置索引列,并按月分组,作新客占比折线图
tmp.reset_index().groupby('month').new.apply(lambda x: x.sum()/x.count()).plot()
plt.title("新客占比折线图")
plt.xlim('2010-12','2011-11')
plt.show()

请添加图片描述

可以看出从去年12月到今年8月,每月新客数都在急剧下降。

4.35 复购率

复购率:即当月内,购买多次的用户占比

#用户每个月的消费次数
pivoted_counts=salesOR_NoDupNA.pivot_table(index='CustomerID',columns='month',values='InvoiceDate',aggfunc='count').fillna(0)
pivoted_counts.head()
month2010-12-012011-01-012011-02-012011-03-012011-04-012011-05-012011-06-012011-07-012011-08-012011-09-012011-10-012011-11-01
CustomerID
12346.00.01.00.00.00.00.00.00.00.00.00.00.0
12347.031.029.00.00.024.00.018.00.022.00.047.00.0
12348.017.06.00.00.05.00.00.00.00.03.00.00.0
12349.00.00.00.00.00.00.00.00.00.00.00.073.0
12350.00.00.017.00.00.00.00.00.00.00.00.00.0
#用户每个月的消费次数
pivoted_counts=salesOR_NoDupNA.pivot_table(index='CustomerID',columns='month',values='InvoiceNo',aggfunc='nunique').fillna(0)
pivoted_counts.head()
month2010-12-012011-01-012011-02-012011-03-012011-04-012011-05-012011-06-012011-07-012011-08-012011-09-012011-10-012011-11-01
CustomerID
12346.00.01.00.00.00.00.00.00.00.00.00.00.0
12347.01.01.00.00.01.00.01.00.01.00.01.00.0
12348.01.01.00.00.01.00.00.00.00.01.00.00.0
12349.00.00.00.00.00.00.00.00.00.00.00.01.0
12350.00.00.01.00.00.00.00.00.00.00.00.00.0
# applymap() 如果消费超过1次则赋值为1 若等于1则赋值为0  若无消费则赋值为空
purchase_r = pivoted_counts.applymap(lambda x: 1 if x > 1 else np.NaN if x == 0 else 0)
purchase_r.head()
month2010-12-012011-01-012011-02-012011-03-012011-04-012011-05-012011-06-012011-07-012011-08-012011-09-012011-10-012011-11-01
CustomerID
12346.0NaN0.0NaNNaNNaNNaNNaNNaNNaNNaNNaNNaN
12347.00.00.0NaNNaN0.0NaN0.0NaN0.0NaN0.0NaN
12348.00.00.0NaNNaN0.0NaNNaNNaNNaN0.0NaNNaN
12349.0NaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaN0.0
12350.0NaNNaN0.0NaNNaNNaNNaNNaNNaNNaNNaNNaN
#  复购率= sum()复购人数 / 总消费人数count()  
(purchase_r.sum() / purchase_r.count()).plot(figsize = (10,4))plt.title('2010年12月至2011年11月公司复购率变化趋势图')
plt.xlim('2010-12','2011-11')
plt.show()

请添加图片描述

复购率在20%-30%之间波动。

4.36 退货金额

在原数据集说明中,订单号如果以字母c开头表示该订单为取消订单,所以在这里想看看退货的情况。

querySer3=salesOR_NoDupNA.loc[:,'InvoiceNo'].str.startswith('c')
querySer3.unique()
array([False])

结果显示:array([False]),该数据集中并没有包含取消的订单,所以本次分析没能够涉及这一块。

5、构建RFM模型

5.1RFM指标计算

5.11 R - Recency

Recency在这里定义为用户最后一次购物距离现在有多少个月。

#First_purchase:第一次购买日期
#Last_purchase:最后一次购买日期
Last_purchase=salesOR_NoDupNA.groupby(['CustomerID']).Date.max()
First_purchase=salesOR_NoDupNA.groupby(['CustomerID']).Date.min()
Last_purchase.head()
CustomerID
12346.0    2011-01-18
12347.0    2011-10-31
12348.0    2011-09-25
12349.0    2011-11-21
12350.0    2011-02-02
Name: Date, dtype: object

先计算出最后一次购买时间距离最后一天的天数,然后再计算月分数。

#最后一次购买时间距离最后一天的天数
Max_date=salesOR_NoDupNA.Date.max()+dt.timedelta(days=1)
Recency_days=Last_purchase.map(lambda x:(x-Max_date).days)
#最后一次购买时间距离最后一天的月数
Recency_months=Recency_days.map(lambda x:round((-1*x)/30,1))
Recency_months.head()
CustomerID
12346.0    10.6
12347.0     1.0
12348.0     2.2
12349.0     0.3
12350.0    10.1
Name: Date, dtype: float64

为了了解数据集的情况,查看一下描述统计指标。

Recency_months.describe()
count    4297.000000
mean        3.049244
std         3.285932
min         0.000000
25%         0.500000
50%         1.700000
75%         4.900000
max        12.200000
Name: Date, dtype: float64

再切片看看分布情况。

bins1=[0,1,2,3,4,5,6,7,8,9,10,11,12,13]
pd.cut(Recency_months,bins1).value_counts()
(0, 1]      1606
(1, 2]       708
(2, 3]       445
(3, 4]       228
(4, 5]       194
(5, 6]       186
(6, 7]       180
(8, 9]       156
(7, 8]       140
(9, 10]      123
(10, 11]     115
(11, 12]      81
(12, 13]      43
Name: Date, dtype: int64

大概有37.7%的客户在一个月之内有购物行为,再绘制条形图更加直观的观察数据。

pd.cut(Recency_months,bins1).value_counts().plot.bar(rot=20)
<AxesSubplot:>

请添加图片描述

分布总体来说比较理想,可以利用以客户为中心的商业理念来提高[1, 2]以及[2, 3],即1~2个月,2~3个月之内有购物行为的用户。

5.12 F- Frequency

Frequency在这里定义为购物频率,也即在分析的一年里面,用户总购买次数。

#Freq_purchase:购物频率(多久购买一次)
Unique_purchase=salesOR_NoDupNA.drop_duplicates(subset=['InvoiceNo'])
Freq_purchase=Unique_purchase.groupby(['CustomerID']).InvoiceNo.count()
Freq_purchase.head()
CustomerID
12346.0    1
12347.0    6
12348.0    4
12349.0    1
12350.0    1
Name: InvoiceNo, dtype: int64

描述统计指标:

Freq_purchase.describe()
count    4297.000000
mean        4.131720
std         7.412705
min         1.000000
25%         1.000000
50%         2.000000
75%         4.000000
max       200.000000
Name: InvoiceNo, dtype: float64

购买的频率差异很大,最小为1次,最大为200次,切片看数据分布情况。

bins2=[1,20,40,60,80,100,120,140,160,180,200]
pd.cut(Freq_purchase,bins2).value_counts()
(1, 20]       2673
(20, 40]        65
(40, 60]        12
(80, 100]        4
(60, 80]         2
(180, 200]       2
(100, 120]       1
(120, 140]       0
(140, 160]       0
(160, 180]       0
Name: InvoiceNo, dtype: int64

大部分用户在一年的时间里购买次数在1~20次以内,比较符合实际情况。这里要注意有些离散的数值,比如一年里面消费100~120次的用户有1位用户,180~200次的用户有2位,后续的分析会针对这些离散值做特殊处理。

pd.cut(Freq_purchase,bins2).value_counts().plot.bar(rot=20)
<AxesSubplot:>

请添加图片描述

5.13 M - Monetary

Monetary定义为用户在一年之内所花总金额

#Money_purchase:消费金额
Money_purchase=salesOR_NoDupNA.groupby(['CustomerID']).Amount.sum()
Money_purchase.head()
CustomerID
12346.0    77183.60
12347.0     4085.18
12348.0     1797.24
12349.0     1757.55
12350.0      334.40
Name: Amount, dtype: float64

描述统计指标:

Money_purchase.describe()
count      4297.000000
mean       1947.874902
std        8351.696003
min           2.900000
25%         303.160000
50%         650.750000
75%        1594.230000
max      268478.000000
Name: Amount, dtype: float64

每个用户的消费总金额差异很大,最小值位2.9欧元,最大值位268478欧元。

再通过切片看数据分布:

bins3=[2.5,10,50,100,1000,10000,100000,200000,300000]
pd.cut(Money_purchase,bins3).value_counts()
(100.0, 1000.0]         2518
(1000.0, 10000.0]       1520
(50.0, 100.0]            119
(10000.0, 100000.0]       94
(10.0, 50.0]              36
(2.5, 10.0]                4
(100000.0, 200000.0]       4
(200000.0, 300000.0]       2
Name: Amount, dtype: int64
pd.cut(Money_purchase,bins3).value_counts().plot.bar(rot=20)
<AxesSubplot:>

请添加图片描述

跟购物频率F一样,购买金额也存在一些离散值,后续分析中会特别处理。

综合上面的计算,RFM的三个指标的数据区间范围分别是:

R [0.0, 12.1]

F [1.0, 200.0]

M [2.9, 268478.0]

5.2 利用RFM模型挖掘价值用户

5.21 建立RFM矩阵,并给列名重命名。

#计算RFM指标
rfm=salesOR_NoDupNA.groupby(['CustomerID']).agg({'Date':lambda x:(Max_date-x.max()).days,'InvoiceNo':'count','Amount':'sum'})
#列重命名
rfm.rename(columns={'Date':'Recency','InvoiceNo':'Frequency','Amount':'Monetary'},inplace=True)
rfm.head()
RecencyFrequencyMonetary
CustomerID
12346.0317177183.60
12347.0311714085.18
12348.067311797.24
12349.010731757.55
12350.030217334.40

RFM数据概览

sns.pairplot(rfm)
<seaborn.axisgrid.PairGrid at 0x1401bb40550>

请添加图片描述

单独抽出RFM直方图观看

plt.figure(1,figsize=(12,6))
n=0
for x in ['Frequency','Recency','Monetary']:n+=1plt.subplot(1,3,n)plt.subplots_adjust(hspace=0.5,wspace=0.5)sns.distplot(rfm[x],bins=30)plt.title('{} 直方图'.format(x))
plt.show()

请添加图片描述

可以看出购买商品的客户绝大多数是进行少量多次购买的客户

5.22 建立用户评分机制

计算用于划分客户的阙值,R、F、M的均值(通过分布直方图可以发现该份数据不适合用中位数来分层,因此这里用均值做分层)

rfm[['Recency','Frequency','Monetary']].apply(lambda x:x-x.mean())
RecencyFrequencyMonetary
CustomerID
12346.0225.518501-86.4251875235.725098
12347.0-60.48149983.574822137.305098
12348.0-24.481499-56.42518-150.634902
12349.0-81.481499-14.42518-190.324902
12350.0210.518501-70.42518-1613.474902
............
18280.0177.518501-77.42518-1767.274902
18281.080.518501-80.42518-1867.054902
18282.026.518501-80.42518-1847.664902
18283.0-90.481499583.57482-110.344902
18287.0-57.481499-17.42518-110.594902

4297 rows × 3 columns

创建label分析

def rfm_func(x):level = x.apply(lambda x : '1' if x>=0 else '0')label = level.Recency + level.Frequency + level.Monetaryd = {'111':'重要价值客户','011':'重要保持客户','101':'重要挽留客户','001':'重要发展客户','110':'一般价值客户','010':'一般保持客户','100':'一般挽留客户','000':'一般发展客户'}result = d[label]return resultrfm['label'] = rfm[['Recency','Frequency','Monetary']].apply(lambda x:x- x.mean()).apply(rfm_func,axis = 1)
rfm.head(10)
RecencyFrequencyMonetarylabel
CustomerID
12346.0317177183.60重要挽留客户
12347.0311714085.18重要保持客户
12348.067311797.24一般发展客户
12349.010731757.55一般发展客户
12350.030217334.40一般挽留客户
12352.028852506.04重要发展客户
12353.0196489.00一般挽留客户
12354.0224581079.40一般挽留客户
12355.020613459.40一般挽留客户
12356.014592811.43重要发展客户

查看不同类型顾客的总数

plt.figure(1,figsize=(12,7))
sns.countplot(y="label",order=rfm['label'].value_counts().index ,data=rfm,palette='Blues')
plt.title('不同类型的客户总数',fontsize=20)
plt.xlabel('')
plt.ylabel('')con=list(rfm.groupby('label').label.count().values)
con=sorted(con,reverse=True)for x,y in enumerate(con):plt.text(y+0.1,x,'%s' %y,va='center',size=12)
plt.show()

请添加图片描述

各个类型顾客收入

rfm.groupby('label').sum()
RecencyFrequencyMonetary
label
一般价值客户12324903475783.730
一般保持客户990756246449782.530
一般发展客户59739574941051071.043
一般挽留客户28121431068555079.770
重要价值客户57385936111406.171
重要保持客户145532077795279155.540
重要发展客户38526719584836.050
重要挽留客户57691390262903.620
plt.figure(figsize=(24,12))
#不同类型的客户销售份额
plt.subplot(1,2,1)
plt.axis('equal')labels=rfm.groupby('label').Monetary.sum().index
explode=[0,0,0,0,0,0.1,0,0]plt.pie(rfm.groupby('label').Monetary.sum(),explode=explode,labels=labels,radius=1,autopct='%.2f%%')
plt.title('不同类型的客户销售份额',fontsize=20)#不同类型的客户数量分布
plt.subplot(1,2,2)
plt.axis('equal')
plt.pie(rfm.groupby('label').count()['Monetary'],autopct='%.2f%%',labels = labels,pctdistance=0.9,explode=explode,radius=1)
plt.title("不同类型的客户数量分布")
plt.show()

请添加图片描述

rfm.query("label=='重要保持客户'")['Recency'].describe()
count    687.000000
mean      21.183406
std       20.123775
min        1.000000
25%        7.000000
50%       14.000000
75%       30.000000
max       91.000000
Name: Recency, dtype: float64

5.3 利用K-Means算法挖掘价值用户

5.31 对数变换

rfm_k=rfm[['Recency','Frequency','Monetary']]
#unskew the data with log transformation
rfm_log=rfm[['Recency','Frequency','Monetary']].apply(np.log,axis=1).round(3)

为方便查看,把三个标签的直方图画在一起。

#绘制RFM值的分布
f,ax=plt.subplots(figsize=(10,12))
plt.subplot(3,1,1);sns.distplot(rfm_log.Recency,label='Recency')
plt.subplot(3,1,2);sns.distplot(rfm_log.Frequency,label='Frequency')
plt.subplot(3,1,3);sns.distplot(rfm_log.Monetary,label='Monetary')
plt.show()

请添加图片描述

5.32 标准化

对数变换之后是标准化,使用公式:Z=(X-μ)/σ 可以使用scikit-learn里面preprocessing下面的StandardScaler实现标准化转换。

#使用StandardScaler对变量进行归一化
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
scaler.fit(rfm_log)
#单独存储用于聚类
rfm_normalized=scaler.transform(rfm_log)

5.33 选择适合的聚类数目

选择聚类数目的方法有好几种,这里采用的是Elbow Criterion method。

ks = range(1,8)
inertias=[]for k in ks:kc = KMeans(n_clusters=k, random_state=1)kc.fit(rfm_normalized)inertias.append(kc.inertia_)#样本到它们最近的聚类中心的平方距离的总和

把计算的1-8个聚类数目情况画在一起,寻找Elbow弯点。

f,ax = plt.subplots(figsize=(10,6))
plt.plot(ks,inertias,'-o')
plt.xlabel('Number of clusters,k')
plt.ylabel('Inertia')
plt.title('KMeans 的弯点是多少?')
plt.show()

请添加图片描述

选择cluster_k=3进行分组聚类计算。

# clustering
kc=KMeans(n_clusters=3,random_state=1)
kc.fit(rfm_normalized)# 在原始DataFrame中创建一个簇标签列
cluster_labels = kc.labels_# 计算每个集群的平均 RFM 值和大小
rfm_k3=rfm_k.assign(k_cluster=cluster_labels)
rfm_k3.groupby('k_cluster').agg({'Recency':'mean','Frequency':'mean','Monetary':['mean','count']}).round(0)
RecencyFrequencyMonetary
meanmeanmeancount
k_cluster
0174.015.0290.01464
167.058.01057.01818
217.0245.05935.01015

通过对比RFM模型得出这里的三个聚类0,1,2最具有价值的用户群是k_cluster = 2这个类别的用户。利用K-Means算法把用户进行分类,从而挖掘出价值用户,得出分析的最终结果。

rfm_k3.head()
RecencyFrequencyMonetaryk_cluster
CustomerID
12346.0317177183.601
12347.0311714085.182
12348.067311797.241
12349.010731757.552
12350.030217334.400

找到k_cluster=2的价值用户,对价值用户的购物行为进行针对性分析,比如消费习惯(购物时段)、喜欢购买的商品种类、购物种类的关联性(喜欢一起购买的产品)等等,这样就可以给价值用户提供个性化的消费方案,针对价值用户提供以消费者为中心的智能商业模式。

6、价值用户挖掘结果

6.1 用户画像

用户已经进行分组,接下来看看不同类型用户的消费行为特征。

 #cov=方差系数
def cov(x):return np.std(x)/np.mean(x)rfm.groupby('label').agg({'Recency':['mean','std',cov],'Frequency':['mean','std',cov],'Monetary':['mean','std',cov]}).round(1)
RecencyFrequencyMonetary
meanstdcovmeanstdcovmeanstdcov
label
一般价值客户171.265.70.4125.540.80.31052.6482.30.5
一般保持客户25.921.30.8146.970.60.51174.4470.20.4
一般发展客户36.124.70.734.722.70.7634.3446.50.7
一般挽留客户215.278.70.423.819.70.8424.7357.80.8
重要价值客户163.971.10.4169.686.50.53183.01713.00.5
重要保持客户21.220.10.9302.4465.71.57684.419262.52.5
重要发展客户31.126.20.854.225.00.54716.46711.41.4
重要挽留客户180.371.30.443.431.60.78215.715891.91.9

通过建立RFM模型及评分方法分组得出的结果显示,重要保持客户用户最近一次消费的平均值是21.2天,平均频率是302.4次,平均花费是7684.4英镑。

 #cov=方差系数
def cov(x):return np.std(x)/np.mean(x)rfm_k3_sta=rfm_k3.groupby('k_cluster').agg({'Recency':['mean','std',cov],'Frequency':['mean','std',cov],'Monetary':['mean','std',cov]}).round(1)
rfm_k3_sta 
RecencyFrequencyMonetary
meanstdcovmeanstdcovmeanstdcov
k_cluster
0173.9106.50.614.611.10.8290.3269.50.9
167.066.71.058.339.60.71056.72384.92.3
216.518.81.1244.6395.61.65934.816245.42.7

上面结果是使用K-Means方法得出的结果,价值用户最近一次消费的平均值是16.5 天,平均频率是244.6次,平均花费是5934.8英镑,结果比使用评分方法要更好一些。

6.2 价值用户对销售额的贡献

# 对不同的用户分组和进行K-means的客户数量统计
customer_num=rfm_k3.groupby('k_cluster').agg({'Recency':['count']})
#饼图
fig=plt.figure(figsize=(6,9))
test=[1815,1462,1020]
labels = [u'cluster0',u'cluster1',u'cluster2']
colors=['red','yellow','green']
patches,text1,text2 = plt.pie(test,labels=labels,colors=colors,autopct = '%3.2f%%',labeldistance = 1.2,shadow=False,startangle=90,pctdistance = 0.6)
plt.axis('equal')
plt.legend()
plt.title('各用户分组占比情况 %')
plt.show()

请添加图片描述

上图是用户分组的情况,按照价值贡献情况排名分别是cluster2, cluster0, cluster1,价值用户cluster2占比23.74%。下面看看不同用户分组对销售额的贡献情况。

# 使用 K-means 进行不同客用户分组和相关销售
customer_sales=rfm_k3.groupby('k_cluster').agg({'Monetary':['sum']})
# pie figure
fig=plt.figure(figsize=(6,9))
test=[1911297.113,424054.261,6034667.080]
labels = [u'cluster0',u'cluster1',u'cluster2']
colors=['red','yellow','green']
patches,text1,text2 = plt.pie(test,labels=labels,colors=colors,autopct = '%3.2f%%',labeldistance = 1.2,shadow=False,startangle=90,pctdistance = 0.6)
plt.axis('equal')
plt.legend()
plt.title('各用户分组对销售额占比情况 %')
plt.show()

请添加图片描述

价值用户cluster2占比只有23.74%却贡献了72.10%的销售额,是业务的重点发展对象。

7、总结

本项目利用Python语言对电子零售数据进行了数据分析和挖掘,计算分析主要有两个方面:(1)运营指标统计分析;(2)使用了两种方法RFM模型以及K-Means机器学习算法挖掘价值用户。

(1)通过运营指标统计分析,了解了该电子商务公司的整体运营情况,计算了月销售数量、月销售总额、月均销售额、周销售量、周销售总额、周均销售额、客单价、件单价、连带率,月新客占比,复购率通过这些指标了解到该电子商务公司月销售单数、月销售额在2011年的9月、10月以及11月有明显的增长,周销售额在星期四达到最高值,有64%的顾客购买了两次及以上的商品,相对来说该公司的销售额是较依赖回头客的模式。占总体客户15.99%的重要保持客户,贡献了63.07%的总销售额。。

(2)为了挖掘价值用户,使用了两种方法,第一个方法是利用RFM模型对用户进行评分分组,第二个方法是利用K-Means算法对用户进行“机器学习”分组,两种方法都挖掘出价值用户,比较两种方法得出的用户画像结果,RMF模型中重要保持客户占总体客户15.99%,贡献了63.07%的总销售额,K-Means方法得出的结果更好一些,最终挖掘出的价值用户占比23.74%却贡献了72.10%的销售额。

确定价值用户之后可以进一步学习价值用户的消费习惯,从而提供以消费者为中心的智能商业模式,根据实际应用情况进行多次迭代来优化价值用户挖掘的模型。


http://www.ppmy.cn/news/330624.html

相关文章

论文阅读-16-Class-Balanced Loss Based on Effective Number of Samples

文章目录 翻译Abstract1. Introduction2. Related Work3. Effective Number of Samples3.1 Data Sampling as Random Covering3.2 Mathematical Formulation 4. Class-Balanced Loss4.1 Class-Balanced Softmax Cross-Entropy Loss4.2 Class-Balanced Sigmoid Cross-Entropy Lo…

FX3G和MR-J2S-A伺服的实验

定位实验 时间 2010年4月21日星期三 关键字 FX3G、MR-J2S、PLSY、No.0、No.21、M8029、D8140、D8142、 系统 1&#xff09;FX3G-14MT/ES-A 2&#xff09;MR-J2S-20AHC-MFS23 3&#xff09;GX-Developer V8 伺服设定 1) 设置基本参数No.0&#xff08;控制模式&#…

大数据高级开发工程师——工作流调度器Azkaban(1)

文章目录 工作流调度器AzkabanAzkaban介绍为什么需要工作流调度系统工作流调度实现方式Azkaban简介 Azkaban架构Azkaban基本架构Azkaban架构的三种运行模式1. solo server mode(单机模式&#xff09;2. two server mode3. multiple executor mode Azkaban安装部署编译 azkaban1…

[20140909]oracle cluster index (11g).txt

[20140909]oracle cluster index (11g).txt --应用中除了堆表,很少使用cluser表,也就仅仅在生产系统使用IOT索引组织表. --实际上系统表中许多都是cluster表.比如SYS.TAB$,SYS.COL$等都建立在cluster中. --没事,简单研究一下其存储结构. 1.建立测试环境: SCOTTtest> ver BAN…

【代码】第11章 APP的爬取,appium打开微信朋友圈

11.1 Charles的使用 电脑win10安装Charles&#xff0c;安装受信任的证书&#xff1b; 替换安装目录的charles.jar&#xff0c;得到破解版&#xff1b; 为解决乱码问题&#xff0c;网上一些做法&#xff1a; 将SSL代理设置里add&#xff0c;Host填 * ,port填443&#xff1b; Ch…

UA分享

之前自架短地址服务搜集到的UA&#xff0c;感觉很乱没法分析。看看大佬们有没有兴趣。 Mozilla/5.0 (Linux; U; Android 4.4.2; zh-cn; GT-I9500 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko)Version/4.0 MQQBrowser/5.0 QQ-URL-Manager Mobile Safari/537.36 Mozill…

linux audio A2B

Android 中并没有使用标准的 ALSA&#xff0c;而是使用一个 ALSA 的简化版叫做 tinyalsa。ALSA是Advanced Linux Sound Architecture&#xff0c;高级Linux声音架构的简称,它在Linux操作系统上提供了音频和MIDI&#xff08;Musical Instrument Digital Interface&#xff0c;音…

PHP中的内存破坏漏洞利用(CVE-2014-8142和CVE-2015-0231)(连载之第二篇)

Chuck 2015/02/28 10:41 0x00 前言 作者&#xff1a;Cigital公司的安全顾问Qsl1pknotp&#xff08;Tim Michaud&#xff09; 题目&#xff1a;Exploiting memory corruption bugs in PHP (CVE-2014-8142 and CVE-2015-0231) Part 2&#xff1a;Remote Exploitation 地址&#…