英国电商用户行为数据分析-python

分析案例练习。
数据连接https://archive.ics.uci.edu/ml/datasets/online+retail#。
原文是用jupyter写的,周末专门用spyder实现了一遍,对原文多次调用的绘图,直接写成了函数,同时原文应该有个别语法错误的地方的。

# -*- coding: utf-8 -*-
"""
Created on Thu Aug  6 07:11:46 2020

@author: heart
"""
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import os,time,random,sys,re
import warnings
import seaborn as sns
warnings.filterwarnings('ignore')

os.chdir(r'D:\hard_way\pycharm\202008_exper')

sale_df = pd.read_excel('Online Retail.xlsx', sheet_name='Online Retail',dtype=str)
print(sale_df.dtypes)
sale_df.head()

sale_df.columns

'''
InvoiceNo 发票编号,如果C开头则表示订单被取消
StockCode 产品代码,唯一编码
Description 产品描述
Quantity 订单中产品数量
InvoiceDate 发票日期和时间
UnitPrice 单价
CustomerID 客户编号。
Country 国家
'''

'''
分析思路:
销售情况的描述性统计:
    -订单维度
        -笔单价=总销售额 / 总笔数
        -连带率=售出商品总数 / 总笔数
        -订单金额与订单内商品件数的分布及关系
        
    -客户维度
        -客单价=总销售额 / 总客户数
        -客户消费金额与消费件数的分布及关系
        
    -商品维度
        -商品价位的分布
        -商品单价和商品销售额的关系
        
    -时间维度
        -月 - 销量、销售额、订单数
        -日 - 销量、销售额、订单数
        
    -区位维度
        -出口国家分布情况
        
客户消费行为分析:
    -客户的生命周期
    -客户的留存情况
    -客户的购买周期
'''

# 列名重命名
sale_df.rename(columns={'InvoiceDate':'InvoiceTime'},inplace=True)

# 删除重复值
rows_before = sale_df.shape[0]
sale_df.drop_duplicates(inplace=True)
rows_after = sale_df.shape[0]
print('原数据行数:',rows_before,'现数据行数:',rows_after,'删除数据行数:',rows_before - rows_after)

# 重设索引,并删除原索引
sale_df.reset_index(drop=True,inplace=True)

# 查看缺失值
sale_df.isnull().sum()
'''
InvoiceNo           0
StockCode           0
Description      1454
Quantity            0
InvoiceTime         0
UnitPrice           0
CustomerID     135037
Country             0
'''
sale_df[sale_df['CustomerID'] =='0']
# CustomerID 客户编号,约20%的空值,计划才有0填充,故先确认客户编号是否存在id为0的客户。
# 填充缺失id
sale_df['CustomerID'].fillna('0',inplace=True)

#一致化处理
sale_df['InvoiceTime'] = pd.to_datetime(sale_df['InvoiceTime'],errors='coerce')
sale_df['date'] = pd.to_datetime(sale_df['InvoiceTime'].dt.date,errors='coerce')
sale_df['Month'] = sale_df['InvoiceTime'].astype('datetime64[M]')
sale_df.iloc[:3]
# 缺失值检查
sale_df.isnull().sum()

# 字段类型转换
sale_df.Quantity = sale_df.Quantity.astype('int32')
sale_df.UnitPrice = sale_df.UnitPrice.astype('float')
sale_df.CustomerID = sale_df.CustomerID.astype('int32')
# 计算总价
sale_df['Sumprice'] = sale_df.Quantity * sale_df.UnitPrice

# 异常值探查处理
sale_df.describe()
'''
            Quantity      UnitPrice     CustomerID       Sumprice
count  536641.000000  536641.000000  536641.000000  536641.000000
mean        9.620029       4.632656   11435.904653      18.123861
std       219.130156      97.233118    6795.044250     380.656263
min    -80995.000000  -11062.060000       0.000000 -168469.600000
25%         1.000000       1.250000       0.000000       3.750000
50%         3.000000       2.080000   14336.000000       9.870000
75%        10.000000       4.130000   16241.000000      17.400000
max     80995.000000   38970.000000   18287.000000  168469.600000
'''
# 存在负值
sale_df[(sale_df.Quantity <=0) | (sale_df.UnitPrice <=0) ]

# 对C字头的取消订单,进行分类
query_c = sale_df.InvoiceNo.str[0] =='C'
# 只含取消订单
sales_cancel = sale_df.loc[query_c,:].copy()
sales_success = sale_df.loc[-query_c,:].copy()

# 为取消订单表 增加原订单号
sales_cancel['SrcInvoiceNo'] = sales_cancel.InvoiceNo.str.split('C',expand=True)[1]

