一、数据集介绍
该数据集是跨国数据集,包含2010年12月12日至2011年12月9日期间发生的所有在英国注册的非商店在线零售业务的交易。该公司主要销售礼品,并且大部分客户是批发商。
二、字段介绍
InvoiceNo: 发票号码,每笔交易分配唯一的6位整数,而退货订单的代码以字母'c'开头。
StockCode: 产品代码,每个不同的产品分配唯一的5位整数。
Description: 产品描述,对每件产品的简略描述。
Quantity: 产品数量,每笔交易的每件产品的数量。
InvoiceDate: 发票日期和时间,每笔交易发生的日期和时间。
UnitPrice: 单价(英镑),单位产品价格。
CustomerID:顾客号码,每个客户分配唯一的5位整数。
Country: 国家的名字,每个客户所在国家/地区的名称。
三、分析目的
通过分析销售数据来了解在线零售业务的消费情况,分析用户消费数据分析用户消费行为。
分析问题:
- 商店消费情况
- 每月成交金额
- 每月销售金额
- 每月消费人数
- 每月订单数量
- 每月消费产品数量
- 每月客单价
- 消费国家分布
- 下单时间分布
- 每月退款产品数
- 每月退款金额
- 每月退款率
- 用户消费行为
- 用户消费次数、用户消费金额、用户购买产品数量
- 消费次数与消费金额关系
- 用户购买周期
- 新用户、活跃用户、不活跃用户、回流用户、回流率
- 复购率和回购率
- RFM
四、分析内容
载入库和获取数据
# 载入库
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
# 设置中文编码和负号的正常显示
plt.rcParams['font.sans-serif']=['SimHei']
plt.rcParams['axes.unicode_minus']=False
# 导入数据
df = pd.read_csv('data.csv', encoding='ISO-8859-1')
df.head()
df.info()
用户ID部分缺失,无法通过其他信息填充,因此后期清洗将用户ID缺失记录删除。
df.describe()
# 将产品数量为负数的记录数与退款订单的记录数进行对比
df[df['Quantity']<0].count() == df['InvoiceNo'].str.startswith('C').sum()
部分产品数量是负数,经过验证是退款订单的产品数为负数
数据清洗和处理
# 删除用户ID缺失的记录
df = df[df['CustomerID'].notnull()].copy()
# 数据类型转换
df['InvoiceDate'] = pd.to_datetime(df['InvoiceDate'])
df['CustomerID'] = df['CustomerID'].astype('O')
# 计算时间变量
df['Month'] = df['InvoiceDate'].values.astype('datetime64[M]')
df['Day'] = df['InvoiceDate'].dt.day
df['Dayofweek'] = df['InvoiceDate'].dt.dayofweek
df['Hour'] = df['InvoiceDate'].dt.hour
# 计算价格
df['Price'] = df['Quantity'] * df['UnitPrice']
探索性数据分析
商店销售情况分析
原数据集粒度为订购商品,转换为订单粒度,看一下数据情况
# 订单描述性统计分析
df.groupby('InvoiceNo')[['Quantity','Price']].sum().describe()
因为零售商大部分客户是批发商,用户每笔订单平均产品数量和平均金额波动大,中位数和75分位数相差挺大,说明部分批发商批发数量大。
每月成交金额
# 商店消费情况绘图函数
def store_plot(data, xlab, ylab, title):
plt.figure(figsize=(10,5))
plt.plot(data)
plt.xlabel(xlab)
plt.ylabel(ylab)
plt.title(title)
# 每月成交金额,包含退款订单的金额
store_plot(data=df[df['Quantity']>0].groupby('Month')['price'].sum(),
xlab='月份',
ylab='成交金额',
title='每月成交金额')
从2010年12月至2011年11月月成交金额总体呈现上涨趋势,从2010年8月开始高涨,可能是因为公司主要销售的产品包括礼品,而后半年国外有各种节日导致对礼品需求大增,也有可能是接近年底有各种促销活动,2011年2月和2011年4月都有一个明显的下降,暂时不清楚具体原因。
每月销售金额
# 每月销售金额,成交金额-退款金额
store_plot(data=df.groupby('Month')['Price'].sum(),
xlab='月份',
ylab='销售金额',
title='每月销售金额')
月销售金额跟月成交金额趋势非常相似,可以看到2011年1月销售金额较成交金额有较大差异,可能是由于这个月的退款金额比较大。
每月消费人数
# 每月消费人数
store_plot(data=df[df['Quantity']>0].groupby('Month')['CustomerID'].nunique(),
xlab='月份',
ylab='消费人数',
title='每月消费人数')
每月订单数量
# 每月订单数量
store_plot(data=df[df['Quantity']>0].groupby('Month')['InvoiceNo'].nunique(),
xlab='月份',
ylab='订单数量',
title='每月订单数量')
每月消费产品数量
# 每月销售产品数量
store_plot(data=df[df['Quantity']>0].groupby('Month')['Quantity'].sum(),
xlab='月份',
ylab='销售产品数量',
title='每月销售产品数量')
每月客单价
# 每月客单价
store_plot(data=df[df['Quantity']>0].groupby('Month')['Price'].sum()/df[df['Quantity']>0].groupby('Month')['InvoiceNo'].nunique(),
xlab='月份',
ylab='客单价',
title='每月客单价')
- 月订单数量、月消费人数和销售产品数量趋势都跟月成交金额趋势相似,每月客单价趋势与月成交金额趋势差别比较大
- 2011年1月较2010年12月消费人数和订单数量下降,销售产品数量和客单价上升,尤其是客单价很高,因此2011年1月与2010年12月成交金额持平主要原因是1月客单价远远高于12月客单价
- 2011年2月和2011年4月的成交金额环比减少是由于消费人数、订单数量、销售产品数量和客单价都减少的原因
- 2011年5月至2011年10月总体呈上升趋势原因是消费人数、订单数量、销售产品数量和客单价总体持平或上升
- 2011年11月成交金额大涨主要是因为购买用户数、订单数和购买产品数增多,这个月客户购买的产品价格比较低
- 由于数据集中只有成交信息、产品信息、地区和时间,具体上升或下降的原因没办法具体判断,推测可能是年底的促销活动或节日引起的
消费国家分布
# 国家用户数量分布
plt.figure(figsize=(12,8))
df[df['Quantity']>0].groupby('Country')['CustomerID'].nunique().sort_values(ascending=True).plot.barh()
plt.xlabel('用户数量')
plt.ylabel('国家')
plt.title('不同国家用户数量')
# 国家订单数量分布
plt.figure(figsize=(12,8))
df[df['Quantity']>0].groupby('Country')['InvoiceNo'].nunique().sort_values(ascending=True).plot.barh()
plt.xlabel('订单数量')
plt.ylabel('国家')
plt.title('不同国家订单数量')
# 国家成交金额分布
plt.figure(figsize=(12,8))
df[df['Quantity']>0].groupby('Country')['Price'].sum().sort_values(ascending=True).plot.barh()
plt.xlabel('成交金额')
plt.ylabel('国家')
plt.title('不同国家成交金额')
- 因为该公司在英国,因此可以看到客户数量、订单数量和成交金额英国都远远超过其他国家,英国是客户来源的主要国家,属于重要价值国家。
- 除英国外,德国和法国在用户数量、订单数量和成交金额方面都属于前5,属于重点维持国家。
- 荷兰、爱尔兰和澳大利亚虽然用户数量和订单数量少,但是成交金额位于前6,这三个国家的客单价比较高,属于重点发展国家。
- 重点维持国家和重点发展国家可以进行适当推广、完善物流服务等等。
下单时间分布
# 星期
plt.figure(figsize=(12,8))
df[df['Quantity']>0].groupby('Dayofweek')['InvoiceNo'].nunique().plot.bar()
plt.xticks(range(6),['周一','周二','周三','周四','周五','周日'], rotation=0)
plt.xlabel('星期')
plt.ylabel('订单数')
plt.title('订单数随星期变化')
绝大多数订单在周一至周四完成。
# 小时
plt.figure(figsize=(12,8))
df[df['Quantity']>0].groupby('Hour')['InvoiceNo'].nunique().plot()
plt.xlabel('小时')
plt.ylabel('订单数')
plt.title('订单数随小时变化')
订单集中在上午10点至下午3点,中午12点是高峰期,这段时间内注意维持好网站的稳定性。
每月退款订单数
# 每月退款订单数量
store_plot(data=df[df['Quantity']<0].groupby('Month')['InvoiceNo'].nunique(),
xlab='月份',
ylab='退款订单数量',
title='每月退款订单数量')
每月退款订单数量2010年12月至2011年10月围绕280上下波动,略微呈上涨趋势,虽然2011年11月订单数量非常大,但同时退款订单数量也非常大。
每月退款金额
# 每月退款金额
store_plot(data=df[df['Quantity']<0].groupby('Month')['Price'].sum().abs(),
xlab='月份',
ylab='退款金额',
title='每月退款金额')
- 每月退款金额围绕25000波动。
- 2011年1月退款订单数量不大,因为客单价高,所以退款金额非常高,也印证了我们之前的猜测。
- 由于2011年11月客单价很低,即使退款订单很多,退款金额也不大。
每月退款率
# 每月退款率
store_plot(data=df[df['Quantity']<0].groupby('Month')['InvoiceNo'].nunique()/df[df['Quantity']>0].groupby('Month')['InvoiceNo'].nunique(),
xlab='月份',
ylab='退款率',
title='每月退款率')
每月退款率呈下降趋势,这是个不错的现象。
用户消费行为分析
用户消费次数、用户消费金额、用户购买产品数量
将数据集粒度转换成用户,看一下用户消费行为。
df[df['Quantity']>0].groupby('CustomerID').agg({'InvoiceNo':'nunique',
'Quantity':'sum',
'Price':'sum'}).describe()
- 超过一半客户至少消费了两次,甚至有客户消费了210次,属于非常忠诚的用户了。
- 因为大部分客户是批发商,超过75%的客户购买数量在100以上。
- 用户平均消费金额是2053元,标准差是8988元,用户平均消费金额大于75分位数,存在部分高额消费用户。
消费次数与消费金额关系
# 消费次数与消费金额关系
plt.figure(figsize=(12,8))
plt.scatter(x=df[df['Quantity']>0].groupby('CustomerID')['InvoiceNo'].nunique(),
y=df[df['Quantity']>0].groupby('CustomerID')['Price'].sum())
plt.xlabel('消费次数')
plt.ylabel('消费金额')
plt.title('消费次数与消费金额关系')
plt.savefig('消费次数与消费金额关系.jpeg')
消费次数和消费金额有较弱的相关性,用户消费次数越大,消费金额越大,可以引导用户多次消费,那么多长时间召回呢?我们来看看用户的消费间距。
用户消费周期
# 用户消费周期
purchase_time = df[df['Quantity']>0].groupby('CustomerID').apply(lambda x: x['InvoiceDate']-x['InvoiceDate'].shift()).dt.days
purchase_time.describe()
purchase_time[purchase_time>0].describe()
至少消费两次的用户有一半消费间隔在一个月内,75%消费间隔在两个月内,可以在10天、28天、58天三个时间点对用户进行推送和提醒。
新用户、活跃用户、不活跃用户、回流用户、回流率
pivoted_amount = df[df['Quantity']>0].pivot_table(index='CustomerID',
columns='Month',
values='Price',
aggfunc='mean').fillna(0)
columns_month = df['Month'].sort_values().astype('str').unique()
pivoted_amount.columns = columns_month
pivoted_purchase = pivoted_amount.applymap(lambda x: 1 if x>0 else 0)
# 用户分层
def active_status(data):
status = []
for i in range(13):
# 若本月没有消费
if data[i] == 0:
if len(status) > 0:
if status[i-1] == 'unreg':
status.append('unreg')
else:
status.append('unactive')
else:
status.append('unreg')
# 若本月消费
else:
if len(status) == 0:
status.append('new')
else:
if status[i-1] == 'unactive':
status.append('return')
elif status[i-1] == 'unreg':
status.append('new')
else:
status.append('active')
return pd.Series(status, index=columns_month)
pivoted_purchase_status = pivoted_purchase.apply(lambda x: active_status(x), axis=1)
purchase_status_counts = pivoted_purchase_status.replace('unreg',np.NaN).apply(lambda x:pd.value_counts(x))
purchase_status_counts.fillna(0).T.plot.area(figsize=(12,6))
plt.xticks(range(12), columns_month)
plt.title('用户分层')
# 回流率
return_rate = purchase_status_counts.apply(lambda x:x / x.sum(), axis=1)
return_rate.loc['return'].plot(figsize=(12,6))
plt.xticks(range(1,13), columns_month)
plt.title('回流率')
在后期消费中新用户占比减少,回流用户和活跃用户增多,回流率一直在上涨,整体用户质量不错。
复购率和回购率
# 复购率
pivoted_counts = df[df['Quantity']>0].pivot_table(index='CustomerID',
columns='Month',
values='InvoiceNo',
aggfunc='nunique').fillna(0)
columns_month = df['Month'].sort_values().astype('str').unique()
pivoted_counts.columns = columns_month
pivoted_counts_transf = pivoted_counts.applymap(lambda x: 1 if x>1 else np.NaN if x==0 else 0)
(pivoted_counts_transf.sum() / pivoted_counts_transf.count()).plot(figsize=(12,6))
plt.xticks(range(12), columns_month)
plt.title('复购率'
由于早期新用户占比大,前期复购率比后期复购率低,且后期复购率波动大,但复购率基本在20%以上。
# 回购率
def purchase_return(data):
status = []
for i in range(12):
if data[i] == 1:
if data[i+1] == 1:
status.append(1)
if data[i+1] == 0:
status.append(0)
else:
status.append(np.NaN)
status.append(np.NaN)
return pd.Series(status, index=columns_month)
pivoted_purchase_return = pivoted_purchase.apply(purchase_return,axis=1)
(pivoted_purchase_return.sum() / pivoted_purchase_return.count()).plot(figsize=(12,6))
plt.xticks(range(12), columns_month)
plt.title('回购率')
用户的回购率高于复购率,都大于30%,后期因为回流用户增多,这段时间老用户的忠诚度表现较好,所以后期回购率较高。
RFM
# rfm
rfm = df[df['Quantity']>0].groupby('InvoiceNo').agg({'daydiff':'min',
'Quantity':'sum',
'Price':'sum'})
rfm.rename(columns={'daydiff':'R',
'Quantity':'F',
'Price':'M'},inplace=True)
def rfm_func(x):
level = x.apply(lambda x: '1' if x > 0 else '0')
label = level.R + level.F + level.M
d = {
'111':'重要价值客户',
'011':'重要保持客户',
'101':'重要挽留客户',
'001':'重要发展客户',
'110':'一般价值客户',
'010':'一般保持客户',
'100':'一般挽留客户',
'000':'一般发展客户'
}
result = d[label]
return result
rfm['label'] = rfm[['R','F','M']].apply(lambda x: x-x.median()).apply(rfm_func, axis=1)
rfm.groupby('label').sum()
rfm.groupby('label')['M'].count()
五、总结
- 从2011年5月开始成交金额总体上升,11月达到高峰,成交金额主要受到消费人数、订单数量和客单价的影响,但由于数据只包含部分成交数据没有办法得知上涨或下降的具体原因,推测可能是年中以后节日增多导致礼品需求量增大或促销活动引起。
- 英国是主要的客户来源地区,而德国、法国、荷兰、爱尔兰和澳大利亚成交金额表现不错,可以尝试拓展这些国家的市场;订单下单时间高峰期主要在10点至15点,这段时间要维护好网站的稳定。
- 退款订单数略微呈上涨趋势,2011年11月退款订单数最高;退款金额总体持平,2011年1月退款金额高;退款率在下降,这是个好现象。
- 多数用户至少消费了两次,由于大部分客户是批发商,购买数量和金额较大;用户消费次数越多,消费金额越大,可以在半个月、一个月、两个月三个时间段对用户进行召回,引导客户进行多次消费。
- 活跃用户比例和回流用户比例随时间增大,后期老用户忠诚度较好,因此复购率和回购率表现不错;可以通过RFM模型对用户价值进行分类,不同价值用户采取不同的业务决策。