一、数据说明
1.1 数据来源
https://www.kesci.com/mw/dataset/5f64a07b71c700003072df60/file
1.2 数据集说明
本数据集选取了2019年1月1日志2020年5月28日之间,有104557条记录,共计11个字段
1.3 字段说明
- id:索引
- orderID:订单编号
- userID:用户编号
- goodsID:商品编号
- orderAmount:订单总额
- payment:实际支付金额
- chanelID:渠道编号
- platfromType:购买渠道类型
- orderTime:订单创建时间
- payTime:订单支付时间
- chargeback:是否退款
1.4 分析工具
Python 3
二、分析思路
三、分析过程
3.1 导入包和数据集
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
import seaborn as sns
plt.rcParams['font.sans-serif'] = ['SimHei']#(显示中文)
plt.rcParams['axes.unicode_minus'] = False#(显示负数)
data=pd.read_excel(r'D:\home\order2019.xlsx')
data.head()
3.2 数据初步探索
导入数据后发现数据集id列是整个数据集的索引,因此将id列设为数据集的索引。
data=pd.read_excel(r'D:\home\order2019.xlsx',index_col=0)
3.2.1 查看数据
data.shape
data.info()
处理后数据集共有10个字段,104557条记录。
3.2.2 查看重复值
data.duplicated().sum()
无重复值
3.2.3 查看缺失值
data.isnull().sum()
根据统计,只有chanelID列存在8个缺失值,因为缺失值数量少,对总数据影响不大,可以采用众数对其进行填充。
data['chanelID'].fillna(data['chanelID'].mode()[0],inplace=True)
data['chanelID'].isnull().sum()
再次查看数据集缺失值情况
data.info()
经处理,数据集已经不存在缺失值。
3.2.4 异常值处理
经观察显示的数据发现platfromType列存在无意义空格所以需要将其去除。
data['platfromType']=[i.replace(' ','') for i in data['platfromType']]
处理完空格后,简单查看数据汇总信息
data.describe()
处理payment列异常值
通过观察发现汇总信息的最小值发现payment列即实际支付金额列存在负值,正常情况下付款金额不会为负,因此payment列存在异常值。
data[data['payment']<0]
data.drop(index=data[data.payment<0].index,inplace=True)
处理订单总额与实际支付金额异常值
再次观察数据汇总表,最大值和四分之三位数显示orderAmount(订单总额)小于payment(实际支付金额),一般情况下客户在电商平台上购买商品一般会存在使用优惠券的行为,所以订单总额一般都会大于或者等于实际支付金额,说明此处也存在异常值。
data[data['orderAmount']
处理订单支付时间异常
除了付款金额外还需要考虑付款时间的异常值即orderTime(订单创建时间)晚于payTime(订单付款时间)
data[data['orderTime']>data['payTime']]
data.drop(index=data[data['orderTime']>data['payTime']].index,inplace=True)
处理支付时间过长的记录
正常平台支付时间都会设置为半小时,超出半小时该订单就会自动取消,因此需要删除支付时间超过30分钟的订单记录。
data['payInterval']=(data['payTime']-data['orderTime']).dt.total_seconds()
data.drop(index=data[data['payInterval']>1800].index,inplace=True)
重置索引
data=data.reset_index(drop=True)
再次检查数据基本信息
data.info()
清洗后的数据量为101588条记录。
因为后续会做RFM分析,数据时间使用的精确度到“天”,在此处添加辅助数据列payTime_D
data['payTime_D']=pd.to_datetime(data['payTime'].dt.date)
data.head()
添加天数列。统计查看处理后的数据发现因为数据截止到2020年1月1日,天数计算公式为:统计截止日期-开始日期,统计截止日期可以取分析开始当天或者统计数据截至处,此处取整月份即2020年1月1日。
data['days']=(pd.to_datetime('2020-01-01')-pd.to_datetime(data['payTime_D'])).dt.days
data.head()
3.3 数据分析
3.3.1整体销售分析
GMV
data_y= data.groupby(pd.Grouper(key='payTime_D',freq='M')).sum()['orderAmount']
x =[f'{y}年{m}月' for y,m in zip(data_y.index.year, data_y.index.month)]
plt.figure(figsize=(16,9),dpi=160)
plt.plot(range(len(x)), data_y)
plt.text(0,data_y.max(),f'总销售额情况为{round(data_y.sum(),2)}元',fontsize=20)
plt.xticks(range(len(x)), x)
for x,y in zip(range(len(x)),data_y.values):
plt.text(x, y,int(y),ha='center')
plt.xlabel('时间')
plt.title('GMV',fontsize=25)
plt.style.use('ggplot')
plt.show()
根据统计结果显示,2019年5月-8月及2019年11月-12月都保持着较高的商品交易总额。2019年的GMV整体呈上升趋势,但是2020年GMV急剧下滑,因为疫情影响,我国当时经济处于停滞状态,所有行业都受到了巨大的冲击,各电商平台盈利下降。
2019年每月销售情况
data_not_chargeback = data[data.chargeback=='否']
data_y= data_not_chargeback.groupby(pd.Grouper(key='payTime_D',freq='M')).sum()['payment']
x =[f'{y}年{m}月' for y,m in zip(data_y.index.year, data_y.index.month)]
plt.figure(figsize=(16,9),dpi=160)
plt.plot(range(len(x)), data_y)
plt.text(0,data_y.max(),f'总销售额情况为{round(data_y.sum(),2)}元',fontsize=20)
plt.xticks(range(len(x)), x)
for x,y in zip(range(len(x)),data_y.values):
plt.text(x, y,int(y),ha='center')
plt.xlabel('时间')
plt.title('每月销售额',fontsize=25)
plt.show()
根据统计可知,销售额最高发生2019年11月,同年12月份销售额仅低于11月,2019年11月份的销售额约占消费总额的10%。2019年的整体销售情况呈上升趋势,6月份达到销售的小高峰,6月份到11月之间有小幅度下降趋势但在11月份达到销售顶峰。但是到了2020年由于疫情的影响,销售额急剧下降一直到同年五月份平台销售情况都十分平稳,并没有上升的趋势。
猜想2019年11月的销售额最高的原因是因为双十一的活动拉动了当月的营业额,在全平台活动期间购物优惠力度大,用户消费更加集中。
2019年11月每日销售情况
data_nov= data[(data['payTime_D'] >=pd.to_datetime('2019-11-01')) & (data['payTime_D'] <= pd.to_datetime('2019-11-30'))]
data_not_chargeback = data_nov[data.chargeback=='否']
data_y= data_not_chargeback.groupby(pd.Grouper(key='payTime_D',freq='D')).sum()['payment']
x =[f'{m}月{d}日' for m,d in zip(data_y.index.month, data_y.index.day)]
plt.figure(figsize=(16,9),dpi=160)
plt.plot(range(len(x)), data_y)
plt.text(0,data_y.max(),f'11月总销售额情况为{round(data_y.sum(),2)}元',fontsize=20)
plt.xticks(range(len(x)), x,rotation=45)
for x,y in zip(range(len(x)),data_y.values):
plt.text(x, y,int(y),ha='center')
plt.xlabel('时间')
plt.title('11月每日销售情况',fontsize=25)
plt.show()
根据折线图可以看出十一月销量最高的是16日。一般来说11日是双十一活动日,理应销售额达到最高,但是统计结果并非如此,这说明该平台在同类竞争平台当中竞争力并不强,可替代性非常高。
2019年11月16日销售情况
data_detail= data[(data['payTime_D'] ==pd.to_datetime('2019-11-16'))]
data_not_chargeback = data_detail[data.chargeback=='否']
data_y= data_not_chargeback.groupby(pd.Grouper(key='payTime',freq='H')).sum()['payment']
x =[f'{d}日{h}时' for d,h in zip(data_y.index.day, data_y.index.hour)]
plt.figure(figsize=(16,9),dpi=160)
plt.plot(range(len(x)), data_y)
plt.text(0,data_y.max(),f'11月总销售额情况为{round(data_y.sum(),2)}元',fontsize=20)
plt.xticks(range(len(x)), x,rotation=45)
for x,y in zip(range(len(x)),data_y.values):
plt.text(x, y,int(y),ha='center')
plt.xlabel('时间')
plt.title('11月16日销售情况',fontsize=25)
plt.show()
根据统计既然过来看,当日成交量最高时段在晚上19:00
2019年11月16日18:00-19:00销售情况
data_not_chargeback = data_H[data.chargeback=='否']
data_y= data_not_chargeback.groupby(pd.Grouper(key='payTime',freq='MIN')).sum()['payment']
x =[f'{h}时{min}分' for h,min in zip(data_y.index.hour, data_y.index.minute)]
plt.figure(figsize=(16,9),dpi=160)
plt.plot(range(len(x)), data_y)
plt.text(0,data_y.max(),f'11月总销售额情况为{round(data_y.sum(),2)}元',fontsize=20)
plt.xticks(range(len(x)), x,rotation=45)
for x,y in zip(range(len(x)),data_y.values):
plt.text(x, y,int(y),ha='center')
plt.xlabel('时间')
plt.title('11月16日18:00-19:00销售情况',fontsize=25)
plt.show()
经查看发现在18:26分时出现了一笔5080元的订单,这笔订单价格远远高于其他订单金额,因此使得当天销售额高于当与其他天的销售额。
不同时段销售量分析
sale_time=data.groupby('orderTime')['orderID'].count()
plt.figure(figsize=(16,9),dpi=100)
s=data['orderTime'].dt.floor('30T')
data['orderTime']=s.dt.strftime('%H:%M')+'-'+(s+pd.Timedelta(29*60,unit='s')).dt.strftime('%H:%M')
sale_time=data.groupby('orderTime')['orderID'].count()
sale_time_x=sale_time.index
sale_time_y=sale_time.values
plt.style.use('ggplot')
plt.xticks(range(len(sale_time_x)),sale_time_x,rotation=45)
plt.title('时段销售量分析')
rect=plt.bar(sale_time_x,sale_time_y,width=0.3,color='r')
根据统计结果显示,销售高峰时段在中午12:00-14:29及晚上19:00-20:59正处于用户休息时间。
畅销商品
data_not_chargeback = data[data.chargeback=='否']
top_10=data[data.chargeback=='否'].groupby('goodsID').count().sort_values('orderID',ascending=False)[0:10].index.tolist()
top_10_saleCount=data[data.chargeback=='否'].groupby('goodsID').count()['orderID'].sort_values(ascending=False)[0:10].values.tolist()
y=top_10_saleCount
x=top_10
top_10_sale=[{key:values} for key,values in zip(top_10,top_10_saleCount)]
top_10_sale
经统计,畅销商品的销量在200-300件左右。
滞销商品
data_not_chargeback = data[data.chargeback=='否']
tail_10=data[data.chargeback=='否'].groupby('goodsID').count().sort_values('orderID',ascending=True)[0:10].index.tolist()
tail_10_saleCount=data[data.chargeback=='否'].groupby('goodsID').count()['orderID'].sort_values(ascending=True)[0:10].values.tolist()
y=tail_10_saleCount
x=tail_10
tail_10_sale=[{key:values} for key,values in zip(tail_10,tail_10_saleCount)]
tail_10_sale
统计结果显示滞销商品的总销量都在30件以下。
渠道平台销量统计
data_not_chargeback = data[data.chargeback=='否']
channel_sale=data_not_chargeback.groupby(pd.Grouper(key='platfromType')).sum()['payment']
x=list(channel_sale.index)
plt.figure(figsize=(16,9),dpi=120)
plt.bar(range(len(x)), channel_sale)
plt.xticks(range(len(x)), x)
for x,y in zip(range(len(x)),channel_sale.values):
plt.text(x, y,int(y),ha='center')
plt.ylabel('销售额')
plt.xlabel('销售渠道')
plt.title('不同销售渠道的销售情况',fontsize=25)
plt.show()
统计结果显示,用户的主要购买渠道是是APP和WechatMP,网页端、阿里平台、微信小程序商城和手机网站的使用的人数相对较少。由此,平台可以根据统计结果适当优化在各渠道的投资比例分配,对于销量极少的wap平台可以减少在此平台的推广费用,甚至可以考虑不在投资,将这部分资金转移到其他销量较好有潜力的渠道。
退款情况
data['chargeback'].value_counts().plot.pie(labels=['否','是'],autopct='%.0f%%',fontsize=15,figsize=(6,6))
plt.title('是否退款')
观察发现,平台实际成交单数占总订单的87%,远远大于退款的比例,成交率高说明产品本身质量较好,受到了用户的肯定。
转化率
from pyecharts.charts import Funnel
from pyecharts import options as opts
# 本数据集里的客户都存在支付购买行为,但在支付完成后又存在部分退款行为,因此,chargeback字段的值为“否”时才说明本次交易实际成交
rates = pd.Series({
'创建':data['orderTime'].count(),
'付款':data['payTime'].count(),
'实际成交':data[data.chargeback=='否'].shape[0]
}
,name='订单量').to_frame()
# 绝对转化率=各环节订单数/订单创建数
rates['整体转化率'] = rates['订单量'].apply(lambda x: round(x*100/rates.iloc[0,0],2))
print(rates)
c=(
Funnel()
.add(
'转化率',
[list(z) for z in zip(rates.index,rates['整体转化率'])],
# 设置标签位置及数据展现形式
label_opts=opts.LabelOpts(position='inside',formatter='{b}:{c}')
) # 填充漏斗图内容
.set_global_opts(title_opts=opts.TitleOpts(title='整体转化率(%)'))
)
c.render_notebook()
根据统计结果可知,订单的创建到付款的转化率很高,最后的实际成交也约为总订单的87%,说明转化率非常高,商品本身价格合理,用户给出的评价多为好评,售前和售后都能使用户得到保障,可以继续保持现有方案。
3.3.2 个体消费情况分析
人均购买频次
total_buy_time = data[data.chargeback=='否'].count()['userID']
total_paying_user_num = data[data.chargeback=='否'].nunique()['userID']
user_avg_buy_time = total_buy_time/total_paying_user_num
user_avg_buy_time
实际成交的用户购买数量大多都在10件以下,大多数消费者都购买了一件左右。
复购率
user_buy_time = data[data.chargeback=='否'].groupby('userID').count()
user_twice_time = user_buy_time[user_buy_time['goodsID']>2].count()['goodsID']
user_buy = data[data.chargeback=='否'].nunique()['userID']
rebuy_rate = user_twice_time/user_buy
round(rebuy_rate,2)
商品复购率为4%,偏低,因此可以说明平台的商品大多都是一次性的交易,忠实用户少,再加上2020年疫情影响,全平台的销售受到了巨大的冲击,导致平台商品复购率低。平台方需要应季推出往常有较高复购率的商品,找到高复购率和低复购率的差别,选择销售价格区间合适的商品。平台需要对老用户和会员设置适当的优惠活动,针对用户特点进行会员营销,并选定复购率较高的商品进行优惠推广。
小结:经过统计分析发现平台本身的复购率和人均购买次数都偏低说明店铺现在还在处于用户获取阶段,主要应该关注的就是用户留存问题,在解决用户留存培养一批忠实用户期间注重营销策略的制定,为将来复购等问题提供基础。
RFM
建立数据透视表,计算RFM三个指标值
df2 = data_not_chargeback.pivot_table(index="userID",
values=["orderID", "days", "payment"],
aggfunc={"orderID":"count", "days":"min", "payment":"sum"})
df2 = df2[['days', 'orderID', 'payment']]
df2.columns = ['R', 'F', 'M']
df2.reset_index()
df2.head(10)
分别给RFM三个指标打分,并为用户打上标签
avg_r = df2['R'].mean()
avg_f = df2['F'].mean()
avg_m = df2['M'].mean()
df2['R_score'] = [1 if i>avg_r else 0 for i in df2['R']]
df2['F_score'] = [1 if i>avg_f else 0 for i in df2['F']]
df2['M_score'] = [1 if i>avg_m else 0 for i in df2['M']]
# 给用户打标签
def functions(x):
if x.iloc[0]==1 and x.iloc[1]==1 and x.iloc[2]==1:
return "重要价值用户"
elif x.iloc[0]==0 and x.iloc[1]==1 and x.iloc[2]==1:
return "重要保持用户"
elif x.iloc[0]==0 and x.iloc[1]==0 and x.iloc[2]==1:
return "重要挽留用户"
elif x.iloc[0]==1 and x.iloc[1]==0 and x.iloc[2]==1:
return "重要发展用户"
elif x.iloc[0]==1 and x.iloc[1]==1 and x.iloc[2]==0:
return "一般价值用户"
elif x.iloc[0]==1 and x.iloc[1]==0 and x.iloc[2]==0:
return "一般发展用户"
elif x.iloc[0]==0 and x.iloc[1]==1 and x.iloc[2]==0:
return "一般保持用户"
elif x.iloc[0]==0 and x.iloc[1]==0 and x.iloc[2]==0:
return "一般挽留用户"
df2['标签'] = df2[['R_score', 'F_score', 'M_score']].apply(functions,axis=1)
df2.sample(10)
不同类型客户人数对比
df3 = df2.groupby("标签").agg({"标签":"count"})
df3['不同客户的占比'] = df3["标签"].apply(lambda x:x/np.sum(df3["标签"]))
df3.index.name = '客户标签'
df3 = df3.sort_values(by="标签", ascending=True)
plt.figure(figsize=(6,4), dpi=100)
x = df3.index
y = df3['标签']
plt.barh(x, height=0.5, width=y, align='center')
plt.title('不同类型客户的人数对比')
for x,y in enumerate(y):
plt.text(y+450, x, y, va='center', fontsize=14)
plt.xticks(np.arange(0,30001,2000), rotation=45)
plt.tight_layout()
不同类型客户累计消费金额
df3 = df2.groupby("标签").agg({"M":"sum"})
df3["M"] = df3["M"].apply(lambda x:round(x))
df3["不同客户的占比"] = df3["M"].apply(lambda x:x/np.sum(df3["M"]))
df3 = df3.sort_values(by="M",ascending=True)
plt.figure(figsize=(6,4),dpi=120)
x = df3.index
y = df3["M"]
plt.barh(x,height=0.5,width=y,align="center")
plt.title("不同类型客户累计消费金额")
for x,y in enumerate(y):
plt.text(y,x,y,va="center",fontsize=14)
plt.tight_layout()
根据统计结果可知,一般发展用户和一般挽留用户占比最高,一般价值用户占比最少。由此可以发现,平台本身处于用户获取阶段,此时需要紧急解决的是用户留存问题,提高用户粘性。由于主要创造价值的用户是重要保持用户和重要发展用户,一般价值用户创造价值并不高,所以需要平台采取积极的手段维系重要客户,针对重要客户的特点分别制定策略,对于潜力一般的客户可以少放精力来维系。从商品角度来看,可以从商品的季节性、质量和种类上进行调整,增加客户的粘性;从平台角度来看,加大平台的宣传力度,优化渠道推广,针对新老用户分别采取不同的优惠力度和营销方式,多组织活动使用户频繁的参与进来。