# 将取消订单和未取消定标进行合并
pd.merge(sales_cancel,sales_success,left_on='SrcInvoiceNo',right_on='InvoiceNo')
'''
确认取消订单和成功订单并无对应关系
'''
print('取消订单行数:%s,成功订单行数:%s.' % (sales_cancel.shape,sales_success.shape))

# 单价为0的免费订单
query_free = sales_success.UnitPrice == 0
sales_cancel = sales_success.loc[query_free, :].copy()
sales_success = sales_success.loc[-query_free, :]
# 都只含普通订单

sales_success.describe()
# 存在单价为负值的订单

quer_minus = sales_success['UnitPrice'] < 0
sales_success.loc[quer_minus,:]
# 两笔订单 A开头,是为调整坏账,先将其剔除
sales_success = sales_success.loc[-quer_minus,:]
sales_success.shape[0]

# 致辞 数据清洗告一段落

'''
Part4.分析与可视化
'''
# 订单维度
invoice_groped = sales_success.groupby('InvoiceNo')[['Quantity','Sumprice']].sum()
invoice_groped.describe()
# 笔单价 连带率
# 笔单价 = 总销售额 比 总订单笔数
# 连带率 = 售出商品总数 比 总订单笔数
'''
结论1,产生有效订单19960笔,笔单价533英镑,连带率279件,说明以批发性质的订单为主。
结论2,订单交易金额和订单内商品件数,其均值都高于中位数,订单交易金额的均值甚至高于Q3分位数,
说明订单总体差异大,存在部分购买力极强的客户
'''
# 绘制图形
def plt_scatter(data,cola,colb,n=50):
    '''
    绘制散点图,cola 为x轴,colb为y轴,n为分组数
    '''
    fig,ax = plt.subplots(1,2,figsize=(14,4))
    ax[0].scatter(data[cola],data[colb],color='r',s=6)
    ax[0].set_title('%s & %s' % (cola,colb))
    ax[0].set_xlabel('%s' % (cola))
    ax[0].set_ylabel('%s' % (colb))
    
    ax[1].scatter(data[data[cola]<n][cola],
                  data[data[cola]<n][colb],
                  color='r',s=6)
    ax[1].set_title('%s & %s (Below %s)' % (cola,colb,n))
    ax[1].set_xlabel('%s' % (cola))
    ax[1].set_ylabel('%s' % (colb))
    
def plt_hist(data,cola,n=1000):
    '''
    绘制柱形图,cola 为x轴,n为分组数
    '''
    sns.set()
    fig,ax = plt.subplots(1,2,figsize=(14,4))
    data[cola].plot(kind='hist',bins=100,ax=ax[0],color='c' )
    ax[0].set_title('%s Distribution' % (cola))
    ax[0].set_ylabel('Frequency')
    ax[0].set_xlabel('%s' % (cola))
    
    data[data[cola]<=n][cola].plot(kind='hist',bins=100,
        title='AvgPrice Distribution (Below 100)',ax=ax[1],color='c' )
    ax[1].set_title('%s Distribution (Below %s)' % (cola,n))
    ax[1].set_ylabel('Frequency')
    ax[1].set_xlabel('%s' % (cola))
    
    
plt_hist(invoice_groped,'Sumprice',1000)    
# 部分订单的交易金额过大,影响图表的可读性,筛去1000英镑以上的订单;

'''
结论3,订单金额集中在400英镑以内,三个峰值分别为20英镑以内,100-230英镑、300-30英镑。
其中300-320英镑的订单数量特别多,不知道是否存在某种共性,之后可以进行进一步探究
'''
# 对订单内商品数量的分布绘制柱形图
plt_hist(invoice_groped,'Quantity',2000)

'''
特征:订单内商品数量呈现出很典型的长尾分布,大部分订单的商品数量在250件内,商品数量
越多,订单数相对越少
'''
# 为了进一步探究订单交易金额与订单内商品件数的关系,绘制散点图
plt_scatter(invoice_groped,'Quantity','Sumprice',20000,)

'''
结论4,总体来说订单交易金额与订单内商品件数是正相关的,订单内的商品数越多,订单金额
也相对越高,但在Quantity靠近0的位置也有若干量少价高的订单,后续可以试探究
'''

# 客户维度
# 仅对含有CustomerId的客户进行分析;
sales_customer = sales_success[sales_success.CustomerID != 0 ].copy()
customer_groped = sales_customer.groupby(['CustomerID','InvoiceNo'])[['Quantity','Sumprice']].sum().reset_index()
customer_groped = customer_groped.groupby('CustomerID').agg({'InvoiceNo':np.size,
                                                             'Quantity':np.sum,
                                                             'Sumprice':np.sum})
customer_groped.describe()
'''
特征:人均购买4笔,25%的用户近下过一次单,并未留存。每位客户平均购买1187件,甚至超过Q3
分位数,最多的用户购买19w件,客单价为2049英镑,平均值同样超过Q3分位值,说明客户的购买力
存在较大差距,存在小部分的高消费用户拉高了人均消费
'''
# 绘制图形,观察客户消费金额的分布;
plt_hist(customer_groped,'Sumprice',5000)
# 图中反应 大部分用户的消费能力确实不高,高消费用户在图上几乎看不到。 符号消费行为的行业规律
# 截取5000英镑以内的客户

'''
结论5,与之前定的金额的多峰分布相比,客户消费金额的分布呈现单峰长尾形态,金额更为集中,
峰值在83-333英镑之间
'''
# 绘制消费金额与消费件数的散点图
plt_scatter(customer_groped,'Quantity','Sumprice',25000)

# 商品维度发现相同的商品在不同的订单中单价不同,可知商品的单价会发生波动。以商品10002为例
sales_success.loc[sales_success.StockCode == '10002',:].UnitPrice.value_counts()
'''
0.85    50
1.66    14
1.63     7
Name: UnitPrice, dtype: int64
'''
# 下面求每件商品的平均价格,思路:平均价格 = 该商品的总销售额 比 该商品的销售数量。
# 具体,先按商品编号进行分组,对数量和总价分别求和,然后取商得到平均价格
goods_groped = sales_success.groupby('StockCode')[['Quantity','Sumprice']].sum()
goods_groped['AvgPrice'] = goods_groped.Sumprice / goods_groped.Quantity
goods_groped.head()

plt_hist(goods_groped, 'AvgPrice',100)
# 峰值是1-2英镑,单价10英镑以上的商品已经很少见,该电商的定位主要是价格低的小商品市场
# 绘制 商品单价和商品销量的散点图,观测那种价位的商品更受欢迎

plt_scatter(goods_groped,'AvgPrice','Quantity',50)
# 从商品销量上来看,低于5英镑的低价区商品最多,更受客户欢迎
# 绘制商品单价和销售金额的散点图


plt_scatter(goods_groped,'AvgPrice','Sumprice')
# 低价区的商品,不仅销售数量上一骑绝尘,也构成了销售额的主要部分;
# 高价的商品虽然单价高昂,但销量很低,并没有带来太多的销售额,据此,建议平台采购部门可以遴选售价低于10英镑的产品,来进一步扩充低价区的品类

# 时间维度。
time_groped = sales_success.groupby('InvoiceNo').agg({'date':np.min,
                                                      'Month':np.min,
                                                      'Quantity':np.sum,
                                                      'Sumprice':np.sum}).reset_index()
month = time_groped.groupby('Month').agg({'Quantity':np.sum,
                                          'Sumprice':np.sum,
                                          'InvoiceNo':np.size})
# 主坐标轴,次坐标轴 
monthplt = month.plot(secondary_y='InvoiceNo',x_compat=True,figsize=(12,4),marker='o',)
monthplt.set_ylabel('Quantity & Sumprice')
monthplt.right_ax.set_ylabel('Order Quantites')

time_groped = time_groped.set_index('date')

day = time_groped.groupby(time_groped.index).agg({
    'Quantity':np.sum,'Sumprice':np.sum,'InvoiceNo':np.size
    }).plot(secondary_y='InvoiceNo',figsize=(12,4))
day.set_ylabel('Quantity & Sumprice')
day.right_ax.set_ylabel('Order Quantites')

# 销量和销售额趋势趋同,随销量变化而涨跌
# 但注意在最后一天,销量、销售额显著激增,

# 获取20111001只20111209的数据
time_groppart = time_groped['2011-10-01':'2011-12-09']
day_part = time_groppart.groupby(time_groppart.index).agg({
    'Quantity':np.sum,'Sumprice':np.sum,'InvoiceNo':np.size
    }).plot(secondary_y='InvoiceNo',figsize=(12,4),marker='o')
day_part.set_ylabel('Quantity & Sumprice')
day_part.right_ax.set_ylabel('Order Quantites')
# 2011年12月9日订单量大幅下降时,却创造了样本区间内销量和销售额的历史新高。推测存在某笔或
# 某几笔购买力极大的订单,从而使得销售额大幅上升
sales_success[sales_success.date == '2011-12-09'].sort_values('Sumprice',ascending=False).head()
# 显然一位客户购买了8w件商品,总金额16w

# 区位维度
sale_country = sales_success.drop_duplicates(subset=['CustomerID','Country'])
# 客户id及其国家的关系表
country_groped = sales_customer.groupby('CustomerID')[['Sumprice']].sum()
# 客户分组计算消费总额
country_groped = country_groped.merge(sale_country,on=['CustomerID'])
# 按国家再次分组,计算各国客户的消费总额和客户总数
country_groped = country_groped.groupby('Country').agg({'Sumprice_x':np.sum,'CustomerID':np.size})
country_groped.columns = ['Sumprice','CustomerID']
country_groped['Avgamount'] = country_groped.Sumprice / country_groped.CustomerID
country_groped.sort_values('Sumprice',ascending=False).head(20)
'''
结论:绝大部分客户仍来自英国本土,主要境外收入也多为英国周边国家,基本以英国为圆心向外辐射
'''

# 客户生命周期
sales_customer = sales_success[sales_success.CustomerID != 0 ].copy()
# 查看用户初次消费时间和末次消费时间
mindate = sales_customer.groupby('CustomerID')[['date']].min()
maxdate = sales_customer.groupby('CustomerID')[['date']].max()
mindate.date.value_counts().head()
maxdate.date.value_counts().head()

left_time = (maxdate - mindate)
left_time.head()
'''
0 days --->表示用户在某一天内消费国,未能留存 (首次和末次消费都在同一天)
'''
left_time.describe()
'''
结论:共有4338为客户,其平均生命周期为130天,中位数则为93天,说明有部分生命周期很长的忠实客户拉高了均值。而最小值和Q1分位数都
是0天,,说明存在25%以上的客户仅消费了一次,生命周期的分布呈现两极分化的状态
'''
left_time['left_time'] = left_time.date.dt.days  # timedelta类型转化为数值
left_time.left_time.hist(bins=20,color='c',figsize=(12,4))
'''
结论:许多客户只消费了一次,没有留存下来。需要重视初次购物体验,需要记忆改进。
BUt,350天左右出现一个小高峰,下一步排除掉生命周期为0天的客户再观测下
'''
left_time[left_time.left_time>0]['left_time'].hist(bins=100,figsize=(12,4),color='c')
'''
结论:生命周期1-75天的客户数略高于75-170天的。1-75天的客户有引导空间
其次生命周期在170-330天内的客户质量较高。
330天以上生命周期的客户,属于死忠粉,用户粘性极高。
'''
left_time[left_time.left_time>0]['left_time'].mean()
customer_retention = sales_customer.merge(mindate,on='CustomerID',how='inner',suffixes=['','Min'])
customer_retention['datediff'] = (customer_retention.date - customer_retention.dateMin).dt.days
# 计算本次消费日期与首次消费日期的时间差,并转化为数值

date_bins = [0,3,7,30,60,90,180]  #时间差分段
customer_retention['datediffbin'] = pd.cut(customer_retention.datediff,bins=date_bins,)
customer_retention['datediffbin'].value_counts()
retention_pivot = customer_retention.pivot_table(index=['CustomerID'],columns=['datediffbin'],
                                                        values =['Sumprice'],
                                                        aggfunc=np.sum)
retention_pivot_trans = retention_pivot.fillna(0).applymap(lambda x:1 if x>0 else 0)
# applymap 对应DataFrame元素级别的操作
# apply 对应DataFrame行列的操作

# 统计留存客户首次消费后各时间段内的购买率
(retention_pivot_trans.sum() / retention_pivot_trans.count()).plot.bar()
# 老客户在第一次消费之后,3日内有过二次消费的占比3.2%,4-7天有过二次消费的占比6.6%,依次类推

# 客户的购买周期。对其相邻的两次下佛诶日期相减,
# 先对客户编号级其消费日期都相同的数据进行去重。参数keep=first 表示保留重复值中的第一条。
sale_cycle = customer_retention.drop_duplicates(subset=['CustomerID','date'],keep='first')
sale_cycle.sort_values('date',ascending=True,inplace=True)

def diff(group):
    '''定义diff函数,计算相邻两次消费的时间差'''
    d = group.datediff - group.datediff.shift()  # shift() 是往上偏移一个位置,shift(-1)是往下偏移一个位置
    return d

last_diff = sale_cycle.groupby('CustomerID').apply(diff)
last_diff.head()

sale_cycle[customer_retention.CustomerID == 12347]  #验证
# 绘制柱状图,订单统计的购买周期分布图
last_diff.hist(bins=70,figsize=(12,4),color='c')

# 对客户维度,进行统计,取订单时间周期的均值
last_diff_customer = last_diff.groupby('CustomerID').mean()
last_diff_customer.hist(bins=70,figsize=(12,4),color='c')
# 右偏分布,峰值分布15-70天,说明大部分留存客户的购买周期集中于此,建议可以每隔30天左右对客户进行优惠活动的推送,比较符合大部分客户的购买周期

emmm,安利下知乎小哥哥的文章地址,https://zhuanlan.zhihu.com/p/65907187。缺乏这方面的完整的数据分析。也学到了好几个pandas语法,基本都是平时没有用过的。

  • 2
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值